diff --git a/patches/drupal-core-11.1.3.patch b/patches/drupal-core-11.1.3.patch
new file mode 100644
index 0000000000000000000000000000000000000000..fe080e2aef9b71dec1baa042048e81a9f3944af2
--- /dev/null
+++ b/patches/drupal-core-11.1.3.patch
@@ -0,0 +1,16723 @@
+diff --git a/core/lib/Drupal/Component/Datetime/DateTimePlus.php b/core/lib/Drupal/Component/Datetime/DateTimePlus.php
+index 35f2e80f0d3354c2f110d2bbf5be2d189d9484e2..7a1f44aa1a2d12123346e01915e4c6313700236f 100644
+--- a/core/lib/Drupal/Component/Datetime/DateTimePlus.php
++++ b/core/lib/Drupal/Component/Datetime/DateTimePlus.php
+@@ -3,6 +3,7 @@
+ namespace Drupal\Component\Datetime;
+ 
+ use Drupal\Component\Utility\ToStringTrait;
++use MongoDB\BSON\UTCDateTime;
+ 
+ /**
+  * Wraps DateTime().
+@@ -203,6 +204,12 @@ public static function createFromArray(array $date_parts, $timezone = NULL, $set
+    *   If the timestamp is not numeric.
+    */
+   public static function createFromTimestamp($timestamp, $timezone = NULL, $settings = []) {
++    // In MongoDB timestamp are stored as instances of MongoDB\BSON\UTCDateTime.
++    if ($timestamp instanceof UTCDateTime) {
++      $timestamp = (int) $timestamp->__toString();
++      $timestamp = $timestamp / 1000;
++      $timestamp = (string) $timestamp;
++    }
+     if (!is_numeric($timestamp)) {
+       throw new \InvalidArgumentException('The timestamp must be numeric.');
+     }
+diff --git a/core/lib/Drupal/Core/Batch/BatchStorage.php b/core/lib/Drupal/Core/Batch/BatchStorage.php
+index 46f9fb97c0d6359da0cbd84279ba4bdd92b77523..fbc1d8dda75fbd8df70a779b60ab3103816ae634 100644
+--- a/core/lib/Drupal/Core/Batch/BatchStorage.php
++++ b/core/lib/Drupal/Core/Batch/BatchStorage.php
+@@ -6,6 +6,7 @@
+ use Drupal\Core\Access\CsrfTokenGenerator;
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\DatabaseException;
++use MongoDB\BSON\UTCDateTime;
+ use Symfony\Component\HttpFoundation\Session\SessionInterface;
+ 
+ class BatchStorage implements BatchStorageInterface {
+@@ -15,6 +16,15 @@ class BatchStorage implements BatchStorageInterface {
+    */
+   const TABLE_NAME = 'batch';
+ 
++  /**
++   * Indicator for the existence of the database table.
++   *
++   * This variable is only used by the database driver for MongoDB.
++   *
++   * @var bool
++   */
++  protected $tableExists = FALSE;
++
+   /**
+    * Constructs the database batch storage service.
+    *
+@@ -44,7 +54,7 @@ public function load($id) {
+     try {
+       $batch = $this->connection->select('batch', 'b')
+         ->fields('b', ['batch'])
+-        ->condition('bid', $id)
++        ->condition('bid', (int) $id)
+         ->condition('token', $this->csrfToken->get($id))
+         ->execute()
+         ->fetchField();
+@@ -65,7 +75,7 @@ public function load($id) {
+   public function delete($id) {
+     try {
+       $this->connection->delete('batch')
+-        ->condition('bid', $id)
++        ->condition('bid', (int) $id)
+         ->execute();
+     }
+     catch (\Exception $e) {
+@@ -77,10 +87,16 @@ public function delete($id) {
+    * {@inheritdoc}
+    */
+   public function update(array $batch) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     try {
+       $this->connection->update('batch')
+         ->fields(['batch' => serialize($batch)])
+-        ->condition('bid', $batch['id'])
++        ->condition('bid', (int) $batch['id'])
+         ->execute();
+     }
+     catch (\Exception $e) {
+@@ -93,9 +109,14 @@ public function update(array $batch) {
+    */
+   public function cleanup() {
+     try {
++      $timestamp = $this->time->getRequestTime() - 864000;
++      if ($this->connection->driver() == 'mongodb') {
++        $timestamp = new UTCDateTime($timestamp * 1000);
++      }
++
+       // Cleanup the batch table and the queue for failed batches.
+       $this->connection->delete('batch')
+-        ->condition('timestamp', $this->time->getRequestTime() - 864000, '<')
++        ->condition('timestamp', $timestamp, '<')
+         ->execute();
+     }
+     catch (\Exception $e) {
+@@ -107,6 +128,12 @@ public function cleanup() {
+    * {@inheritdoc}
+    */
+   public function create(array $batch) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     // Ensure that a session is started before using the CSRF token generator,
+     // and update the database record.
+     $this->session->start();
+@@ -115,7 +142,7 @@ public function create(array $batch) {
+         'token' => $this->csrfToken->get($batch['id']),
+         'batch' => serialize($batch),
+       ])
+-      ->condition('bid', $batch['id'])
++      ->condition('bid', (int) $batch['id'])
+       ->execute();
+   }
+ 
+@@ -126,22 +153,39 @@ public function create(array $batch) {
+    *   A batch id.
+    */
+   public function getId(): int {
+-    $try_again = FALSE;
+-    try {
+-      // The batch table might not yet exist.
+-      return $this->doInsertBatchRecord();
+-    }
+-    catch (\Exception $e) {
+-      // If there was an exception, try to create the table.
+-      if (!$try_again = $this->ensureTableExists()) {
+-        // If the exception happened for other reason than the missing table,
+-        // propagate the exception.
+-        throw $e;
++    if ($this->connection->driver() == 'mongodb') {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      if (!$this->tableExists) {
++        $this->tableExists = $this->ensureTableExists();
+       }
++
++      return $this->connection->insert('batch')
++        ->fields([
++          'timestamp' => new UTCDateTime($this->time->getRequestTime() * 1000),
++          'token' => '',
++          'batch' => NULL,
++        ])
++        ->execute();
+     }
+-    // Now that the table has been created, try again if necessary.
+-    if ($try_again) {
+-      return $this->doInsertBatchRecord();
++    else {
++      $try_again = FALSE;
++      try {
++        // The batch table might not yet exist.
++        return $this->doInsertBatchRecord();
++      }
++      catch (\Exception $e) {
++        // If there was an exception, try to create the table.
++        if (!$try_again = $this->ensureTableExists()) {
++          // If the exception happened for other reason than the missing table,
++          // propagate the exception.
++          throw $e;
++        }
++      }
++      // Now that the table has been created, try again if necessary.
++      if ($try_again) {
++        return $this->doInsertBatchRecord();
++      }
+     }
+   }
+ 
+@@ -205,7 +249,7 @@ protected function catchException(\Exception $e) {
+    * @internal
+    */
+   public function schemaDefinition() {
+-    return [
++    $schema = [
+       'description' => 'Stores details about batches (processes that run in multiple HTTP requests).',
+       'fields' => [
+         'bid' => [
+@@ -237,6 +281,13 @@ public function schemaDefinition() {
+         'token' => ['token'],
+       ],
+     ];
++
++    if ($this->connection->driver() == 'mongodb') {
++      // For MongoDB timestamps are stored as real dates.
++      $schema['fields']['timestamp']['type'] = 'date';
++    }
++
++    return $schema;
+   }
+ 
+ }
+diff --git a/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php b/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php
+index 00bf806c3dee1df743646160e9f5110fdb3034b4..9eb5b0471e67fa197ce2a0acf968c4ad688ee060 100644
+--- a/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php
++++ b/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php
+@@ -32,6 +32,15 @@ trait CacheTagsChecksumTrait {
+    */
+   protected $tagCache = [];
+ 
++  /**
++   * Indicator for the existence of the database table.
++   *
++   * This variable is only used by the database driver for MongoDB.
++   *
++   * @var bool
++   */
++  protected $tableExists = FALSE;
++
+   /**
+    * Callback to be invoked just after a database transaction gets committed.
+    *
+@@ -51,6 +60,13 @@ public function rootTransactionEndCallback($success) {
+    * Implements \Drupal\Core\Cache\CacheTagsInvalidatorInterface::invalidateTags()
+    */
+   public function invalidateTags(array $tags) {
++    if (isset($this->connection) && ($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
++    // Only invalidate tags once per request unless they are written again.
+     foreach ($tags as $key => $tag) {
+       if (isset($this->invalidatedTags[$tag])) {
+         unset($tags[$key]);
+@@ -80,6 +96,12 @@ public function invalidateTags(array $tags) {
+    * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::getCurrentChecksum()
+    */
+   public function getCurrentChecksum(array $tags) {
++    if (isset($this->connection) && ($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     // Any cache writes in this request containing cache tags whose invalidation
+     // has been delayed due to an in-progress transaction must not be read by
+     // any other request, so use a nonsensical checksum which will cause any
+diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+index 8b45010914fef5a96c04b1a1b7eacb34c23c5b6b..09b655c0e94efe45d058ede82a592019d6edd028 100644
+--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
++++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+@@ -8,6 +8,9 @@
+ use Drupal\Component\Utility\Crypt;
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\DatabaseException;
++use Drupal\mongodb\Driver\Database\mongodb\Statement;
++use MongoDB\BSON\Binary;
++use MongoDB\BSON\Decimal128;
+ 
+ /**
+  * Defines a default cache implementation.
+@@ -68,6 +71,15 @@ class DatabaseBackend implements CacheBackendInterface {
+    */
+   protected $checksumProvider;
+ 
++  /**
++   * Indicator for the existence of the database table.
++   *
++   * This variable is only used by the database driver for MongoDB.
++   *
++   * @var bool
++   */
++  protected $tableExists = FALSE;
++
+   /**
+    * Constructs a DatabaseBackend object.
+    *
+@@ -128,7 +140,23 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
+     // ::select() is a much smaller proportion of the request.
+     $result = [];
+     try {
+-      $result = $this->connection->query('SELECT [cid], [data], [created], [expire], [serialized], [tags], [checksum] FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE [cid] IN ( :cids[] ) ORDER BY [cid]', [':cids[]' => array_keys($cid_mapping)]);
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . $this->bin;
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          ['cid' => ['$in' => array_keys($cid_mapping)]],
++          [
++            'projection' => ['cid' => 1, 'data' => 1, 'created' => 1, 'expire' => 1, 'serialized' => 1, 'tags' => 1, 'checksum' => 1, '_id' => 0],
++            'sort' => ['cid' => 1],
++            'session' => $this->connection->getMongodbSession(),
++          ]
++        );
++
++        $statement = new Statement($this->connection, $cursor, ['cid', 'data', 'created', 'expire', 'serialized', 'tags', 'checksum']);
++        $result = $statement->execute()->fetchAll();
++      }
++      else {
++        $result = $this->connection->query('SELECT [cid], [data], [created], [expire], [serialized], [tags], [checksum] FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE [cid] IN ( :cids[] ) ORDER BY [cid]', [':cids[]' => array_keys($cid_mapping)]);
++      }
+     }
+     catch (\Exception) {
+       // Nothing to do.
+@@ -205,22 +233,33 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
+    * {@inheritdoc}
+    */
+   public function setMultiple(array $items) {
+-    $try_again = FALSE;
+-    try {
+-      // The bin might not yet exist.
++    if ($this->connection->driver() == 'mongodb') {
++      // For MongoDB the table need to exists. Otherwise MongoDB creates one
++      // without the correct validation.
++      if (!$this->tableExists) {
++        $this->tableExists = $this->ensureBinExists();
++      }
++
+       $this->doSetMultiple($items);
+     }
+-    catch (\Exception $e) {
+-      // If there was an exception, try to create the bins.
+-      if (!$try_again = $this->ensureBinExists()) {
+-        // If the exception happened for other reason than the missing bin
+-        // table, propagate the exception.
+-        throw $e;
++    else {
++      $try_again = FALSE;
++      try {
++        // The bin might not yet exist.
++        $this->doSetMultiple($items);
++      }
++      catch (\Exception $e) {
++        // If there was an exception, try to create the bins.
++        if (!$try_again = $this->ensureBinExists()) {
++          // If the exception happened for other reason than the missing bin
++          // table, propagate the exception.
++          throw $e;
++        }
++      }
++      // Now that the bin has been created, try again if necessary.
++      if ($try_again) {
++        $this->doSetMultiple($items);
+       }
+-    }
+-    // Now that the bin has been created, try again if necessary.
+-    if ($try_again) {
+-      $this->doSetMultiple($items);
+     }
+   }
+ 
+@@ -264,14 +303,29 @@ protected function doSetMultiple(array $items) {
+           continue;
+         }
+ 
+-        if (!is_string($item['data'])) {
+-          $fields['data'] = $this->serializer->encode($item['data']);
+-          $fields['serialized'] = 1;
++        if ($this->connection->driver() == 'mongodb') {
++          $fields['created'] = new Decimal128($fields['created']);
++
++          if (!is_string($item['data'])) {
++            $fields['data'] = new Binary(serialize($item['data']), Binary::TYPE_GENERIC);
++            $fields['serialized'] = TRUE;
++          }
++          else {
++            $fields['data'] = new Binary($item['data'], Binary::TYPE_GENERIC);
++            $fields['serialized'] = FALSE;
++          }
+         }
+         else {
+-          $fields['data'] = $item['data'];
+-          $fields['serialized'] = 0;
++          if (!is_string($item['data'])) {
++            $fields['data'] = $this->serializer->encode($item['data']);
++            $fields['serialized'] = 1;
++          }
++          else {
++            $fields['data'] = $item['data'];
++            $fields['serialized'] = 0;
++          }
+         }
++
+         $values[] = $fields;
+       }
+ 
+@@ -308,6 +362,12 @@ public function delete($cid) {
+    * {@inheritdoc}
+    */
+   public function deleteMultiple(array $cids) {
++    if (($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureBinExists();
++    }
++
+     $cids = array_values(array_map([$this, 'normalizeCid'], $cids));
+     try {
+       // Delete in chunks when a large array is passed.
+@@ -331,6 +391,12 @@ public function deleteMultiple(array $cids) {
+    * {@inheritdoc}
+    */
+   public function deleteAll() {
++    if (($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureBinExists();
++    }
++
+     try {
+       $this->connection->truncate($this->bin)->execute();
+     }
+@@ -357,6 +423,15 @@ public function invalidate($cid) {
+   public function invalidateMultiple(array $cids) {
+     $cids = array_values(array_map([$this, 'normalizeCid'], $cids));
+     try {
++      if ($this->connection->driver() == 'mongodb') {
++        $session = $this->connection->getMongodbSession();
++        $session_started = FALSE;
++        if (!$session->isInTransaction()) {
++          $session->startTransaction();
++          $session_started = TRUE;
++        }
++      }
++
+       // Update in chunks when a large array is passed.
+       $requestTime = $this->time->getRequestTime();
+       foreach (array_chunk($cids, 1000) as $cids_chunk) {
+@@ -365,9 +440,18 @@ public function invalidateMultiple(array $cids) {
+           ->condition('cid', $cids_chunk, 'IN')
+           ->execute();
+       }
++
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->commitTransaction();
++      }
+     }
+     catch (\Exception $e) {
+-      $this->catchException($e);
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->abortTransaction();
++      }
++      else {
++        $this->catchException($e);
++      }
+     }
+   }
+ 
+@@ -394,12 +478,16 @@ public function garbageCollection() {
+       if ($this->maxRows !== static::MAXIMUM_NONE) {
+         $first_invalid_create_time = $this->connection->select($this->bin)
+           ->fields($this->bin, ['created'])
+-          ->orderBy("{$this->bin}.created", 'DESC')
++          ->orderBy('created', 'DESC')
+           ->range($this->maxRows, 1)
+           ->execute()
+           ->fetchField();
+ 
+         if ($first_invalid_create_time) {
++          if ($this->connection->driver() == 'mongodb') {
++            // The created field is saved in MongoDB as Decimal128.
++            $first_invalid_create_time = new Decimal128($first_invalid_create_time);
++          }
+           $this->connection->delete($this->bin)
+             ->condition('created', $first_invalid_create_time, '<=')
+             ->execute();
+@@ -562,6 +650,18 @@ public function schemaDefinition() {
+       ],
+       'primary key' => ['cid'],
+     ];
++
++    if ($this->connection->driver() == 'mongodb') {
++      // The date field cannot be transformed to a real date field, because it can
++      // be set to infinity with the value -1.
++      $schema['fields']['serialized'] = [
++        'description' => 'A flag to indicate whether content is serialized (TRUE) or not (FALSE).',
++        'type' => 'bool',
++        'not null' => TRUE,
++        'default' => FALSE,
++      ];
++    }
++
+     return $schema;
+   }
+ 
+diff --git a/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php b/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
+index aa41952128c240b3d1a4bd00a664dd70834f00fc..f4c99cfb2d824ad6b94a87eee4fb9bf419cc2980 100644
+--- a/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
++++ b/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
+@@ -4,6 +4,7 @@
+ 
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\DatabaseException;
++use Drupal\mongodb\Driver\Database\mongodb\Statement;
+ 
+ /**
+  * Cache tags invalidations checksum implementation that uses the database.
+@@ -35,11 +36,24 @@ public function __construct(Connection $connection) {
+   protected function doInvalidateTags(array $tags) {
+     try {
+       foreach ($tags as $tag) {
+-        $this->connection->merge('cachetags')
+-          ->insertFields(['invalidations' => 1])
+-          ->expression('invalidations', '[invalidations] + 1')
+-          ->key('tag', $tag)
+-          ->execute();
++        if ($this->connection->driver() == 'mongodb') {
++          $prefixed_table = $this->connection->getPrefix() . 'cachetags';
++          $this->connection->getConnection()->selectCollection($prefixed_table)->updateOne(
++            ['tag' => $tag],
++            ['$inc' => ['invalidations' => 1]],
++            [
++              'upsert' => TRUE,
++              'session' => $this->connection->getMongodbSession(),
++            ],
++          );
++        }
++        else {
++          $this->connection->merge('cachetags')
++            ->insertFields(['invalidations' => 1])
++            ->expression('invalidations', '[invalidations] + 1')
++            ->key('tag', $tag)
++            ->execute();
++        }
+       }
+     }
+     catch (\Exception $e) {
+@@ -57,8 +71,22 @@ protected function doInvalidateTags(array $tags) {
+    */
+   protected function getTagInvalidationCounts(array $tags) {
+     try {
+-      return $this->connection->query('SELECT [tag], [invalidations] FROM {cachetags} WHERE [tag] IN ( :tags[] )', [':tags[]' => $tags])
+-        ->fetchAllKeyed();
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . 'cachetags';
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          ['tag' => ['$in' => array_values($tags)]],
++          [
++            'projection' => ['tag' => 1, 'invalidations' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ],
++        );
++        $statement = new Statement($this->connection, $cursor, ['tag', 'invalidations']);
++        return $statement->execute()->fetchAllKeyed();
++      }
++      else {
++        return $this->connection->query('SELECT [tag], [invalidations] FROM {cachetags} WHERE [tag] IN ( :tags[] )', [':tags[]' => $tags])
++          ->fetchAllKeyed();
++      }
+     }
+     catch (\Exception $e) {
+       // If the table does not exist yet, create.
+diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
+index 70933f8fb0eb3006b43603b72e24a9c040714568..a8721eb3d380106995914e60c7aa132df2322183 100644
+--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
++++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
+@@ -5,6 +5,7 @@
+ use Drupal\Component\Utility\Crypt;
+ use Drupal\Component\Utility\NestedArray;
+ use Drupal\Core\Config\Entity\ConfigDependencyManager;
++use Drupal\Core\Database\Database;
+ use Drupal\Core\Extension\ExtensionPathResolver;
+ use Drupal\Core\Installer\InstallerKernel;
+ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+@@ -74,6 +75,13 @@ class ConfigInstaller implements ConfigInstallerInterface {
+    */
+   protected $extensionPathResolver;
+ 
++  /**
++   * The database override directory.
++   *
++   * @var string
++   */
++  protected $databaseDriverOverrideDirectory;
++
+   /**
+    * Constructs the configuration installer.
+    *
+@@ -100,6 +108,34 @@ public function __construct(ConfigFactoryInterface $config_factory, StorageInter
+     $this->eventDispatcher = $event_dispatcher;
+     $this->installProfile = $install_profile;
+     $this->extensionPathResolver = $extension_path_resolver;
++
++    // Init the base database driver override directory. We do this here to do
++    // it only once.
++    $this->initBaseDatabaseDriverOverrideDirectory();
++  }
++
++  /**
++   * Initiate the base database driver override directory.
++   */
++  protected function initBaseDatabaseDriverOverrideDirectory(): void {
++    if (Database::isActiveConnection()) {
++      $database_driver_override_directory = $this->extensionPathResolver->getPath('module', \Drupal::database()->getProvider()) . '/' . InstallStorage::CONFIG_OVERRIDES_DIRECTORY;
++      if (is_dir($database_driver_override_directory)) {
++        // Only set the base database driver when the module providing the
++        // database driver has one.
++        $this->databaseDriverOverrideDirectory = $database_driver_override_directory;
++      }
++    }
++  }
++
++  /**
++   * Check whether the database driver has a config override directory.
++   *
++   * @return bool
++   *   Return TRUE when the database driver has the config override directory.
++   */
++  protected function hasBaseDatabaseDriverOverrideDirectory(): bool {
++    return (bool) $this->databaseDriverOverrideDirectory;
+   }
+ 
+   /**
+@@ -128,13 +164,21 @@ public function installDefaultConfig($type, $name) {
+         $prefix = $name . '.';
+       }
+ 
++      $database_driver_override_storage = NULL;
++      if ($this->hasBaseDatabaseDriverOverrideDirectory()) {
++        $database_driver_override_config_directory = $this->databaseDriverOverrideDirectory . '/' . $name . '/install';
++        if (is_dir($database_driver_override_config_directory)) {
++          $database_driver_override_storage = new FileStorage($database_driver_override_config_directory, StorageInterface::DEFAULT_COLLECTION);
++        }
++      }
++
+       // Gets profile storages to search for overrides if necessary.
+       $profile_storages = $this->getProfileStorages($name);
+ 
+       // Gather information about all the supported collections.
+       $collection_info = $this->configManager->getConfigCollectionInfo();
+       foreach ($collection_info->getCollectionNames() as $collection) {
+-        $config_to_create = $this->getConfigToCreate($storage, $collection, $prefix, $profile_storages);
++        $config_to_create = $this->getConfigToCreate($storage, $collection, $database_driver_override_storage, $prefix, $profile_storages);
+         if ($name == $this->drupalGetProfile()) {
+           // If we're installing a profile ensure simple configuration that
+           // already exists is excluded as it will have already been written.
+@@ -161,7 +205,16 @@ public function installDefaultConfig($type, $name) {
+       if (is_dir($optional_install_path)) {
+         // Install any optional config the module provides.
+         $storage = new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION);
+-        $this->installOptionalConfig($storage, '');
++
++        $database_driver_override_storage = NULL;
++        if ($this->hasBaseDatabaseDriverOverrideDirectory()) {
++          $database_driver_override_config_directory = $this->databaseDriverOverrideDirectory . '/' . $name . '/optional';
++          if (is_dir($database_driver_override_config_directory)) {
++            $database_driver_override_storage = new FileStorage($database_driver_override_config_directory, StorageInterface::DEFAULT_COLLECTION);
++          }
++        }
++
++        $this->installOptionalConfig($storage, '', $database_driver_override_storage);
+       }
+       // Install any optional configuration entities whose dependencies can now
+       // be met. This searches all the installed modules config/optional
+@@ -177,7 +230,7 @@ public function installDefaultConfig($type, $name) {
+   /**
+    * {@inheritdoc}
+    */
+-  public function installOptionalConfig(?StorageInterface $storage = NULL, $dependency = []) {
++  public function installOptionalConfig(?StorageInterface $storage = NULL, $dependency = [], ?StorageInterface $database_driver_override_storage = NULL) {
+     $profile = $this->drupalGetProfile();
+     $enabled_extensions = $this->getEnabledExtensions();
+     $existing_config = $this->getActiveStorages()->listAll();
+@@ -221,7 +274,18 @@ public function installOptionalConfig(?StorageInterface $storage = NULL, $depend
+ 
+     $all_config = array_merge($existing_config, $list);
+     $all_config = array_combine($all_config, $all_config);
+-    $config_to_create = $storage->readMultiple($list);
++
++    // Get the config items from the database driver override directory first.
++    // Get the other config items from the normal storage directory.
++    if ($database_driver_override_storage) {
++      $override_list = $database_driver_override_storage->listAll();
++      $config_to_create = $database_driver_override_storage->readMultiple($override_list);
++      $list = array_diff($list, $override_list);
++      $config_to_create += $storage->readMultiple($list);
++    }
++    else {
++      $config_to_create = $storage->readMultiple($list);
++    }
+     // Check to see if the corresponding override storage has any overrides or
+     // new configuration that can be installed.
+     if ($profile_storage) {
+@@ -268,6 +332,9 @@ public function installOptionalConfig(?StorageInterface $storage = NULL, $depend
+    *   The configuration storage to read configuration from.
+    * @param string $collection
+    *   The configuration collection to use.
++   * @param StorageInterface|null $database_driver_override_storage
++   *   (optional) The database driver override configuration storage to read
++   *   configuration from.
+    * @param string $prefix
+    *   (optional) Limit to configuration starting with the provided string.
+    * @param \Drupal\Core\Config\StorageInterface[] $profile_storages
+@@ -278,11 +345,21 @@ public function installOptionalConfig(?StorageInterface $storage = NULL, $depend
+    *   An array of configuration data read from the source storage keyed by the
+    *   configuration object name.
+    */
+-  protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '', array $profile_storages = []) {
++  protected function getConfigToCreate(StorageInterface $storage, $collection, ?StorageInterface $database_driver_override_storage = NULL, $prefix = '', array $profile_storages = []) {
+     if ($storage->getCollectionName() != $collection) {
+       $storage = $storage->createCollection($collection);
+     }
+-    $data = $storage->readMultiple($storage->listAll($prefix));
++
++    // Get the config items from the database driver override directory first.
++    // Get the other config items from the normal storage directory.
++    if ($database_driver_override_storage) {
++      $names = $database_driver_override_storage->listAll($prefix);
++      $data = $database_driver_override_storage->readMultiple($names);
++      $data = array_merge($data, $storage->readMultiple(array_diff($storage->listAll($prefix), $names)));
++    }
++    else {
++      $data = $storage->readMultiple($storage->listAll($prefix));
++    }
+ 
+     // Check to see if configuration provided by the install profile has any
+     // overrides.
+@@ -482,13 +559,13 @@ public function isSyncing() {
+    *   Array of configuration object names that already exist keyed by
+    *   collection.
+    */
+-  protected function findPreExistingConfiguration(StorageInterface $storage) {
++  protected function findPreExistingConfiguration(StorageInterface $storage, ?StorageInterface $database_driver_override_storage = NULL) {
+     $existing_configuration = [];
+     // Gather information about all the supported collections.
+     $collection_info = $this->configManager->getConfigCollectionInfo();
+ 
+     foreach ($collection_info->getCollectionNames() as $collection) {
+-      $config_to_create = array_keys($this->getConfigToCreate($storage, $collection));
++      $config_to_create = array_keys($this->getConfigToCreate($storage, $collection, $database_driver_override_storage));
+       $active_storage = $this->getActiveStorages($collection);
+       foreach ($config_to_create as $config_name) {
+         if ($active_storage->exists($config_name)) {
+@@ -515,6 +592,14 @@ public function checkConfigurationToInstall($type, $name) {
+ 
+     $storage = new FileStorage($config_install_path, StorageInterface::DEFAULT_COLLECTION);
+ 
++    $database_driver_override_storage = NULL;
++    if ($this->hasBaseDatabaseDriverOverrideDirectory()) {
++      $database_driver_override_config_directory = $this->databaseDriverOverrideDirectory . '/' . $name . '/install';
++      if (is_dir($database_driver_override_config_directory)) {
++        $database_driver_override_storage = new FileStorage($database_driver_override_config_directory, StorageInterface::DEFAULT_COLLECTION);
++      }
++    }
++
+     $enabled_extensions = $this->getEnabledExtensions();
+     // Add the extension that will be enabled to the list of enabled extensions.
+     $enabled_extensions[] = $name;
+@@ -522,7 +607,7 @@ public function checkConfigurationToInstall($type, $name) {
+     $profile_storages = $this->getProfileStorages($name);
+ 
+     // Check the dependencies of configuration provided by the module.
+-    [$invalid_default_config, $missing_dependencies] = $this->findDefaultConfigWithUnmetDependencies($storage, $enabled_extensions, $profile_storages);
++    [$invalid_default_config, $missing_dependencies] = $this->findDefaultConfigWithUnmetDependencies($storage, $enabled_extensions, $database_driver_override_storage, $profile_storages);
+     if (!empty($invalid_default_config)) {
+       throw UnmetDependenciesException::create($name, array_unique($missing_dependencies, SORT_REGULAR));
+     }
+@@ -533,7 +618,7 @@ public function checkConfigurationToInstall($type, $name) {
+       // Throw an exception if the module being installed contains configuration
+       // that already exists. Additionally, can not continue installing more
+       // modules because those may depend on the current module being installed.
+-      $existing_configuration = $this->findPreExistingConfiguration($storage);
++      $existing_configuration = $this->findPreExistingConfiguration($storage, $database_driver_override_storage);
+       if (!empty($existing_configuration)) {
+         throw PreExistingConfigException::create($name, $existing_configuration);
+       }
+@@ -547,6 +632,9 @@ public function checkConfigurationToInstall($type, $name) {
+    *   The storage containing the default configuration.
+    * @param array $enabled_extensions
+    *   A list of all the currently enabled modules and themes.
++   * @param \Drupal\Core\Config\StorageInterface|null $database_driver_override_storage
++   *   (optional) The database driver override storage containing the default
++   *   configuration.
+    * @param \Drupal\Core\Config\StorageInterface[] $profile_storages
+    *   An array of storage interfaces containing profile configuration to check
+    *   for overrides.
+@@ -557,9 +645,9 @@ public function checkConfigurationToInstall($type, $name) {
+    *     - An array that will be filled with the missing dependency names, keyed
+    *       by the dependents' names.
+    */
+-  protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, array $profile_storages = []) {
++  protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, ?StorageInterface $database_driver_override_storage = NULL, array $profile_storages = []) {
+     $missing_dependencies = [];
+-    $config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION, '', $profile_storages);
++    $config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION, $database_driver_override_storage, '', $profile_storages);
+     $all_config = array_merge($this->configFactory->listAll(), array_keys($config_to_create));
+     foreach ($config_to_create as $config_name => $config) {
+       if ($missing = $this->getMissingDependencies($config_name, $config, $enabled_extensions, $all_config)) {
+@@ -691,6 +779,13 @@ protected function getProfileStorages($installing_name = '') {
+       $profile_path = $this->extensionPathResolver->getPath('module', $profile);
+       foreach ([InstallStorage::CONFIG_INSTALL_DIRECTORY, InstallStorage::CONFIG_OPTIONAL_DIRECTORY] as $directory) {
+         if (is_dir($profile_path . '/' . $directory)) {
++          if ($this->hasBaseDatabaseDriverOverrideDirectory()) {
++            $database_driver_override_config_directory = $this->databaseDriverOverrideDirectory . $profile . substr($directory, 6);
++            if (is_dir($database_driver_override_config_directory)) {
++              $profile_storages[] = new FileStorage($database_driver_override_config_directory, StorageInterface::DEFAULT_COLLECTION);
++            }
++          }
++
+           $profile_storages[] = new FileStorage($profile_path . '/' . $directory, StorageInterface::DEFAULT_COLLECTION);
+         }
+       }
+diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
+index c9a25bd19a1e7d62b2969825049cdd94abda5303..20c2de8e916582376f35900fa1f591b2a4f15ab6 100644
+--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
++++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
+@@ -5,6 +5,7 @@
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\DatabaseException;
+ use Drupal\Core\DependencyInjection\DependencySerializationTrait;
++use Drupal\mongodb\Driver\Database\mongodb\Statement;
+ 
+ /**
+  * Defines the Database storage.
+@@ -40,6 +41,15 @@ class DatabaseStorage implements StorageInterface {
+    */
+   protected $collection = StorageInterface::DEFAULT_COLLECTION;
+ 
++  /**
++   * Indicator for the existence of the database table.
++   *
++   *  This variable is only used by the database driver for MongoDB.
++   *
++   * @var bool
++   */
++  protected $tableExists = FALSE;
++
+   /**
+    * Constructs a new DatabaseStorage.
+    *
+@@ -64,20 +74,41 @@ public function __construct(Connection $connection, $table, array $options = [],
+    * {@inheritdoc}
+    */
+   public function exists($name) {
+-    try {
+-      return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :name', 0, 1, [
+-        ':collection' => $this->collection,
+-        ':name' => $name,
+-      ], $this->options)->fetchField();
+-    }
+-    catch (\Exception $e) {
+-      if ($this->connection->schema()->tableExists($this->table)) {
+-        throw $e;
++    if ($this->connection->driver() == 'mongodb') {
++      $prefixed_table = $this->connection->getPrefix() . $this->table;
++      $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++        [
++          'collection' => ['$eq' => $this->collection],
++          'name' => ['$eq' => $name],
++        ],
++        [
++          'projection' => ['_id' => 1],
++          'session' => $this->connection->getMongodbSession(),
++        ]
++      );
++
++      if ($cursor && !empty($cursor->toArray())) {
++        return TRUE;
+       }
+-      // If we attempt a read without actually having the table available,
+-      // return false so the caller can handle it.
++
+       return FALSE;
+     }
++    else {
++      try {
++        return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :name', 0, 1, [
++          ':collection' => $this->collection,
++          ':name' => $name,
++        ], $this->options)->fetchField();
++      }
++      catch (\Exception $e) {
++        if ($this->connection->schema()->tableExists($this->table)) {
++          throw $e;
++        }
++        // If we attempt a read without actually having the table available,
++        // return false so the caller can handle it.
++        return FALSE;
++      }
++    }
+   }
+ 
+   /**
+@@ -86,7 +117,22 @@ public function exists($name) {
+   public function read($name) {
+     $data = FALSE;
+     try {
+-      $raw = $this->connection->query('SELECT [data] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :name', [':collection' => $this->collection, ':name' => $name], $this->options)->fetchField();
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . $this->table;
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          ['collection' => ['$eq' => $this->collection], 'name' => ['$eq' => $name]],
++          ['projection' => ['data' => 1, '_id' => 0]]
++        );
++
++        $statement = new Statement($this->connection, $cursor, ['data']);
++        $raw = $statement->execute()->fetchField();
++      }
++      else {
++        $raw = $this->connection->query('SELECT [data] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :name', [
++          ':collection' => $this->collection,
++          ':name' => $name,
++        ], $this->options)->fetchField();
++      }
+       if ($raw !== FALSE) {
+         $data = $this->decode($raw);
+       }
+@@ -111,7 +157,22 @@ public function readMultiple(array $names) {
+ 
+     $list = [];
+     try {
+-      $list = $this->connection->query('SELECT [name], [data] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] IN ( :names[] )', [':collection' => $this->collection, ':names[]' => $names], $this->options)->fetchAllKeyed();
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . $this->table;
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          ['collection' => ['$eq' => $this->collection], 'name' => ['$in' => $names]],
++          [
++            'projection' => ['name' => 1, 'data' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ]
++        );
++
++        $statement = new Statement($this->connection, $cursor, ['name', 'data']);
++        $list = $statement->execute()->fetchAllKeyed();
++      }
++      else {
++        $list = $this->connection->query('SELECT [name], [data] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] IN ( :names[] )', [':collection' => $this->collection, ':names[]' => $names], $this->options)->fetchAllKeyed();
++      }
+       foreach ($list as &$data) {
+         $data = $this->decode($data);
+       }
+@@ -131,16 +192,27 @@ public function readMultiple(array $names) {
+    */
+   public function write($name, array $data) {
+     $data = $this->encode($data);
+-    try {
++    if ($this->connection->driver() == 'mongodb') {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      if (!$this->tableExists) {
++        $this->tableExists = $this->ensureTableExists();
++      }
++
+       return $this->doWrite($name, $data);
+     }
+-    catch (\Exception $e) {
+-      // If there was an exception, try to create the table.
+-      if ($this->ensureTableExists()) {
++    else {
++      try {
+         return $this->doWrite($name, $data);
+       }
+-      // Some other failure that we can not recover from.
+-      throw new StorageException($e->getMessage(), 0, $e);
++      catch (\Exception $e) {
++        // If there was an exception, try to create the table.
++        if ($this->ensureTableExists()) {
++          return $this->doWrite($name, $data);
++        }
++        // Some other failure that we can not recover from.
++        throw new StorageException($e->getMessage(), 0, $e);
++      }
+     }
+   }
+ 
+@@ -277,7 +349,12 @@ public function listAll($prefix = '') {
+       $query->condition('collection', $this->collection, '=');
+       $query->condition('name', $prefix . '%', 'LIKE');
+       $query->orderBy('collection')->orderBy('name');
+-      return $query->execute()->fetchCol();
++      $list = $query->execute()->fetchCol();
++      if ($this->connection->driver() == 'mongodb') {
++        // MongoDB does not remove duplicate values from the list.
++        return array_unique($list);
++      }
++      return $list;
+     }
+     catch (\Exception $e) {
+       if ($this->connection->schema()->tableExists($this->table)) {
+@@ -333,9 +410,24 @@ public function getCollectionName() {
+    */
+   public function getAllCollectionNames() {
+     try {
+-      return $this->connection->query('SELECT DISTINCT [collection] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] <> :collection ORDER by [collection]', [
+-        ':collection' => StorageInterface::DEFAULT_COLLECTION,
+-      ])->fetchCol();
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . $this->table;
++        $collections = $this->connection->getConnection()->selectCollection($prefixed_table)->distinct(
++          'collection',
++          ['collection' => ['$ne' => StorageInterface::DEFAULT_COLLECTION]],
++          ['session' => $this->connection->getMongodbSession()]
++        );
++
++        // The distinct query does not allow sorting.
++        sort($collections);
++
++        return $collections;
++      }
++      else {
++        return $this->connection->query('SELECT DISTINCT [collection] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] <> :collection ORDER by [collection]', [
++          ':collection' => StorageInterface::DEFAULT_COLLECTION,
++        ])->fetchCol();
++      }
+     }
+     catch (\Exception $e) {
+       if ($this->connection->schema()->tableExists($this->table)) {
+diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php
+index 136be5d6bd7c54efd5351386a0683b59caaa8055..fffa07ee3c64dc42b595ab9872df1a13db11ea28 100644
+--- a/core/lib/Drupal/Core/Config/InstallStorage.php
++++ b/core/lib/Drupal/Core/Config/InstallStorage.php
+@@ -2,6 +2,7 @@
+ 
+ namespace Drupal\Core\Config;
+ 
++use Drupal\Core\Database\Database;
+ use Drupal\Core\Extension\ExtensionDiscovery;
+ use Drupal\Core\Extension\Extension;
+ 
+@@ -33,6 +34,11 @@ class InstallStorage extends FileStorage {
+    */
+   const CONFIG_SCHEMA_DIRECTORY = 'config/schema';
+ 
++  /**
++   * Extension sub-directory containing configuration database driver overrides.
++   */
++  const CONFIG_OVERRIDES_DIRECTORY = 'config/overrides';
++
+   /**
+    * Folder map indexed by configuration name.
+    *
+@@ -47,6 +53,13 @@ class InstallStorage extends FileStorage {
+    */
+   protected $directory;
+ 
++  /**
++   * The database override directory.
++   *
++   * @var string
++   */
++  protected $databaseDriverOverrideDirectory;
++
+   /**
+    * Constructs an InstallStorage object.
+    *
+@@ -59,6 +72,10 @@ class InstallStorage extends FileStorage {
+    */
+   public function __construct($directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION) {
+     parent::__construct($directory, $collection);
++
++    // Init the base database driver override directory. We do this here to do
++    // it only once.
++    $this->initBaseDatabaseDriverOverrideDirectory();
+   }
+ 
+   /**
+@@ -206,11 +223,84 @@ public function getComponentNames(array $list) {
+             $folders[basename($file, $extension)] = $directory;
+           }
+         }
++
++        // Let a config item be overridden by a database driver one.
++        if ($this->hasBaseDatabaseDriverOverrideDirectory()) {
++          $database_driver_override_directory = $this->getDatabaseDriverOverrideDirectory($directory, $extension_object);
++          if (is_dir($database_driver_override_directory)) {
++            $database_driver_override_files = scandir($database_driver_override_directory);
++            foreach ($database_driver_override_files as $database_driver_override_file) {
++              if ($database_driver_override_file[0] !== '.' && preg_match($pattern, $database_driver_override_file)) {
++                $folders[basename($database_driver_override_file, $extension)] = $database_driver_override_directory;
++              }
++            }
++          }
++        }
+       }
+     }
+     return $folders;
+   }
+ 
++  /**
++   * Initiate the base database driver override directory.
++   */
++  protected function initBaseDatabaseDriverOverrideDirectory(): void {
++    if (Database::isActiveConnection()) {
++      $connection = Database::getConnection();
++      // Get the module root directory from the autoload directory setting from
++      // the database connection.
++      $database_driver_autoload_directory = $connection->getConnectionOptions()['autoload'] ?? '';
++      $pos = strpos($database_driver_autoload_directory, 'src/Driver/Database/');
++      if ($pos !== FALSE) {
++        $database_driver_override_directory = substr($database_driver_autoload_directory, 0, $pos) . self::CONFIG_OVERRIDES_DIRECTORY;
++        if (is_dir($database_driver_override_directory)) {
++          // Only set the base database driver when the module providing the
++          // database driver has one.
++          $this->databaseDriverOverrideDirectory = $database_driver_override_directory;
++        }
++      }
++    }
++  }
++
++  /**
++   * Check whether the database driver has a config override directory.
++   *
++   * @return bool
++   *   Return TRUE when the database driver has the config override directory.
++   */
++  protected function hasBaseDatabaseDriverOverrideDirectory(): bool {
++    return (bool) $this->databaseDriverOverrideDirectory;
++  }
++
++  /**
++   * Get the database driver directory for overridden config items.
++   *
++   * @param string $directory
++   *   The directory in which to search for config items.
++   * @param \Drupal\Core\Extension\Extension $extension
++   *   The extension item from which the config belongs to.
++   *
++   * @return string
++   *   The directory to search for by the database driver overridden config
++   *   items.
++   */
++  protected function getDatabaseDriverOverrideDirectory(string $directory, Extension $extension): string {
++    // The overridden config items are in  the database drivers override directory
++    $dir = $this->databaseDriverOverrideDirectory . '/' . $extension->getName();
++
++    if (str_ends_with($directory, self::CONFIG_INSTALL_DIRECTORY)) {
++      $dir .= '/install';
++    }
++    elseif (str_ends_with($directory, self::CONFIG_OPTIONAL_DIRECTORY)) {
++      $dir .= '/optional';
++    }
++    elseif (str_ends_with($directory, self::CONFIG_SCHEMA_DIRECTORY)) {
++      $dir .= '/schema';
++    }
++
++    return $dir;
++  }
++
+   /**
+    * Get all configuration names and folders for Drupal core.
+    *
+diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
+index 7a9de46a7d410ebc792743bf136adedbfd5f0b7c..332c9a88125cac8e890a503be18d38802ad35cdc 100644
+--- a/core/lib/Drupal/Core/Database/Connection.php
++++ b/core/lib/Drupal/Core/Database/Connection.php
+@@ -1305,6 +1305,9 @@ public function __sleep(): array {
+    * @param string $root
+    *   The root directory of the Drupal installation. Some database drivers,
+    *   like for example SQLite, need this information.
++   * @param string $hosts
++   *   (optional) The host names when there are multiple host names. The host
++   *   name in the $url has been replaced with a placeholder.
+    *
+    * @return array
+    *   The connection options.
+@@ -1319,7 +1322,7 @@ public function __sleep(): array {
+    *
+    * @see \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo()
+    */
+-  public static function createConnectionOptionsFromUrl($url, $root) {
++  public static function createConnectionOptionsFromUrl($url, $root, $hosts = '') {
+     $url_components = parse_url($url);
+     if (!isset($url_components['scheme'], $url_components['host'], $url_components['path'])) {
+       throw new \InvalidArgumentException("The database connection URL '$url' is invalid. The minimum requirement is: 'driver://host/database'");
+diff --git a/core/lib/Drupal/Core/Database/Database.php b/core/lib/Drupal/Core/Database/Database.php
+index aceb5fd128273f0b95e028cabbb11499f4084bfd..63755c83f4563a7e944242b5d6e353bf73981b04 100644
+--- a/core/lib/Drupal/Core/Database/Database.php
++++ b/core/lib/Drupal/Core/Database/Database.php
+@@ -514,23 +514,44 @@ public static function convertDbUrlToConnectionInfo($url, $root, ?bool $include_
+     }
+     $driverName = $matches[1];
+ 
++    // As MongoDB is a NoSQL database and therefore it works with multiple
++    // servers to create a single logical database. To make maintenance less
++    // complicated MongoDB supports a DNS-constructed seed list. Using DNS to
++    // construct the available servers list allows more flexibility of
++    // deployment and the ability to change the servers in rotation without
++    // reconfiguring clients.
++    if (strpos($driverName, '+') !== FALSE) {
++      $driverNameParts = explode('+', $driverName);
++      $driverName = $driverNameParts[0];
++    }
++
+     // Determine if the database driver is provided by a module.
+     // @todo https://www.drupal.org/project/drupal/issues/3250999. Refactor when
+     // all database drivers are provided by modules.
+     $url_components = parse_url($url);
++
++    // The function parse_url() can fail for MongoDB. With MongoDB there are
++    // multiple hosts. URLs with multiple hosts are not supported by
++    // parse_url(). Get the host names and replace them with a placeholder
++    // hostname and run parse_url() again.
++    if ($url_components === FALSE) {
++      // The host names are the ones between the character "@" and the character "/".
++      preg_match('/\@(.*)\//', $url, $matches);
++      if (isset($matches[1])) {
++        $hosts = $matches[1];
++        $url = str_replace($hosts, 'placeholder_host', $url);
++        $url_components = parse_url($url);
++      }
++    }
++
+     $url_component_query = $url_components['query'] ?? '';
+     parse_str($url_component_query, $query);
+ 
+-    // Add the module key for core database drivers when the module key is not
+-    // set.
+-    if (!isset($query['module']) && in_array($driverName, ['mysql', 'pgsql', 'sqlite'], TRUE)) {
+-      $query['module'] = $driverName;
+-    }
+-    if (!isset($query['module'])) {
+-      throw new \InvalidArgumentException("Can not convert '$url' to a database connection, the module providing the driver '{$driverName}' is not specified");
+-    }
++    // Use the driver name as the module name when the module name is not
++    // provided.
++    $module = $query['module'] ?? $driverName;
+ 
+-    $driverNamespace = "Drupal\\{$query['module']}\\Driver\\Database\\{$driverName}";
++    $driverNamespace = "Drupal\\{$module}\\Driver\\Database\\{$driverName}";
+ 
+     /** @var \Drupal\Core\Extension\DatabaseDriver $driver */
+     $driver = self::getDriverList()
+@@ -559,7 +580,7 @@ public static function convertDbUrlToConnectionInfo($url, $root, ?bool $include_
+ 
+     $additional_class_loader->register(TRUE);
+ 
+-    $options = $connection_class::createConnectionOptionsFromUrl($url, $root);
++    $options = $connection_class::createConnectionOptionsFromUrl($url, $root, $hosts ?? '');
+ 
+     // Add the necessary information to autoload code.
+     // @see \Drupal\Core\Site\Settings::initialize()
+diff --git a/core/lib/Drupal/Core/Database/Query/ConditionInterface.php b/core/lib/Drupal/Core/Database/Query/ConditionInterface.php
+index 01815f2c45a830658c1f74926f1229c53a3f1e66..23a8851ead3423784f1d5542025b8c6932969079 100644
+--- a/core/lib/Drupal/Core/Database/Query/ConditionInterface.php
++++ b/core/lib/Drupal/Core/Database/Query/ConditionInterface.php
+@@ -72,6 +72,28 @@ interface ConditionInterface {
+    */
+   public function condition($field, $value = NULL, $operator = '=');
+ 
++  /**
++   * Compare two database fields with each other.
++   *
++   * This method is used in joins to compare 2 fields from different tables to
++   * each other.
++   *
++   * @param string $field
++   *   The name of the field to compare.
++   * @param string $field2
++   *   The name of the other field to compare.
++   * @param string|null $operator
++   *   (optional) The operator to use. The supported operators are: =, <>, <,
++   *   <=, >, >=, <>.
++   *
++   * @return $this
++   *   The called object.
++   *
++   * @throws \Drupal\Core\Database\InvalidQueryException
++   *   If passed invalid arguments, such as an empty array as $value.
++   */
++  public function compare(string $field, string $field2, ?string $operator = '=');
++
+   /**
+    * Adds an arbitrary WHERE clause to the query.
+    *
+diff --git a/core/lib/Drupal/Core/Database/Query/Condition.php b/core/lib/Drupal/Core/Database/Query/Condition.php
+index 06cb31568c2c087555e651ed580c03e05fd3571c..b3d919fde593336c9deaea0c67d45cf5cc04753e 100644
+--- a/core/lib/Drupal/Core/Database/Query/Condition.php
++++ b/core/lib/Drupal/Core/Database/Query/Condition.php
+@@ -127,6 +127,51 @@ public function condition($field, $value = NULL, $operator = '=') {
+     return $this;
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function compare(string $field, string $field2, ?string $operator = '=') {
++    if (!in_array($operator, ['=', '<', '>', '>=', '<=', '<>'], TRUE)) {
++      throw new InvalidQueryException(sprintf("In a query compare '%s %s %s' the operator must be one of the following: '=', '<', '>', '>=', '<=', '<>'.", $field, $operator, $field2));
++    }
++
++    $this->conditions[] = [
++      'field' => $field,
++      'field2' => $field2,
++      'operator' => $operator,
++    ];
++
++    $this->changed = TRUE;
++
++    return $this;
++  }
++
++  /**
++   * Update the alias placeholder in the condition and its children.
++   *
++   * @param string $placeholder
++   *   The value of the placeholder.
++   * @param string $alias
++   *   The value to replace the placeholder.
++   *
++   * @internal
++   */
++  public function updateAliasPlaceholder(string $placeholder, string $alias): void {
++    foreach ($this->conditions as &$condition) {
++      if (isset($condition['field']) && $condition['field'] instanceof ConditionInterface) {
++        $condition['field']->updateAliasPlaceholder($placeholder, $alias);
++      }
++      else {
++        if (isset($condition['field'])) {
++          $condition['field'] = str_replace($placeholder, $alias, $condition['field']);
++        }
++        if (isset($condition['field2'])) {
++          $condition['field2'] = str_replace($placeholder, $alias, $condition['field2']);
++        }
++      }
++    }
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -227,6 +272,12 @@ public function compile(Connection $connection, PlaceholderInterface $queryPlace
+           // condition on its own: ignore the operator and value parts.
+           $ignore_operator = $condition['operator'] === '=' && $condition['value'] === NULL;
+         }
++        elseif (isset($condition['field2'])) {
++          // The key field2 is only set when we are comparing 2 fields with each
++          // other.
++          $condition_fragments[] = trim(implode(' ', [$connection->escapeField($condition['field']), $condition['operator'], $connection->escapeField($condition['field2'])]));
++          continue;
++        }
+         elseif (!isset($condition['operator'])) {
+           // Left hand part is a literal string added with the
+           // @see ConditionInterface::where() method. Put brackets around
+@@ -361,7 +412,7 @@ public function __clone() {
+         if ($condition['field'] instanceof ConditionInterface) {
+           $this->conditions[$key]['field'] = clone($condition['field']);
+         }
+-        if ($condition['value'] instanceof SelectInterface) {
++        if (isset($condition['value']) && ($condition['value'] instanceof SelectInterface)) {
+           $this->conditions[$key]['value'] = clone($condition['value']);
+         }
+       }
+diff --git a/core/lib/Drupal/Core/Database/Query/Merge.php b/core/lib/Drupal/Core/Database/Query/Merge.php
+index 6f040bd0181433979f190f7c981cdc90e9a500cd..b62e0d423b0196c635ef06bfbebc33848a687b18 100644
+--- a/core/lib/Drupal/Core/Database/Query/Merge.php
++++ b/core/lib/Drupal/Core/Database/Query/Merge.php
+@@ -361,7 +361,7 @@ public function execute() {
+ 
+     $select = $this->connection->select($this->conditionTable)
+       ->condition($this->condition);
+-    $select->addExpression('1');
++    $select->addExpressionConstant('1');
+ 
+     if (!$select->execute()->fetchField()) {
+       try {
+diff --git a/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php
+index 3620d9b5d17c9d42b1e368db07be7fc52f9ac1fb..9fbe7098804b3081e8e7cb7a1f95b7fe823820a4 100644
+--- a/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php
++++ b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php
+@@ -73,7 +73,7 @@ public function execute() {
+     }
+     $this->ensureElement();
+ 
+-    $total_items = $this->getCountQuery()->execute()->fetchField();
++    $total_items = (int) $this->getCountQuery()->execute()->fetchField();
+     $pager = $this->connection->getPagerManager()->createPager($total_items, $this->limit, $this->element);
+     $this->range($pager->getCurrentPage() * $this->limit, $this->limit);
+ 
+diff --git a/core/lib/Drupal/Core/Database/Query/QueryConditionTrait.php b/core/lib/Drupal/Core/Database/Query/QueryConditionTrait.php
+index 83be429fa581cfa7e4e8360385877c4a72ba6289..8d012c3ab3e5df0fe954e8e1ce205ee704ccd93a 100644
+--- a/core/lib/Drupal/Core/Database/Query/QueryConditionTrait.php
++++ b/core/lib/Drupal/Core/Database/Query/QueryConditionTrait.php
+@@ -28,6 +28,14 @@ public function condition($field, $value = NULL, $operator = '=') {
+     return $this;
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function compare($field, $field2, $operator = '=') {
++    $this->condition->compare($field, $field2, $operator);
++    return $this;
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+diff --git a/core/lib/Drupal/Core/Database/Query/SelectExtender.php b/core/lib/Drupal/Core/Database/Query/SelectExtender.php
+index 61d91867a8928081e0e4a4ab612dec50c9cf9dff..d88855918e1457d1940cf504356f95a68044d26c 100644
+--- a/core/lib/Drupal/Core/Database/Query/SelectExtender.php
++++ b/core/lib/Drupal/Core/Database/Query/SelectExtender.php
+@@ -123,6 +123,14 @@ public function arguments() {
+     return $this->query->arguments();
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function compare(string $field, string $field2, ?string $operator = '=') {
++    $this->query->compare($field, $field2, $operator);
++    return $this;
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -359,6 +367,69 @@ public function addExpression($expression, $alias = NULL, $arguments = []) {
+     return $this->query->addExpression($expression, $alias, $arguments);
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionConstant($constant, $alias = NULL) {
++    return $this->query->addExpressionConstant($constant, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionField($field, $alias = NULL) {
++    return $this->query->addExpressionField($field, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionMax($field, $alias = NULL) {
++    return $this->query->addExpressionMax($field, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionMin($field, $alias = NULL) {
++    return $this->query->addExpressionMin($field, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionSum($field, $alias = NULL) {
++    return $this->query->addExpressionSum($field, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionCount($field, $alias = NULL) {
++    return $this->query->addExpressionCount($field, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionCountAll($alias = NULL) {
++    return $this->query->addExpressionCountAll($alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionCountDistinct($field, $alias = NULL) {
++    return $this->query->addExpressionCountDistinct($field, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionCoalesce($fields, $alias = NULL) {
++    return $this->query->addExpressionCoalesce($fields, $alias);
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -387,6 +458,13 @@ public function addJoin($type, $table, $alias = NULL, $condition = NULL, $argume
+     return $this->query->addJoin($type, $table, $alias, $condition, $arguments);
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function joinCondition(string $conjunction = 'AND') {
++    return $this->query->joinCondition($conjunction);
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+diff --git a/core/lib/Drupal/Core/Database/Query/SelectInterface.php b/core/lib/Drupal/Core/Database/Query/SelectInterface.php
+index f3a161af2da6261c8c5a090376495d5ed6746bd6..1c9bd8283f0be78e7e0543687ef12a9047a26f66 100644
+--- a/core/lib/Drupal/Core/Database/Query/SelectInterface.php
++++ b/core/lib/Drupal/Core/Database/Query/SelectInterface.php
+@@ -242,6 +242,148 @@ public function fields($table_alias, array $fields = []);
+    */
+   public function addExpression($expression, $alias = NULL, $arguments = []);
+ 
++  /**
++   * Adds a constant as an expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $constant
++   *   The field for which to create an expression.
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionConstant(string $constant, ?string $alias = NULL);
++
++  /**
++   * Adds a field expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $field
++   *   The field for which to create a value.
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionField(string $field, ?string $alias = NULL);
++
++  /**
++   * Adds a maximum field expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $field
++   *   The field for which to get the maximum value.
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionMax(string $field, ?string $alias = NULL);
++
++  /**
++   * Adds a minimum field expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $field
++   *   The field for which to get the minimum value.
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionMin(string $field, ?string $alias = NULL);
++
++  /**
++   * Adds a sum field expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $field
++   *   The field for which to get the sum value.
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionSum(string $field, ?string $alias = NULL);
++
++  /**
++   * Adds a count field expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $field
++   *   The field for which to get the count value.
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionCount(string $field, ?string $alias = NULL);
++
++  /**
++   * Adds a count all expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionCountAll(?string $alias = NULL);
++
++  /**
++   * Adds a count distinct expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $field
++   *   The field for which to get the count distinct value.
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionCountDistinct(string $field, ?string $alias = NULL);
++
++  /**
++   * Adds a coalesce expression to the list of "fields" to be SELECTed.
++   *
++   * @param string $fields
++   *   The fields for which to get the coalesce value.
++   * @param string $alias
++   *   The alias for this expression. If not specified, one will be generated
++   *   automatically in the form "expression_#". The alias will be checked for
++   *   uniqueness, so the requested alias may not be the alias that is assigned
++   *   in all cases.
++   *
++   * @return string
++   *   The unique alias that was assigned for this expression.
++   */
++  public function addExpressionCoalesce(array $fields, ?string $alias = NULL);
++
+   /**
+    * Default Join against another table in the database.
+    *
+@@ -359,6 +501,17 @@ public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments =
+    */
+   public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = []);
+ 
++  /**
++   * Helper method for generation join conditions.
++   *
++   * @param string $conjunction
++   *   The operator to use to combine conditions: 'AND' or 'OR'.
++   *
++   * @return \Drupal\Core\Database\Query\ConditionInterface
++   *   An object holding a group of conditions.
++   */
++  public function joinCondition(string $conjunction = 'AND');
++
+   /**
+    * Orders the result set by a given field.
+    *
+diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php
+index 290cbbf487c960815b094874e433043e58430085..6ff10bd3640935691f2868992a6f01c6fad11865 100644
+--- a/core/lib/Drupal/Core/Database/Query/Select.php
++++ b/core/lib/Drupal/Core/Database/Query/Select.php
+@@ -601,6 +601,79 @@ public function addExpression($expression, $alias = NULL, $arguments = []) {
+     return $alias;
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionConstant(string $constant, ?string $alias = NULL) {
++    return $this->addExpression($constant, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionField(string $field, ?string $alias = NULL) {
++    $field = '[' . str_replace('.', '].[', $field) . ']';
++    return $this->addExpression($field, $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionMax(string $field, ?string $alias = NULL) {
++    $field = '[' . str_replace('.', '].[', $field) . ']';
++    return $this->addExpression('MAX(' . $field . ')', $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionMin(string $field, ?string $alias = NULL) {
++    $field = '[' . str_replace('.', '].[', $field) . ']';
++    return $this->addExpression('MIN(' . $field . ')', $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionSum(string $field, ?string $alias = NULL) {
++    $field = '[' . str_replace('.', '].[', $field) . ']';
++    return $this->addExpression('SUM(' . $field . ')', $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionCount(string $field, ?string $alias = NULL) {
++    $field = '[' . str_replace('.', '].[', $field) . ']';
++    return $this->addExpression('COUNT(' . $field . ')', $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionCountAll(?string $alias = NULL) {
++    return $this->addExpression('COUNT(*)', $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionCountDistinct(string $field, ?string $alias = NULL) {
++    $field = '[' . str_replace('.', '].[', $field) . ']';
++    return $this->addExpression('COUNT(DISTINCT(' . $field . '))', $alias);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function addExpressionCoalesce(array $fields, ?string $alias = NULL) {
++    foreach ($fields as &$field) {
++      $field = '[' . str_replace('.', '].[', $field) . ']';
++    }
++    $expression = 'COALESCE(' . implode(', ', $fields) . ')';
++    return $this->addExpression($expression, $alias);
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -645,6 +718,9 @@ public function addJoin($type, $table, $alias = NULL, $condition = NULL, $argume
+     if (is_string($condition)) {
+       $condition = str_replace('%alias', $alias, $condition);
+     }
++    if ($condition instanceof ConditionInterface) {
++      $condition->updateAliasPlaceholder('%alias', $alias);
++    }
+ 
+     $this->tables[$alias] = [
+       'join type' => $type,
+@@ -657,6 +733,13 @@ public function addJoin($type, $table, $alias = NULL, $condition = NULL, $argume
+     return $alias;
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function joinCondition(string $conjunction = 'AND') {
++    return $this->connection->condition($conjunction);
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -724,7 +807,7 @@ public function countQuery() {
+     $count = $this->prepareCountQuery();
+ 
+     $query = $this->connection->select($count, NULL, $this->queryOptions);
+-    $query->addExpression('COUNT(*)');
++    $query->addExpressionCountAll();
+ 
+     return $query;
+   }
+@@ -770,7 +853,7 @@ protected function prepareCountQuery() {
+ 
+     // If we've just removed all fields from the query, make sure there is at
+     // least one so that the query still runs.
+-    $count->addExpression('1');
++    $count->addExpressionConstant('1');
+ 
+     // Ordering a count query is a waste of cycles, and breaks on some
+     // databases anyway.
+diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+index 3724bf94cee3ffbd83c086e062acd70888ef39d0..fd7b8b6a00b5c6e19d6c9340990a627404313fed 100644
+--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
++++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+@@ -323,7 +323,7 @@ public function setNewRevision($value = TRUE) {
+    * {@inheritdoc}
+    */
+   public function getLoadedRevisionId() {
+-    return $this->loadedRevisionId;
++    return !is_null($this->loadedRevisionId) ? (int) $this->loadedRevisionId : NULL;
+   }
+ 
+   /**
+diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+index 574d3871db0749a3cd9d2f28bf4f63d3dbbd68aa..f57ae79276e82fd91ff1157bf8681e32c537cbb6 100644
+--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
++++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+@@ -333,8 +333,8 @@ protected function isAnyStoredRevisionTranslated(TranslatableInterface $entity)
+     }
+ 
+     $query = $this->getQuery()
+-      ->condition($this->entityType->getKey('id'), $entity->id())
+-      ->condition($this->entityType->getKey('default_langcode'), 0)
++      ->condition($this->entityType->getKey('id'), (int) $entity->id())
++      ->condition($this->entityType->getKey('default_langcode'), FALSE)
+       ->accessCheck(FALSE)
+       ->range(0, 1);
+ 
+@@ -483,7 +483,7 @@ public function getLatestRevisionId($entity_id) {
+     if (!isset($this->latestRevisionIds[$entity_id][LanguageInterface::LANGCODE_DEFAULT])) {
+       $result = $this->getQuery()
+         ->latestRevision()
+-        ->condition($this->entityType->getKey('id'), $entity_id)
++        ->condition($this->entityType->getKey('id'), (int) $entity_id)
+         ->accessCheck(FALSE)
+         ->execute();
+ 
+@@ -508,8 +508,8 @@ public function getLatestTranslationAffectedRevisionId($entity_id, $langcode) {
+     if (!isset($this->latestRevisionIds[$entity_id][$langcode])) {
+       $result = $this->getQuery()
+         ->allRevisions()
+-        ->condition($this->entityType->getKey('id'), $entity_id)
+-        ->condition($this->entityType->getKey('revision_translation_affected'), 1, '=', $langcode)
++        ->condition($this->entityType->getKey('id'), (int) $entity_id)
++        ->condition($this->entityType->getKey('revision_translation_affected'), TRUE, '=', $langcode)
+         ->range(0, 1)
+         ->sort($this->entityType->getKey('revision'), 'DESC')
+         ->accessCheck(FALSE)
+@@ -616,9 +616,14 @@ protected function preLoad(?array &$ids = NULL) {
+       // If we had to load all the entities ($ids was set to NULL), get an array
+       // of IDs that still need to be loaded.
+       else {
++        // For MongoDB all integer values need to be real integer values.
++        $entity_ids = [];
++        foreach (array_keys($entities) as $entity_id) {
++          $entity_ids[] = (int) $entity_id;
++        }
+         $result = $this->getQuery()
+           ->accessCheck(FALSE)
+-          ->condition($this->entityType->getKey('id'), array_keys($entities), 'NOT IN')
++          ->condition($this->entityType->getKey('id'), $entity_ids, 'NOT IN')
+           ->execute();
+         $ids = array_values($result);
+       }
+diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
+index 65de824756c93ff5371d06e9376b758727104ee1..853a448335e836ca861c5765894a6a638c91034a 100644
+--- a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
++++ b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
+@@ -134,7 +134,11 @@ protected function prepare() {
+     // Add a self-join to the base revision table if we're querying only the
+     // latest revisions.
+     if ($this->latestRevision && $revision_field) {
+-      $this->sqlQuery->leftJoin($base_table, 'base_table_2', "[base_table].[$id_field] = [base_table_2].[$id_field] AND [base_table].[$revision_field] < [base_table_2].[$revision_field]");
++      $this->sqlQuery->leftJoin($base_table, 'base_table_2',
++        $this->sqlQuery->joinCondition()
++          ->compare("base_table.$id_field", "base_table_2.$id_field")
++          ->compare("base_table.$revision_field", "base_table_2.$revision_field", '<')
++      );
+       $this->sqlQuery->isNull("base_table_2.$id_field");
+     }
+ 
+@@ -227,9 +231,12 @@ protected function addSort() {
+         // Order based on the smallest element of each group if the
+         // direction is ascending, or on the largest element of each group
+         // if the direction is descending.
+-        $function = $direction == 'ASC' ? 'min' : 'max';
+-        $expression = "$function($sql_alias)";
+-        $expression_alias = $this->sqlQuery->addExpression($expression);
++        if ($direction == 'ASC') {
++          $expression_alias = $this->sqlQuery->addExpressionMin($sql_alias);
++        }
++        else {
++          $expression_alias = $this->sqlQuery->addExpressionMax($sql_alias);
++        }
+         $this->sqlQuery->orderBy($expression_alias, $direction);
+       }
+     }
+diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
+index a873de2b081b6439af759e82544cee217272d23a..401fe5b3783fc40805af89ea9c06f5688a530e5a 100644
+--- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
++++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
+@@ -2,6 +2,7 @@
+ 
+ namespace Drupal\Core\Entity\Query\Sql;
+ 
++use Drupal\Core\Database\Query\ConditionInterface;
+ use Drupal\Core\Database\Query\SelectInterface;
+ use Drupal\Core\Entity\EntityType;
+ use Drupal\Core\Entity\Query\QueryException;
+@@ -365,7 +366,7 @@ protected function ensureEntityTable($index_prefix, $property, $type, $langcode,
+         // gets a unique alias.
+         $key = $index_prefix . ($base_table === 'base_table' ? $table : $base_table);
+         if (!isset($this->entityTables[$key])) {
+-          $this->entityTables[$key] = $this->addJoin($type, $table, "[%alias].[$id_field] = [$base_table].[$id_field]", $langcode);
++          $this->entityTables[$key] = $this->addJoin($type, $table, $this->sqlQuery->joinCondition()->compare("%alias.$id_field", "$base_table.$id_field"), $langcode);
+         }
+         return $this->entityTables[$key];
+       }
+@@ -411,7 +412,7 @@ protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $b
+       if ($field->getCardinality() != 1) {
+         $this->sqlQuery->addMetaData('simple_query', FALSE);
+       }
+-      $this->fieldTables[$index_prefix . $field_name] = $this->addJoin($type, $table, "[%alias].[$field_id_field] = [$base_table].[$entity_id_field]", $langcode, $delta);
++      $this->fieldTables[$index_prefix . $field_name] = $this->addJoin($type, $table, $this->sqlQuery->joinCondition()->compare("%alias.$field_id_field", "$base_table.$entity_id_field"), $langcode, $delta);
+     }
+     return $this->fieldTables[$index_prefix . $field_name];
+   }
+@@ -423,7 +424,7 @@ protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $b
+    *   The join type.
+    * @param string $table
+    *   The table to join to.
+-   * @param string $join_condition
++   * @param \Drupal\Core\Database\Query\ConditionInterface|string $join_condition
+    *   The condition on which to join to.
+    * @param string $langcode
+    *   The langcode used on the join.
+@@ -441,14 +442,24 @@ protected function addJoin($type, $table, $join_condition, $langcode, $delta = N
+       // For a data table, get the entity language key from the entity type.
+       // A dedicated field table has a hard-coded 'langcode' column.
+       $langcode_key = $entity_type->getDataTable() == $table ? $entity_type->getKey('langcode') : 'langcode';
+-      $placeholder = ':langcode' . $this->sqlQuery->nextPlaceholder();
+-      $join_condition .= ' AND [%alias].[' . $langcode_key . '] = ' . $placeholder;
+-      $arguments[$placeholder] = $langcode;
++      if ($join_condition instanceof ConditionInterface) {
++        $join_condition->condition('%alias.' . $langcode_key, $langcode);
++      }
++      else {
++        $placeholder = ':langcode' . $this->sqlQuery->nextPlaceholder();
++        $join_condition .= ' AND [%alias].[' . $langcode_key . '] = ' . $placeholder;
++        $arguments[$placeholder] = $langcode;
++      }
+     }
+     if (isset($delta)) {
+-      $placeholder = ':delta' . $this->sqlQuery->nextPlaceholder();
+-      $join_condition .= ' AND [%alias].[delta] = ' . $placeholder;
+-      $arguments[$placeholder] = $delta;
++      if ($join_condition instanceof ConditionInterface) {
++        $join_condition->condition('%alias.delta', $delta);
++      }
++      else {
++        $placeholder = ':delta' . $this->sqlQuery->nextPlaceholder();
++        $join_condition .= ' AND [%alias].[delta] = ' . $placeholder;
++        $arguments[$placeholder] = $delta;
++      }
+     }
+     return $this->sqlQuery->addJoin($type, $table, NULL, $join_condition, $arguments);
+   }
+@@ -499,7 +510,7 @@ protected function getTableMapping($table, $entity_type_id) {
+    *   The alias of the next entity table joined in.
+    */
+   protected function addNextBaseTable(EntityType $entity_type, $table, $sql_column, FieldStorageDefinitionInterface $field_storage) {
+-    $join_condition = '[%alias].[' . $entity_type->getKey('id') . "] = [$table].[$sql_column]";
++    $join_condition = $this->sqlQuery->joinCondition()->compare('%alias.' . $entity_type->getKey('id'), "$table.$sql_column");
+     return $this->sqlQuery->leftJoin($entity_type->getBaseTable(), NULL, $join_condition);
+   }
+ 
+diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
+index 93398a56fc3bd6d4fba836bfa5805216d890e80e..82658064c1a3b87ab77d07d23e491f9112048108 100644
+--- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
++++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
+@@ -5,6 +5,9 @@
+ use Drupal\Core\Entity\ContentEntityTypeInterface;
+ use Drupal\Core\Entity\EntityTypeInterface;
+ use Drupal\Core\Field\FieldStorageDefinitionInterface;
++use Drupal\views\ViewsConfigUpdater;
++
++// cspell:ignore sharded unsharded
+ 
+ /**
+  * Defines a default table mapping class.
+@@ -62,6 +65,45 @@ class DefaultTableMapping implements TableMappingInterface {
+    */
+   protected $revisionDataTable;
+ 
++  /**
++   * Flag to indicate that we are storing entity data in JSON documents.
++   *
++   * All relational databases (MySQL, MariaDB, PostgreSQL, SQLite, SQL Server
++   * and OracleDB) do not store entity data in JSON documents. Only MongoDB
++   * stores entity data in JSON documents.
++   *
++   * @var bool
++   */
++  protected bool $jsonStorage;
++
++  /**
++   * The JSON storage table that stores the all revisions data for the entity.
++   *
++   * @var string
++   */
++  protected $jsonStorageAllRevisionsTable;
++
++  /**
++   * The JSON storage table that stores the current revision data.
++   *
++   * @var string
++   */
++  protected $jsonStorageCurrentRevisionTable;
++
++  /**
++   * The JSON storage table that stores the latest revision data.
++   *
++   * @var string
++   */
++  protected $jsonStorageLatestRevisionTable;
++
++  /**
++   * The JSON storage table that stores the translations data.
++   *
++   * @var string
++   */
++  protected $jsonStorageTranslationsTable;
++
+   /**
+    * A list of field names per table.
+    *
+@@ -124,23 +166,39 @@ class DefaultTableMapping implements TableMappingInterface {
+    * @param string $prefix
+    *   (optional) A prefix to be used by all the tables of this mapping.
+    *   Defaults to an empty string.
++   * @param bool $json_storage
++   *   (optional) Flag to indicate that we are storing entity data in JSON
++   *   documents. Defaults to FALSE.
+    */
+-  public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '') {
++  public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '', bool $json_storage = FALSE) {
+     $this->entityType = $entity_type;
+     $this->fieldStorageDefinitions = $storage_definitions;
+     $this->prefix = $prefix;
++    $this->jsonStorage = $json_storage;
+ 
+     // @todo Remove table names from the entity type definition in
+     //   https://www.drupal.org/node/2232465.
+     $this->baseTable = $this->prefix . $entity_type->getBaseTable() ?: $entity_type->id();
+-    if ($entity_type->isRevisionable()) {
+-      $this->revisionTable = $this->prefix . $entity_type->getRevisionTable() ?: $entity_type->id() . '_revision';
+-    }
+-    if ($entity_type->isTranslatable()) {
+-      $this->dataTable = $this->prefix . $entity_type->getDataTable() ?: $entity_type->id() . '_field_data';
++    if ($this->jsonStorage) {
++      if ($entity_type->isRevisionable()) {
++        $this->jsonStorageAllRevisionsTable = $this->prefix . $entity_type->id() . '_all_revisions';
++        $this->jsonStorageCurrentRevisionTable = $this->prefix . $entity_type->id() . '_current_revision';
++        $this->jsonStorageLatestRevisionTable = $this->prefix . $entity_type->id() . '_latest_revision';
++      }
++      elseif ($entity_type->isTranslatable()) {
++        $this->jsonStorageTranslationsTable = $this->prefix . $entity_type->id() . '_translations';
++      }
+     }
+-    if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
+-      $this->revisionDataTable = $this->prefix . $entity_type->getRevisionDataTable() ?: $entity_type->id() . '_field_revision';
++    else {
++      if ($entity_type->isRevisionable()) {
++        $this->revisionTable = $this->prefix . $entity_type->getRevisionTable() ?: $entity_type->id() . '_revision';
++      }
++      if ($entity_type->isTranslatable()) {
++        $this->dataTable = $this->prefix . $entity_type->getDataTable() ?: $entity_type->id() . '_field_data';
++      }
++      if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
++        $this->revisionDataTable = $this->prefix . $entity_type->getRevisionDataTable() ?: $entity_type->id() . '_field_revision';
++      }
+     }
+   }
+ 
+@@ -155,13 +213,16 @@ public function __construct(ContentEntityTypeInterface $entity_type, array $stor
+    * @param string $prefix
+    *   (optional) A prefix to be used by all the tables of this mapping.
+    *   Defaults to an empty string.
++   * @param bool $json_storage
++   *   (optional) Flag to indicate that we are storing entity data in JSON
++   *   documents. Defaults to FALSE.
+    *
+    * @return static
+    *
+    * @internal
+    */
+-  public static function create(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '') {
+-    $table_mapping = new static($entity_type, $storage_definitions, $prefix);
++  public static function create(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '', bool $json_storage = FALSE) {
++    $table_mapping = new static($entity_type, $storage_definitions, $prefix, $json_storage);
+ 
+     $revisionable = $entity_type->isRevisionable();
+     $translatable = $entity_type->isTranslatable();
+@@ -199,8 +260,10 @@ public static function create(ContentEntityTypeInterface $entity_type, array $st
+       // denormalized in the base table but also stored in the revision table
+       // together with the entity ID and the revision ID as identifiers.
+       $table_mapping->setFieldNames($table_mapping->baseTable, array_diff($all_fields, $revision_metadata_fields));
+-      $revision_key_fields = [$id_key, $revision_key];
+-      $table_mapping->setFieldNames($table_mapping->revisionTable, array_merge($revision_key_fields, $revisionable_fields));
++      if (!$json_storage) {
++        $revision_key_fields = [$id_key, $revision_key];
++        $table_mapping->setFieldNames($table_mapping->revisionTable, array_merge($revision_key_fields, $revisionable_fields));
++      }
+     }
+     elseif (!$revisionable && $translatable) {
+       // Multilingual layouts store key field values in the base table. The
+@@ -210,9 +273,10 @@ public static function create(ContentEntityTypeInterface $entity_type, array $st
+       // performant queries. This means that only the UUID is not stored on
+       // the data table. Make sure the ID is always in the list, even if the ID
+       // key and the UUID key point to the same field.
+-      $table_mapping
+-        ->setFieldNames($table_mapping->baseTable, $key_fields)
+-        ->setFieldNames($table_mapping->dataTable, array_values(array_unique(array_merge([$id_key], array_diff($all_fields, [$uuid_key])))));
++      $table_mapping->setFieldNames($table_mapping->baseTable, $key_fields);
++      if (!$json_storage) {
++        $table_mapping->setFieldNames($table_mapping->dataTable, array_values(array_unique(array_merge([$id_key], array_diff($all_fields, [$uuid_key])))));
++      }
+     }
+     elseif ($revisionable && $translatable) {
+       // The revisionable multilingual layout stores key field values in the
+@@ -224,18 +288,24 @@ public static function create(ContentEntityTypeInterface $entity_type, array $st
+       // table, as well.
+       $table_mapping->setFieldNames($table_mapping->baseTable, $key_fields);
+ 
+-      // Like in the multilingual, non-revisionable case the UUID is not
+-      // in the data table. Additionally, do not store revision metadata
+-      // fields in the data table.
+-      $data_fields = array_values(array_unique(array_merge([$id_key], array_diff($all_fields, [$uuid_key], $revision_metadata_fields))));
+-      $table_mapping->setFieldNames($table_mapping->dataTable, $data_fields);
+-
+-      $revision_base_fields = array_merge([$id_key, $revision_key, $langcode_key], $revision_metadata_fields);
+-      $table_mapping->setFieldNames($table_mapping->revisionTable, $revision_base_fields);
+-
+-      $revision_data_key_fields = [$id_key, $revision_key, $langcode_key];
+-      $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$langcode_key]);
+-      $table_mapping->setFieldNames($table_mapping->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields));
++      if (!$json_storage) {
++        // Like in the multilingual, non-revisionable case the UUID is not
++        // in the data table. Additionally, do not store revision metadata
++        // fields in the data table.
++        $data_fields = array_values(array_unique(array_merge([$id_key], array_diff($all_fields, [$uuid_key], $revision_metadata_fields))));
++        $table_mapping->setFieldNames($table_mapping->dataTable, $data_fields);
++
++        $revision_base_fields = array_merge([
++          $id_key,
++          $revision_key,
++          $langcode_key,
++        ], $revision_metadata_fields);
++        $table_mapping->setFieldNames($table_mapping->revisionTable, $revision_base_fields);
++
++        $revision_data_key_fields = [$id_key, $revision_key, $langcode_key];
++        $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$langcode_key]);
++        $table_mapping->setFieldNames($table_mapping->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields));
++      }
+     }
+ 
+     // Add dedicated tables.
+@@ -250,14 +320,52 @@ public static function create(ContentEntityTypeInterface $entity_type, array $st
+       'langcode',
+       'delta',
+     ];
+-    foreach ($dedicated_table_definitions as $field_name => $definition) {
+-      $tables = [$table_mapping->getDedicatedDataTableName($definition)];
+-      if ($revisionable && $definition->isRevisionable()) {
+-        $tables[] = $table_mapping->getDedicatedRevisionTableName($definition);
++
++    if ($json_storage) {
++      // Add all fields to all embedded tables, this makes EntityQuery happy!
++      if ($revisionable) {
++        $table_mapping->setFieldNames($table_mapping->jsonStorageAllRevisionsTable, $all_fields);
++        $table_mapping->setFieldNames($table_mapping->jsonStorageCurrentRevisionTable, $all_fields);
++        $table_mapping->setFieldNames($table_mapping->jsonStorageLatestRevisionTable, $all_fields);
+       }
+-      foreach ($tables as $table_name) {
+-        $table_mapping->setFieldNames($table_name, [$field_name]);
+-        $table_mapping->setExtraColumns($table_name, $extra_columns);
++      elseif ($translatable) {
++        $table_mapping->setFieldNames($table_mapping->jsonStorageTranslationsTable, $all_fields);
++      }
++
++      foreach ($dedicated_table_definitions as $field_name => $definition) {
++        $tables = [];
++        if ($table_mapping->jsonStorageCurrentRevisionTable) {
++          $tables[] = $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->jsonStorageCurrentRevisionTable);
++        }
++        if ($table_mapping->jsonStorageTranslationsTable) {
++          $tables[] = $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->jsonStorageTranslationsTable);
++        }
++        if ($table_mapping->jsonStorageAllRevisionsTable) {
++          $tables[] = $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->jsonStorageAllRevisionsTable);
++        }
++        if ($table_mapping->jsonStorageLatestRevisionTable) {
++          $tables[] = $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->jsonStorageLatestRevisionTable);
++        }
++        if (!$definition->isTranslatable() && !$definition->isRevisionable()) {
++          $tables[] = $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->baseTable);
++        }
++
++        foreach ($tables as $table_name) {
++          $table_mapping->setFieldNames($table_name, [$field_name]);
++          $table_mapping->setExtraColumns($table_name, $extra_columns);
++        }
++      }
++    }
++    else {
++      foreach ($dedicated_table_definitions as $field_name => $definition) {
++        $tables = [$table_mapping->getDedicatedDataTableName($definition)];
++        if ($revisionable && $definition->isRevisionable()) {
++          $tables[] = $table_mapping->getDedicatedRevisionTableName($definition);
++        }
++        foreach ($tables as $table_name) {
++          $table_mapping->setFieldNames($table_name, [$field_name]);
++          $table_mapping->setExtraColumns($table_name, $extra_columns);
++        }
+       }
+     }
+ 
+@@ -312,6 +420,54 @@ public function getRevisionDataTable() {
+     return $this->revisionDataTable;
+   }
+ 
++  /**
++   * Gets the JSON storage all revisions table name.
++   *
++   * @return string|null
++   *   The all revisions table name.
++   *
++   * @internal
++   */
++  public function getJsonStorageAllRevisionsTable() {
++    return $this->jsonStorageAllRevisionsTable;
++  }
++
++  /**
++   * Gets the JSON storage current revision table name.
++   *
++   * @return string|null
++   *   The current revision table name.
++   *
++   * @internal
++   */
++  public function getJsonStorageCurrentRevisionTable() {
++    return $this->jsonStorageCurrentRevisionTable;
++  }
++
++  /**
++   * Gets the JSON storage latest revision table name.
++   *
++   * @return string|null
++   *   The latest revision table name.
++   *
++   * @internal
++   */
++  public function getJsonStorageLatestRevisionTable() {
++    return $this->jsonStorageLatestRevisionTable;
++  }
++
++  /**
++   * Gets the JSON storage translations table name.
++   *
++   * @return string|null
++   *   The translations table name.
++   *
++   * @internal
++   */
++  public function getJsonStorageTranslationsTable() {
++    return $this->jsonStorageTranslationsTable;
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -361,18 +517,57 @@ public function getFieldTableName($field_name) {
+     $result = NULL;
+ 
+     if (isset($this->fieldStorageDefinitions[$field_name])) {
+-      // Since a field may be stored in more than one table, we inspect tables
+-      // in order of relevance: the data table if present is the main place
+-      // where field data is stored, otherwise the base table is responsible for
+-      // storing field data. Revision metadata is an exception as it's stored
+-      // only in the revision table.
+       $storage_definition = $this->fieldStorageDefinitions[$field_name];
+-      $table_names = array_filter([
+-        $this->dataTable,
+-        $this->baseTable,
+-        $this->revisionTable,
+-        $this->getDedicatedDataTableName($storage_definition),
+-      ]);
++      if ($this->jsonStorage) {
++        $table_names = [
++          $this->baseTable,
++        ];
++
++        if (!$storage_definition->isTranslatable() && !$storage_definition->isRevisionable()) {
++          $table_names[] = $this->getJsonStorageDedicatedTableName($storage_definition, $this->baseTable);
++        }
++
++        if ($this->jsonStorageTranslationsTable) {
++          $table_names = array_merge($table_names, [
++            $this->jsonStorageTranslationsTable,
++            $this->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageTranslationsTable),
++          ]);
++        }
++
++        if ($this->jsonStorageCurrentRevisionTable) {
++          $table_names = array_merge($table_names, [
++            $this->jsonStorageCurrentRevisionTable,
++            $this->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageCurrentRevisionTable),
++          ]);
++        }
++
++        if ($this->jsonStorageLatestRevisionTable) {
++          $table_names = array_merge($table_names, [
++            $this->jsonStorageLatestRevisionTable,
++            $this->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageLatestRevisionTable),
++          ]);
++        }
++
++        if ($this->jsonStorageAllRevisionsTable) {
++          $table_names = array_merge($table_names, [
++            $this->jsonStorageAllRevisionsTable,
++            $this->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageAllRevisionsTable),
++          ]);
++        }
++      }
++      else {
++        // Since a field may be stored in more than one table, we inspect tables
++        // in order of relevance: the data table if present is the main place
++        // where field data is stored, otherwise the base table is responsible for
++        // storing field data. Revision metadata is an exception as it's stored
++        // only in the revision table.
++        $table_names = array_filter([
++          $this->dataTable,
++          $this->baseTable,
++          $this->revisionTable,
++          $this->getDedicatedDataTableName($storage_definition),
++        ]);
++      }
+ 
+       // Collect field columns.
+       $field_columns = [];
+@@ -395,6 +590,16 @@ public function getFieldTableName($field_name) {
+       throw new SqlContentEntityStorageException("Table information not available for the '$field_name' field.");
+     }
+ 
++    // The class Drupal\views\ViewsConfigUpdater needs the entity base table
++    // name instead of the embedded table name.
++    // @todo Need to test if this does not makes the driver slow.
++    if ($this->jsonStorage) {
++      $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT & DEBUG_BACKTRACE_IGNORE_ARGS, 2);
++      if (isset($backtrace[1]['class']) && ($backtrace[1]['class'] == ViewsConfigUpdater::class)) {
++        return $this->entityType->getBaseTable();
++      }
++    }
++
+     return $result;
+   }
+ 
+@@ -537,14 +742,60 @@ public function getDedicatedTableNames() {
+     $definitions = array_filter($this->fieldStorageDefinitions, function ($definition) use ($table_mapping) {
+       return $table_mapping->requiresDedicatedTableStorage($definition);
+     });
+-    $data_tables = array_map(function ($definition) use ($table_mapping) {
+-      return $table_mapping->getDedicatedDataTableName($definition);
+-    }, $definitions);
+-    $revision_tables = array_map(function ($definition) use ($table_mapping) {
+-      return $table_mapping->getDedicatedRevisionTableName($definition);
+-    }, $definitions);
+-    $dedicated_tables = array_merge(array_values($data_tables), array_values($revision_tables));
+-    return $dedicated_tables;
++
++    if ($this->jsonStorage) {
++      $dedicated_all_revisions_tables = [];
++      if ($table_mapping->jsonStorageAllRevisionsTable) {
++        $dedicated_all_revisions_tables = array_map(function ($definition) use ($table_mapping) {
++          return $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->jsonStorageAllRevisionsTable);
++        }, $definitions);
++      }
++
++      $dedicated_current_revision_tables = [];
++      if ($table_mapping->jsonStorageCurrentRevisionTable) {
++        $dedicated_current_revision_tables = array_map(function ($definition) use ($table_mapping) {
++          return $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->jsonStorageCurrentRevisionTable);
++        }, $definitions);
++      }
++
++      $dedicated_latest_revision_tables = [];
++      if ($table_mapping->jsonStorageLatestRevisionTable) {
++        $dedicated_latest_revision_tables = array_map(function ($definition) use ($table_mapping) {
++          return $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->jsonStorageLatestRevisionTable);
++        }, $definitions);
++      }
++
++      $dedicated_translations_tables = [];
++      if ($table_mapping->jsonStorageTranslationsTable) {
++        $dedicated_translations_tables = array_map(function ($definition) use ($table_mapping) {
++          return $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->jsonStorageTranslationsTable);
++        }, $definitions);
++      }
++
++      $dedicated_non_revision_non_translation_tables = array_map(function ($definition) use ($table_mapping) {
++        if (!$definition->isTranslatable() && !$definition->isRevisionable()) {
++          return $table_mapping->getJsonStorageDedicatedTableName($definition, $table_mapping->baseTable);
++        }
++      }, $definitions);
++
++      return array_merge(
++        array_values($dedicated_all_revisions_tables),
++        array_values($dedicated_current_revision_tables),
++        array_values($dedicated_latest_revision_tables),
++        array_values($dedicated_translations_tables),
++        array_values($dedicated_non_revision_non_translation_tables),
++      );
++    }
++    else {
++      $data_tables = array_map(function ($definition) use ($table_mapping) {
++        return $table_mapping->getDedicatedDataTableName($definition);
++      }, $definitions);
++      $revision_tables = array_map(function ($definition) use ($table_mapping) {
++        return $table_mapping->getDedicatedRevisionTableName($definition);
++      }, $definitions);
++      $dedicated_tables = array_merge(array_values($data_tables), array_values($revision_tables));
++      return $dedicated_tables;
++    }
+   }
+ 
+   /**
+@@ -649,4 +900,42 @@ protected function generateFieldTableName(FieldStorageDefinitionInterface $stora
+     return $table_name;
+   }
+ 
++  /**
++   * Generates a table name for a field embedded table.
++   *
++   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
++   *   The field storage definition.
++   * @param string $parent_table_name
++   *   The parent table name.
++   * @param bool $is_deleted
++   *   (optional) Whether the table name holding the values of a deleted field
++   *   should be returned.
++   *
++   * @return string
++   *   A string containing the generated name for the database table.
++   */
++  public function getJsonStorageDedicatedTableName(FieldStorageDefinitionInterface $storage_definition, $parent_table_name, $is_deleted = FALSE) {
++    if ($is_deleted) {
++      // When a field is a deleted, the table is renamed to
++      // {field_deleted_data_FIELD_UUID}. To make sure we don't end up with
++      // table names longer than 64 characters, we hash the unique storage
++      // identifier and return the first 10 characters so we end up with a short
++      // unique ID.
++      return "field_deleted_data_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
++    }
++    else {
++      $table_name = $parent_table_name . '__' . $storage_definition->getName();
++      // Limit the string to 220 characters, keeping a 16 characters margin for
++      // db prefixes.
++      // The maximum table name for MongoDB is 255 characters for unsharded
++      // collections and 235 characters for sharded collections.
++      // @see: https://www.mongodb.com/docs/manual/reference/limits/#mongodb-limit-Restriction-on-Collection-Names
++      if (strlen($table_name) > 220) {
++        // Truncate the parent table name and hash the of the field UUID.
++        $table_name = substr($parent_table_name, 0, 208) . '__' . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
++      }
++      return $table_name;
++    }
++  }
++
+ }
+diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+index 93029c399df3cc9bc2dfbf3b3b7ddf8a917df9b7..83905f11487d79d70fd07a834cddb465f72c2eaa 100644
+--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
++++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+@@ -24,6 +24,7 @@
+ use Drupal\Core\Language\LanguageInterface;
+ use Drupal\Core\Language\LanguageManagerInterface;
+ use Drupal\Core\Utility\Error;
++use Drupal\mongodb\Driver\Database\mongodb\EmbeddedTableData;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
+ 
+ /**
+@@ -106,6 +107,41 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
+    */
+   protected $revisionDataTable;
+ 
++  /**
++   * The JSON storage table that stores the all revisions data for the entity.
++   *
++   * @var string
++   */
++  protected $jsonStorageAllRevisionsTable;
++
++  /**
++   * The JSON storage table that stores the current revision data.
++   *
++   * @var string
++   */
++  protected $jsonStorageCurrentRevisionTable;
++
++  /**
++   * The JSON storage table that stores the latest revision data.
++   *
++   * @var string
++   */
++  protected $jsonStorageLatestRevisionTable;
++
++  /**
++   * The JSON storage table that stores the translations data.
++   *
++   * @var string
++   */
++  protected $jsonStorageTranslationsTable;
++
++  /**
++   * The MongoDB sequence service.
++   *
++   * @var \Drupal\mongodb\Driver\Database\mongodb\Sequences
++   */
++  protected $mongoSequences;
++
+   /**
+    * Active database connection.
+    *
+@@ -200,22 +236,40 @@ protected function initTableLayout() {
+     $this->dataTable = NULL;
+     $this->revisionDataTable = NULL;
+ 
++    // The JSON storage embedded tables.
++    $this->jsonStorageAllRevisionsTable = NULL;
++    $this->jsonStorageCurrentRevisionTable = NULL;
++    $this->jsonStorageLatestRevisionTable = NULL;
++    $this->jsonStorageTranslationsTable = NULL;
++
+     $table_mapping = $this->getTableMapping();
+     $this->baseTable = $table_mapping->getBaseTable();
+     $revisionable = $this->entityType->isRevisionable();
+     if ($revisionable) {
+       $this->revisionKey = $this->entityType->getKey('revision') ?: 'revision_id';
+-      $this->revisionTable = $table_mapping->getRevisionTable();
++      if ($this->database->driver() == 'mongodb') {
++        $this->jsonStorageAllRevisionsTable = $table_mapping->getJsonStorageAllRevisionsTable();
++        $this->jsonStorageCurrentRevisionTable = $table_mapping->getJsonStorageCurrentRevisionTable();
++        $this->jsonStorageLatestRevisionTable = $table_mapping->getJsonStorageLatestRevisionTable();
++      }
++      else {
++        $this->revisionTable = $table_mapping->getRevisionTable();
++      }
+     }
+     $translatable = $this->entityType->isTranslatable();
+     if ($translatable) {
+-      $this->dataTable = $table_mapping->getDataTable();
++      if ($this->database->driver() != 'mongodb') {
++        $this->dataTable = $table_mapping->getDataTable();
++      }
+       $this->langcodeKey = $this->entityType->getKey('langcode');
+       $this->defaultLangcodeKey = $this->entityType->getKey('default_langcode');
+     }
+-    if ($revisionable && $translatable) {
++    if ($revisionable && $translatable && ($this->database->driver() != 'mongodb')) {
+       $this->revisionDataTable = $table_mapping->getRevisionDataTable();
+     }
++    if (!$revisionable && $translatable && ($this->database->driver() == 'mongodb')) {
++      $this->jsonStorageTranslationsTable = $table_mapping->getJsonStorageTranslationsTable();
++    }
+   }
+ 
+   /**
+@@ -258,6 +312,46 @@ public function getRevisionDataTable() {
+     return $this->revisionDataTable;
+   }
+ 
++  /**
++   * Gets the JSON storage all revisions table name.
++   *
++   * @return string|false
++   *   The table name or FALSE if it is not available.
++   */
++  public function getJsonStorageAllRevisionsTable() {
++    return $this->jsonStorageAllRevisionsTable;
++  }
++
++  /**
++   * Gets the JSON storage current revision table name.
++   *
++   * @return string|false
++   *   The table name or FALSE if it is not available.
++   */
++  public function getJsonStorageCurrentRevisionTable() {
++    return $this->jsonStorageCurrentRevisionTable;
++  }
++
++  /**
++   * Gets the JSON storage latest revision table name.
++   *
++   * @return string|false
++   *   The table name or FALSE if it is not available.
++   */
++  public function getJsonStorageLatestRevisionTable() {
++    return $this->jsonStorageLatestRevisionTable;
++  }
++
++  /**
++   * Gets the JSON storage translations table name.
++   *
++   * @return string|false
++   *   The table name or FALSE if it is not available.
++   */
++  public function getJsonStorageTranslationsTable() {
++    return $this->jsonStorageTranslationsTable;
++  }
++
+   /**
+    * Gets the entity type's storage schema object.
+    *
+@@ -347,13 +441,13 @@ public function getTableMapping(?array $storage_definitions = NULL) {
+     // comparing old and new storage schema, we compute the table mapping
+     // without caching.
+     if ($storage_definitions) {
+-      return $this->getCustomTableMapping($this->entityType, $storage_definitions);
++      return $this->getCustomTableMapping($this->entityType, $storage_definitions, '', ($this->database->driver() == 'mongodb'));
+     }
+ 
+     // If we are using our internal storage definitions, which is our main use
+     // case, we can statically cache the computed table mapping.
+     if (!isset($this->tableMapping)) {
+-      $this->tableMapping = $this->getCustomTableMapping($this->entityType, $this->fieldStorageDefinitions);
++      $this->tableMapping = $this->getCustomTableMapping($this->entityType, $this->fieldStorageDefinitions, '', ($this->database->driver() == 'mongodb'));
+     }
+ 
+     return $this->tableMapping;
+@@ -370,15 +464,18 @@ public function getTableMapping(?array $storage_definitions = NULL) {
+    * @param string $prefix
+    *   (optional) A prefix to be used by all the tables of this mapping.
+    *   Defaults to an empty string.
++   * @param bool $json_storage
++   *   (optional) Flag to indicate that we are storing entity data in JSON
++   *   documents. Defaults to FALSE.
+    *
+    * @return \Drupal\Core\Entity\Sql\TableMappingInterface
+    *   A table mapping object for the entity's tables.
+    *
+    * @internal
+    */
+-  public function getCustomTableMapping(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '') {
++  public function getCustomTableMapping(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '', bool $json_storage = FALSE) {
+     $prefix = $prefix ?: ($this->temporary ? 'tmp_' : '');
+-    return DefaultTableMapping::create($entity_type, $storage_definitions, $prefix);
++    return DefaultTableMapping::create($entity_type, $storage_definitions, $prefix, $json_storage);
+   }
+ 
+   /**
+@@ -449,57 +546,115 @@ protected function mapFromStorageRecords(array $records, $load_from_revision = F
+       return [];
+     }
+ 
+-    // Get the names of the fields that are stored in the base table and, if
+-    // applicable, the revision table. Other entity data will be loaded in
+-    // loadFromSharedTables() and loadFromDedicatedTables().
+-    $field_names = $this->tableMapping->getFieldNames($this->baseTable);
+-    if ($this->revisionTable) {
+-      $field_names = array_unique(array_merge($field_names, $this->tableMapping->getFieldNames($this->revisionTable)));
+-    }
+-
+-    $values = [];
+-    foreach ($records as $id => $record) {
+-      $values[$id] = [];
+-      // Skip the item delta and item value levels (if possible) but let the
+-      // field assign the value as suiting. This avoids unnecessary array
+-      // hierarchies and saves memory here.
+-      foreach ($field_names as $field_name) {
+-        $field_columns = $this->tableMapping->getColumnNames($field_name);
+-        // Handle field types that store several properties.
+-        if (count($field_columns) > 1) {
+-          $definition_columns = $this->fieldStorageDefinitions[$field_name]->getColumns();
+-          foreach ($field_columns as $property_name => $column_name) {
++    if ($this->database->driver() == 'mongodb') {
++      // @todo remove: Get all the embedded table names without the base table.
++      $embedded_table_names = $this->database->tableInformation()->getTableEmbeddedTables($this->baseTable);
++
++      $values_embedded_tables = [];
++      $values = [];
++      foreach ($records as $id => $record) {
++        $values[$id] = [];
++        // Skip the item delta and item value levels (if possible) but let the
++        // field assign the value as suiting. This avoids unnecessary array
++        // hierarchies and saves memory here.
++        foreach ($record as $name => $value) {
++          // Handle columns named [field_name]__[column_name] (e.g for field types
++          // that store several properties).
++          if (in_array($name, $embedded_table_names, TRUE)) {
++            // Add the embedded table data to the values array.
++            $values_embedded_tables[$id][$name] = $value;
++          }
++          elseif ($field_name = strstr($name, '__', TRUE)) {
++            $property_name = substr($name, strpos($name, '__') + 2);
++            // @todo Test if typecasting is necessary. Maybe special case if
++            // $value is null.
++            $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = (is_null($value) ? NULL : (string) $value);
++          }
++          else {
++            // Handle columns named directly after the field (e.g if the field
++            // type only stores one property).
++            // @todo Test if typecasting is necessary. Maybe special case if
++            // $value is null.
++            if (is_null($value)) {
++              $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = NULL;
++            }
++            elseif ($value === FALSE) {
++              // Drupal expects boolean values with the value FALSE to
++              // have the string value of zero.
++              $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = '0';
++            }
++            else {
++              $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = (string) $value;
++            }
++          }
++        }
++
++        // @todo Check if we can remove the next if-statement.
++        if ($load_from_revision && ($record->{$this->revisionKey} != $load_from_revision)) {
++          $values[$id][$this->revisionKey][LanguageInterface::LANGCODE_DEFAULT] = (string) $load_from_revision;
++        }
++      }
++
++      // Initialize translations array.
++      $translations = array_fill_keys(array_keys($values), []);
++
++      // Load values from shared and dedicated tables.
++      $this->loadFromEmbeddedTables($values, $translations, $values_embedded_tables, $load_from_revision);
++    }
++    else {
++      // Get the names of the fields that are stored in the base table and, if
++      // applicable, the revision table. Other entity data will be loaded in
++      // loadFromSharedTables() and loadFromDedicatedTables().
++      $field_names = $this->tableMapping->getFieldNames($this->baseTable);
++      if ($this->revisionTable) {
++        $field_names = array_unique(array_merge($field_names, $this->tableMapping->getFieldNames($this->revisionTable)));
++      }
++
++      $values = [];
++      foreach ($records as $id => $record) {
++        $values[$id] = [];
++        // Skip the item delta and item value levels (if possible) but let the
++        // field assign the value as suiting. This avoids unnecessary array
++        // hierarchies and saves memory here.
++        foreach ($field_names as $field_name) {
++          $field_columns = $this->tableMapping->getColumnNames($field_name);
++          // Handle field types that store several properties.
++          if (count($field_columns) > 1) {
++            $definition_columns = $this->fieldStorageDefinitions[$field_name]->getColumns();
++            foreach ($field_columns as $property_name => $column_name) {
++              if (property_exists($record, $column_name)) {
++                $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = !empty($definition_columns[$property_name]['serialize']) ? unserialize($record->{$column_name}) : $record->{$column_name};
++                unset($record->{$column_name});
++              }
++            }
++          }
++          // Handle field types that store only one property.
++          else {
++            $column_name = reset($field_columns);
+             if (property_exists($record, $column_name)) {
+-              $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = !empty($definition_columns[$property_name]['serialize']) ? unserialize($record->{$column_name}) : $record->{$column_name};
++              $columns = $this->fieldStorageDefinitions[$field_name]->getColumns();
++              $column = reset($columns);
++              $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = !empty($column['serialize']) ? unserialize($record->{$column_name}) : $record->{$column_name};
+               unset($record->{$column_name});
+             }
+           }
+         }
+-        // Handle field types that store only one property.
+-        else {
+-          $column_name = reset($field_columns);
+-          if (property_exists($record, $column_name)) {
+-            $columns = $this->fieldStorageDefinitions[$field_name]->getColumns();
+-            $column = reset($columns);
+-            $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = !empty($column['serialize']) ? unserialize($record->{$column_name}) : $record->{$column_name};
+-            unset($record->{$column_name});
+-          }
++
++        // Handle additional record entries that are not provided by an entity
++        // field, such as 'isDefaultRevision'.
++        foreach ($record as $name => $value) {
++          $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = $value;
+         }
+       }
+ 
+-      // Handle additional record entries that are not provided by an entity
+-      // field, such as 'isDefaultRevision'.
+-      foreach ($record as $name => $value) {
+-        $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = $value;
+-      }
+-    }
++      // Initialize translations array.
++      $translations = array_fill_keys(array_keys($values), []);
+ 
+-    // Initialize translations array.
+-    $translations = array_fill_keys(array_keys($values), []);
++      // Load values from shared and dedicated tables.
++      $this->loadFromSharedTables($values, $translations, $load_from_revision);
++      $this->loadFromDedicatedTables($values, $load_from_revision);
+ 
+-    // Load values from shared and dedicated tables.
+-    $this->loadFromSharedTables($values, $translations, $load_from_revision);
+-    $this->loadFromDedicatedTables($values, $load_from_revision);
++    }
+ 
+     $entities = [];
+     foreach ($values as $id => $entity_values) {
+@@ -512,6 +667,371 @@ protected function mapFromStorageRecords(array $records, $load_from_revision = F
+     return $entities;
+   }
+ 
++  /**
++   * Loads values for fields stored in the embedded tables.
++   *
++   * @param array &$values
++   *   Associative array of entities values, keyed on the entity ID.
++   * @param array &$translations
++   *   List of translations, keyed on the entity ID.
++   * @param array $values_embedded_tables
++   *   The values of the embedded tables.
++   * @param int|bool $load_from_revision_id
++   *   Flag to indicate whether revisions should be loaded or not.
++   */
++  protected function loadFromEmbeddedTables(array &$values, array &$translations, array &$values_embedded_tables, $load_from_revision_id = FALSE) {
++    if ($load_from_revision_id && $this->jsonStorageAllRevisionsTable) {
++      $embedded_table = $this->jsonStorageAllRevisionsTable;
++      $table_mapping = $this->getTableMapping();
++
++      // Find revisioned fields that are not entity keys. Exclude the langcode
++      // key as the base table holds only the default language.
++      $base_fields = array_diff($table_mapping->getFieldNames($this->baseTable), [$this->langcodeKey]);
++
++      $revisioned_fields = array_diff($table_mapping->getFieldNames($this->jsonStorageAllRevisionsTable), [$this->idKey, $this->uuidKey]);
++
++      // If there are no data fields then only revisioned fields are needed
++      // else both data fields and revisioned fields are needed to map the
++      // entity values.
++      $all_fields = $revisioned_fields;
++
++      // Get the field name for the default revision field.
++      $revision_default_field = $this->entityType->getRevisionMetadataKey('revision_default');
++    }
++    elseif ($this->jsonStorageCurrentRevisionTable) {
++      $embedded_table = $this->jsonStorageCurrentRevisionTable;
++      $table_mapping = $this->getTableMapping();
++
++      // Find revisioned fields that are not entity keys. Exclude the langcode
++      // key as the base table holds only the default language.
++      $base_fields = array_diff($table_mapping->getFieldNames($this->baseTable), [$this->langcodeKey]);
++
++      $revisioned_fields = array_diff($table_mapping->getFieldNames($this->jsonStorageCurrentRevisionTable), [$this->idKey, $this->uuidKey]);
++
++      // If there are no data fields then only revisioned fields are needed
++      // else both data fields and revisioned fields are needed to map the
++      // entity values.
++      $all_fields = $revisioned_fields;
++
++      // Get the field name for the default revision field.
++      $revision_default_field = $this->entityType->getRevisionMetadataKey('revision_default');
++    }
++    elseif ($this->jsonStorageTranslationsTable) {
++      $embedded_table = $this->jsonStorageTranslationsTable;
++      $table_mapping = $this->getTableMapping();
++
++      // Find revisioned fields that are not entity keys. Exclude the langcode
++      // key as the base table holds only the default language.
++      $base_fields = array_diff($table_mapping->getFieldNames($this->baseTable), [$this->langcodeKey]);
++
++      $translations_fields = array_diff($table_mapping->getFieldNames($this->jsonStorageTranslationsTable), [$this->idKey, $this->uuidKey]);
++
++      // If there are no data fields then only revisioned fields are needed
++      // else both data fields and revisioned fields are needed to map the
++      // entity values.
++      $all_fields = $translations_fields;
++
++      // There is no default revision field to be set.
++      $revision_default_field = NULL;
++    }
++    else {
++      $embedded_table = $this->baseTable;
++      $base_fields = [];
++      $all_fields = [];
++
++      // There is no default revision field to be set.
++      $revision_default_field = NULL;
++    }
++
++    // Get the field names for the "created" field types
++    $storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId);
++    $created_fields = array_keys(array_filter($storage_definitions, function (FieldStorageDefinitionInterface $definition) {
++      return $definition->getType() == 'created';
++    }));
++
++    $base_fields += [$this->revisionKey];
++    $base_fields += [$this->langcodeKey];
++    $base_fields = array_diff($base_fields, $created_fields);
++    if (isset($revisioned_fields) && is_array($revisioned_fields)) {
++      $base_fields = array_diff($base_fields, $revisioned_fields);
++    }
++    if (isset($translations_fields) && is_array($translations_fields)) {
++      $base_fields = array_diff($base_fields, $translations_fields);
++    }
++
++    // Get the data table and the data revision table data.
++    foreach ($values_embedded_tables as $id => $embedded_tables) {
++      // Get the embedded table data for one entity.
++      $embedded_table_data = [];
++      foreach ($embedded_tables as $embedded_table_name => $embedded_table_rows) {
++        if (!empty($embedded_table_name) && is_array($embedded_table_rows)) {
++          if ($embedded_table_name == $this->jsonStorageTranslationsTable) {
++            $embedded_table_data[$this->jsonStorageTranslationsTable] = $embedded_table_rows;
++          }
++          elseif (($embedded_table_name == $this->jsonStorageCurrentRevisionTable) && !$load_from_revision_id) {
++            $embedded_table_data[$this->jsonStorageCurrentRevisionTable] = $embedded_table_rows;
++          }
++          elseif (($embedded_table_name == $this->jsonStorageAllRevisionsTable) && $load_from_revision_id) {
++            foreach ($embedded_table_rows as $embedded_table_revision) {
++              if ($load_from_revision_id && isset($embedded_table_revision[$this->revisionKey]) && ($embedded_table_revision[$this->revisionKey] == $load_from_revision_id)) {
++                $embedded_table_data[$this->jsonStorageAllRevisionsTable][] = $embedded_table_revision;
++              }
++            }
++          }
++          elseif (!$this->jsonStorageTranslationsTable && !$this->jsonStorageCurrentRevisionTable && !$this->jsonStorageLatestRevisionTable && !$this->jsonStorageAllRevisionsTable) {
++            $embedded_tables[$this->idKey] = $values[$id][$this->idKey][LanguageInterface::LANGCODE_DEFAULT];
++            // @todo Maybe there should be some else statement for the next if
++            // statement.
++            if (!empty($values[$id][$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT])) {
++              $embedded_tables[$this->langcodeKey] = $values[$id][$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT];
++            }
++            $embedded_table_data[$this->baseTable] = [$embedded_tables];
++          }
++        }
++      }
++
++      // Get the list of translations from the latest revision.
++      // foreach ($values_embedded_tables as $id => $embedded_tables) {
++      // foreach ($embedded_tables as $embedded_table_name => $embedded_table_rows) {
++      // if (is_array($embedded_table_rows)) {
++      // foreach ($embedded_table_rows as $embedded_table_row) {
++      // if (empty($embedded_table_row[$this->defaultLangcodeKey])) {
++      // $langcode = $embedded_table_row[$this->langcodeKey];
++      // }
++      // else {
++      // $langcode = LanguageInterface::LANGCODE_DEFAULT;
++      // }
++      //
++      // if ($embedded_table_name == $this->jsonStorageLatestRevisionTable) {
++      // $translations[$id][$langcode] = TRUE;
++      // }
++      // elseif ($embedded_table_name == $this->jsonStorageTranslationsTable) {
++      // $translations[$id][$langcode] = TRUE;
++      // }
++      // }
++      // }
++      // }
++      // }
++
++      // Use the collected embedded table data to retrieve the entity values.
++      foreach ($embedded_table_data as $table_rows) {
++        if (is_array($table_rows)) {
++          foreach ($table_rows as $table_row) {
++            $id = $table_row[$this->idKey];
++
++            // Field values in default language are stored with
++            // LanguageInterface::LANGCODE_DEFAULT as key.
++            if (!empty($this->defaultLangcodeKey) && !empty($this->langcodeKey) && empty($table_row[$this->defaultLangcodeKey]) && !empty($table_row[$this->langcodeKey])) {
++              $langcode = $table_row[$this->langcodeKey];
++            }
++            else {
++              $langcode = LanguageInterface::LANGCODE_DEFAULT;
++            }
++
++            $langcode_is_default_langcode = FALSE;
++            if (isset($values[$id][$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT]) && ($values[$id][$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT] == $langcode)) {
++              $langcode_is_default_langcode = TRUE;
++            }
++
++            $translations[$id][$langcode] = TRUE;
++
++            foreach ($all_fields as $field_name) {
++              if (!in_array($field_name, $base_fields)) {
++                $storage_definition = $storage_definitions[$field_name];
++                $definition_columns = $storage_definition->getColumns();
++                $columns = $table_mapping->getColumnNames($field_name);
++
++                // Do not key single-column fields by property name.
++                if (count($columns) == 1) {
++                  if (is_null($table_row[reset($columns)])) {
++                    $values[$id][$field_name][$langcode] = NULL;
++                  }
++                  elseif ($table_row[reset($columns)] === FALSE) {
++                    // Drupal expects boolean values with the value FALSE to
++                    // have the string value of zero.
++                    $values[$id][$field_name][$langcode] = '0';
++                  }
++                  else {
++                    $column_name = reset($columns);
++                    $column_attributes = $definition_columns[key($columns)];
++                    $values[$id][$field_name][$langcode] = (!empty($column_attributes['serialize'])) ? unserialize($table_row[$column_name]) : (string) $table_row[$column_name];
++                  }
++
++                  if ($langcode_is_default_langcode) {
++                    $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = $values[$id][$field_name][$langcode];
++                  }
++
++                  if ($field_name == $revision_default_field) {
++                    if ($table_row[reset($columns)] === FALSE) {
++                      $values[$id]['isDefaultRevision'][LanguageInterface::LANGCODE_DEFAULT] = '0';
++                    }
++                    else {
++                      $values[$id]['isDefaultRevision'][LanguageInterface::LANGCODE_DEFAULT] = '1';
++                    }
++                  }
++                }
++                else {
++                  $item = [];
++                  foreach ($storage_definitions[$field_name]->getColumns() as $column => $attributes) {
++                    $column_name = $table_mapping->getFieldColumnName($storage_definitions[$field_name], $column);
++
++                    if (is_null($table_row[$column_name])) {
++                      $item[$column] = NULL;
++                    }
++                    elseif ($table_row[$column_name] === FALSE) {
++                      // Drupal expects boolean values with the value FALSE to
++                      // have the string value of zero.
++                      $item[$column] = '0';
++                    }
++                    else {
++                      $item[$column] = (!empty($attributes['serialize'])) ? unserialize($table_row[$column_name]) : $table_row[$column_name];
++                    }
++                  }
++
++                  $values[$id][$field_name][$langcode] = $item;
++
++                  if ($langcode_is_default_langcode) {
++                    $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = $values[$id][$field_name][$langcode];
++                  }
++                }
++              }
++            }
++          }
++        }
++      }
++
++      $this->loadFromEmbeddedDedicatedTables($values, $embedded_table, $embedded_table_data, $load_from_revision_id);
++    }
++  }
++
++  /**
++   * Loads values of fields stored in dedicated tables for a group of entities.
++   *
++   * @param array &$values
++   *   An array of values keyed by entity ID.
++   * @param string $embedded_table_name
++   *   The embedded table name.
++   * @param array $embedded_table_data
++   *   The embedded table data.
++   * @param bool $load_from_revision_id
++   *   (optional) Flag to indicate whether revisions should be loaded or not,
++   *   defaults to FALSE.
++   */
++  protected function loadFromEmbeddedDedicatedTables(array &$values, $embedded_table_name, array $embedded_table_data, $load_from_revision_id) {
++    if (empty($values)) {
++      return;
++    }
++
++    // Collect entities ids, bundles and languages.
++    $bundles = [];
++    $ids = [];
++    $default_langcodes = [];
++    foreach ($values as $key => $entity_values) {
++      if ($this->bundleKey && !empty($entity_values[$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT])) {
++        $bundles[$entity_values[$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT]] = TRUE;
++      }
++      else {
++        $bundles[$this->entityTypeId] = TRUE;
++      }
++      $ids[] = !$load_from_revision_id ? $key : $entity_values[$this->revisionKey][LanguageInterface::LANGCODE_DEFAULT];
++      if ($this->langcodeKey && isset($entity_values[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT])) {
++        $default_langcodes[$key] = $entity_values[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT];
++      }
++    }
++
++    // Collect impacted fields.
++    $storage_definitions = [];
++    $definitions = [];
++    $table_mapping = $this->getTableMapping();
++    foreach ($bundles as $bundle => $v) {
++      $definitions[$bundle] = $this->entityFieldManager->getFieldDefinitions($this->entityTypeId, $bundle);
++      foreach ($definitions[$bundle] as $field_name => $field_definition) {
++        $storage_definition = $field_definition->getFieldStorageDefinition();
++        if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
++          $storage_definitions[$field_name] = $storage_definition;
++        }
++      }
++    }
++
++    // Load field data.
++    $langcodes = array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL));
++    foreach ($storage_definitions as $field_name => $storage_definition) {
++      $table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $embedded_table_name);
++
++      if (isset($embedded_table_data[$embedded_table_name])) {
++        $embedded_table_data = $embedded_table_data[$embedded_table_name];
++      }
++
++      $rows = [];
++      $deltas = [];
++      foreach ($embedded_table_data as $embedded_table_row) {
++        foreach ($embedded_table_row as $embedded_table_key => $embedded_table_value) {
++          if (($embedded_table_key == $table) && is_array($embedded_table_value)) {
++            foreach ($embedded_table_value as $dedicated_table_row) {
++              if (in_array($dedicated_table_row['langcode'], $langcodes, TRUE)) {
++                if (!isset($dedicated_table_row['deleted']) || !$dedicated_table_row['deleted']) {
++                  // Change the table row entity ID to an integer.
++                  if (!$load_from_revision_id && in_array(intval($dedicated_table_row['entity_id']), $ids)) {
++                    $rows[] = (object) $dedicated_table_row;
++                    $deltas[] = $dedicated_table_row['delta'];
++                  }
++                  // Change the table row revision ID to an integer.
++                  elseif ($load_from_revision_id && in_array(intval($dedicated_table_row['revision_id']), $ids)) {
++                    $rows[] = (object) $dedicated_table_row;
++                    $deltas[] = $dedicated_table_row['delta'];
++                  }
++                }
++              }
++            }
++          }
++        }
++      }
++
++      // Sort the dedicated rows according to their delta value.
++      array_multisort($deltas, $rows);
++
++      foreach ($rows as $row) {
++        $bundle = $row->bundle;
++
++        if (!in_array($row->langcode, $langcodes, TRUE)) {
++          continue;
++        }
++        if (isset($row->deleted) && $row->deleted) {
++          continue;
++        }
++
++        // Field values in default language are stored with
++        // LanguageInterface::LANGCODE_DEFAULT as key.
++        $langcode = LanguageInterface::LANGCODE_DEFAULT;
++        if ($this->langcodeKey && isset($default_langcodes[$row->entity_id]) && $row->langcode != $default_langcodes[$row->entity_id]) {
++          $langcode = $row->langcode;
++        }
++
++        if (!isset($values[$row->entity_id][$field_name][$langcode])) {
++          $values[$row->entity_id][$field_name][$langcode] = [];
++        }
++
++        // Ensure that records for non-translatable fields having invalid
++        // languages are skipped.
++        if ($langcode == LanguageInterface::LANGCODE_DEFAULT || $definitions[$bundle][$field_name]->isTranslatable()) {
++          if ($storage_definition->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || count($values[$row->entity_id][$field_name][$langcode]) < $storage_definition->getCardinality()) {
++            $item = [];
++            // For each column declared by the field, populate the item from the
++            // prefixed database column.
++            foreach ($storage_definition->getColumns() as $column => $attributes) {
++              $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
++              // Unserialize the value if specified in the column schema.
++              $item[$column] = (!empty($attributes['serialize']) ? unserialize($row->$column_name) : $row->$column_name);
++            }
++
++            // Add the item to the field values for the entity.
++            $values[$row->entity_id][$field_name][$langcode][] = $item;
++          }
++        }
++      }
++    }
++  }
++
+   /**
+    * Loads values for fields stored in the shared data tables.
+    *
+@@ -551,7 +1071,11 @@ protected function loadFromSharedTables(array &$values, array &$translations, $l
+         $all_fields = $revisioned_fields;
+         if ($data_fields) {
+           $all_fields = array_merge($revisioned_fields, $data_fields);
+-          $query->leftJoin($this->dataTable, 'data', "([revision].[$this->idKey] = [data].[$this->idKey] AND [revision].[$this->langcodeKey] = [data].[$this->langcodeKey])");
++          $query->leftJoin($this->dataTable, 'data',
++            $query->joinCondition()
++              ->compare("revision.$this->idKey", "data.$this->idKey")
++              ->compare("revision.$this->langcodeKey", "data.$this->langcodeKey")
++          );
+           $column_names = [];
+           // Some fields can have more then one columns in the data table so
+           // column names are needed.
+@@ -619,13 +1143,31 @@ protected function doLoadMultipleRevisionsFieldItems($revision_ids) {
+     $revision_ids = $this->cleanIds($revision_ids, 'revision');
+ 
+     if (!empty($revision_ids)) {
+-      // Build and execute the query.
+-      $query_result = $this->buildQuery(NULL, $revision_ids)->execute();
+-      $records = $query_result->fetchAllAssoc($this->revisionKey);
++      if ($this->database->driver() == 'mongodb') {
++        foreach ($revision_ids as $revision_id) {
++          // Build and execute the query.
++          $query_result = $this->buildQuery([], $revision_id)->execute();
++          $records = $query_result->fetchAllAssoc($this->idKey);
++
++          if (!empty($records)) {
++            // Convert the raw records to entity objects.
++            $entities = $this->mapFromStorageRecords($records, $revision_id);
++            $revision = reset($entities) ?: NULL;
++            if ($revision) {
++              $revisions[$revision->getRevisionId()] = $revision;
++            }
++          }
++        }
++      }
++      else {
++        // Build and execute the query.
++        $query_result = $this->buildQuery(NULL, $revision_ids)->execute();
++        $records = $query_result->fetchAllAssoc($this->revisionKey);
+ 
+-      // Map the loaded records into entity objects and according fields.
+-      if ($records) {
+-        $revisions = $this->mapFromStorageRecords($records, TRUE);
++        // Map the loaded records into entity objects and according fields.
++        if ($records) {
++          $revisions = $this->mapFromStorageRecords($records, TRUE);
++        }
+       }
+     }
+ 
+@@ -636,31 +1178,55 @@ protected function doLoadMultipleRevisionsFieldItems($revision_ids) {
+    * {@inheritdoc}
+    */
+   protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {
+-    $this->database->delete($this->revisionTable)
+-      ->condition($this->revisionKey, $revision->getRevisionId())
+-      ->execute();
++    if ($this->database->driver() == 'mongodb') {
++      $revision_id = (int) $revision->getRevisionId();
+ 
+-    if ($this->revisionDataTable) {
+-      $this->database->delete($this->revisionDataTable)
++      $field_data = $this->database->tableInformation()->getTableField($this->baseTable, $this->idKey);
++      if (isset($field_data['type']) && in_array($field_data['type'], ['int', 'serial'])) {
++        $entity_id = (int) $revision->id();
++      }
++      else {
++        $entity_id = (string) $revision->id();
++      }
++
++      $prefixed_table = $this->database->getPrefix() . $this->baseTable;
++      $update_operations = [];
++      $update_operations['$pull'] = [$this->jsonStorageAllRevisionsTable => [$this->revisionKey => $revision_id]];
++
++      // Perform all update operations on the entity.
++      $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++        [$this->idKey => $entity_id],
++        $update_operations,
++        ['session' => $this->database->getMongodbSession()],
++      );
++    }
++    else {
++      $this->database->delete($this->revisionTable)
+         ->condition($this->revisionKey, $revision->getRevisionId())
+         ->execute();
+-    }
+ 
+-    $this->deleteRevisionFromDedicatedTables($revision);
++      if ($this->revisionDataTable) {
++        $this->database->delete($this->revisionDataTable)
++          ->condition($this->revisionKey, $revision->getRevisionId())
++          ->execute();
++      }
++
++      $this->deleteRevisionFromDedicatedTables($revision);
++    }
+   }
+ 
+   /**
+    * {@inheritdoc}
+    */
+   protected function buildPropertyQuery(QueryInterface $entity_query, array $values) {
+-    if ($this->dataTable) {
++    if ($this->entityType->isTranslatable()) {
+       // @todo We should not be using a condition to specify whether conditions
+       //   apply to the default language. See
+       //   https://www.drupal.org/node/1866330.
+       // Default to the original entity language if not explicitly specified
+       // otherwise.
+       if (!array_key_exists($this->defaultLangcodeKey, $values)) {
+-        $values[$this->defaultLangcodeKey] = 1;
++        $values[$this->defaultLangcodeKey] = TRUE;
+       }
+       // If the 'default_langcode' flag is explicitly not set, we do not care
+       // whether the queried values are in the original entity language or not.
+@@ -696,43 +1262,96 @@ protected function buildQuery($ids, $revision_ids = FALSE) {
+ 
+     $query->addTag($this->entityTypeId . '_load_multiple');
+ 
+-    if ($revision_ids) {
+-      $query->join($this->revisionTable, 'revision', "[revision].[{$this->idKey}] = [base].[{$this->idKey}] AND [revision].[{$this->revisionKey}] IN (:revisionIds[])", [':revisionIds[]' => $revision_ids]);
+-    }
+-    elseif ($this->revisionTable) {
+-      $query->join($this->revisionTable, 'revision', "[revision].[{$this->revisionKey}] = [base].[{$this->revisionKey}]");
+-    }
+-
+-    // Add fields from the {entity} table.
+-    $table_mapping = $this->getTableMapping();
+-    $entity_fields = $table_mapping->getAllColumns($this->baseTable);
++    if ($this->database->driver() == 'mongodb') {
++      // Add fields from the {entity} table.
++      $table_mapping = $this->getTableMapping();
++      $entity_fields = $table_mapping->getAllColumns($this->baseTable);
++
++      $query->fields('base', $entity_fields);
++
++      $table_information = $this->database->tableInformation();
++      $table_information->load(TRUE);
++      $embedded_table_names = $table_information->getTableEmbeddedTables($this->entityType->getBaseTable());
++      $query->fields('base', $embedded_table_names);
++
++      if ($ids) {
++        // MongoDB needs integer values to be real integers.
++        $definition = $this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId)[$this->idKey];
++        if ($definition->getType() == 'integer') {
++          if (is_array($ids)) {
++            foreach ($ids as &$id) {
++              $id = (int) $id;
++            }
++          }
++          else {
++            $ids = (int) $ids;
++          }
++        }
+ 
+-    if ($this->revisionTable) {
+-      // Add all fields from the {entity_revision} table.
+-      $entity_revision_fields = $table_mapping->getAllColumns($this->revisionTable);
+-      $entity_revision_fields = array_combine($entity_revision_fields, $entity_revision_fields);
+-      // The ID field is provided by entity, so remove it.
+-      unset($entity_revision_fields[$this->idKey]);
++        $query->condition("base.{$this->idKey}", $ids, 'IN');
++      }
+ 
+-      // Remove all fields from the base table that are also fields by the same
+-      // name in the revision table.
+-      $entity_field_keys = array_flip($entity_fields);
+-      foreach ($entity_revision_fields as $name) {
+-        if (isset($entity_field_keys[$name])) {
+-          unset($entity_fields[$entity_field_keys[$name]]);
++      if ($revision_ids) {
++        // MongoDB needs integer values to be real integers.
++        $definition = $this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId)[$this->revisionKey];
++        if ($definition->getType() == 'integer') {
++          if (is_array($revision_ids)) {
++            foreach ($revision_ids as &$revision_id) {
++              $revision_id = (int) $revision_id;
++            }
++          }
++          else {
++            $revision_ids = (int) $revision_ids;
++          }
+         }
+-      }
+-      $query->fields('revision', $entity_revision_fields);
+ 
+-      // Compare revision ID of the base and revision table, if equal then this
+-      // is the default revision.
+-      $query->addExpression('CASE [base].[' . $this->revisionKey . '] WHEN [revision].[' . $this->revisionKey . '] THEN 1 ELSE 0 END', 'isDefaultRevision');
++        $all_revisions_table = $this->getJsonStorageAllRevisionsTable();
++        $query->condition("base.$all_revisions_table.{$this->revisionKey}", $revision_ids, 'IN');
++      }
+     }
++    else {
++      if ($revision_ids) {
++        $query->join($this->revisionTable, 'revision',
++          $query->joinCondition()
++            ->compare("revision.{$this->idKey}", "base.{$this->idKey}")
++            ->condition("revision.{$this->revisionKey}", $revision_ids, 'IN')
++        );
++      }
++      elseif ($this->revisionTable) {
++        $query->join($this->revisionTable, 'revision', $query->joinCondition()->compare("revision.{$this->revisionKey}", "base.{$this->revisionKey}"));
++      }
++
++      // Add fields from the {entity} table.
++      $table_mapping = $this->getTableMapping();
++      $entity_fields = $table_mapping->getAllColumns($this->baseTable);
+ 
+-    $query->fields('base', $entity_fields);
++      if ($this->revisionTable) {
++        // Add all fields from the {entity_revision} table.
++        $entity_revision_fields = $table_mapping->getAllColumns($this->revisionTable);
++        $entity_revision_fields = array_combine($entity_revision_fields, $entity_revision_fields);
++        // The ID field is provided by entity, so remove it.
++        unset($entity_revision_fields[$this->idKey]);
++
++        // Remove all fields from the base table that are also fields by the same
++        // name in the revision table.
++        $entity_field_keys = array_flip($entity_fields);
++        foreach ($entity_revision_fields as $name) {
++          if (isset($entity_field_keys[$name])) {
++            unset($entity_fields[$entity_field_keys[$name]]);
++          }
++        }
++        $query->fields('revision', $entity_revision_fields);
++
++        // Compare revision ID of the base and revision table, if equal then this
++        // is the default revision.
++        $query->addExpression('CASE [base].[' . $this->revisionKey . '] WHEN [revision].[' . $this->revisionKey . '] THEN 1 ELSE 0 END', 'isDefaultRevision');
++      }
+ 
+-    if ($ids) {
+-      $query->condition("base.{$this->idKey}", $ids, 'IN');
++      $query->fields('base', $entity_fields);
++
++      if ($ids) {
++        $query->condition("base.{$this->idKey}", $ids, 'IN');
++      }
+     }
+ 
+     return $query;
+@@ -748,16 +1367,34 @@ public function delete(array $entities) {
+     }
+ 
+     try {
+-      $transaction = $this->database->startTransaction();
++      if ($this->database->driver() == 'mongodb') {
++        $session = $this->database->getMongodbSession();
++        $session_started = FALSE;
++        if (!$session->isInTransaction()) {
++          $session->startTransaction();
++          $session_started = TRUE;
++        }
++      }
++      else {
++        $transaction = $this->database->startTransaction();
++      }
++
+       parent::delete($entities);
+ 
+       // Ignore replica server temporarily.
+       \Drupal::service('database.replica_kill_switch')->trigger();
++
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->commitTransaction();
++      }
+     }
+     catch (\Exception $e) {
+       if (isset($transaction)) {
+         $transaction->rollBack();
+       }
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->abortTransaction();
++      }
+       Error::logException(\Drupal::logger($this->entityTypeId), $e);
+       throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
+     }
+@@ -773,26 +1410,28 @@ protected function doDeleteFieldItems($entities) {
+       ->condition($this->idKey, $ids, 'IN')
+       ->execute();
+ 
+-    if ($this->revisionTable) {
+-      $this->database->delete($this->revisionTable)
+-        ->condition($this->idKey, $ids, 'IN')
+-        ->execute();
+-    }
++    if ($this->database->driver() != 'mongodb') {
++      if ($this->revisionTable) {
++        $this->database->delete($this->revisionTable)
++          ->condition($this->idKey, $ids, 'IN')
++          ->execute();
++      }
+ 
+-    if ($this->dataTable) {
+-      $this->database->delete($this->dataTable)
+-        ->condition($this->idKey, $ids, 'IN')
+-        ->execute();
+-    }
++      if ($this->dataTable) {
++        $this->database->delete($this->dataTable)
++          ->condition($this->idKey, $ids, 'IN')
++          ->execute();
++      }
+ 
+-    if ($this->revisionDataTable) {
+-      $this->database->delete($this->revisionDataTable)
+-        ->condition($this->idKey, $ids, 'IN')
+-        ->execute();
+-    }
++      if ($this->revisionDataTable) {
++        $this->database->delete($this->revisionDataTable)
++          ->condition($this->idKey, $ids, 'IN')
++          ->execute();
++      }
+ 
+-    foreach ($entities as $entity) {
+-      $this->deleteFromDedicatedTables($entity);
++      foreach ($entities as $entity) {
++        $this->deleteFromDedicatedTables($entity);
++      }
+     }
+   }
+ 
+@@ -800,20 +1439,50 @@ protected function doDeleteFieldItems($entities) {
+    * {@inheritdoc}
+    */
+   public function save(EntityInterface $entity) {
+-    try {
+-      $transaction = $this->database->startTransaction();
+-      $return = parent::save($entity);
+-
+-      // Ignore replica server temporarily.
+-      \Drupal::service('database.replica_kill_switch')->trigger();
+-      return $return;
++    if ($this->database->driver() == 'mongodb') {
++      try {
++        return parent::save($entity);
++      }
++      catch (\Exception $e) {
++        Error::logException(\Drupal::logger($this->entityTypeId), $e);
++        throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
++      }
+     }
+-    catch (\Exception $e) {
+-      if (isset($transaction)) {
+-        $transaction->rollBack();
++    else {
++      try {
++        if ($this->database->driver() == 'mongodb') {
++          $session = $this->database->getMongodbSession();
++          $session_started = FALSE;
++          if (!$session->isInTransaction()) {
++            $session->startTransaction();
++            $session_started = TRUE;
++          }
++        }
++        else {
++          $transaction = $this->database->startTransaction();
++        }
++
++        $return = parent::save($entity);
++
++        // Ignore replica server temporarily.
++        \Drupal::service('database.replica_kill_switch')->trigger();
++
++        if (isset($session) && $session->isInTransaction() && $session_started) {
++          $session->commitTransaction();
++        }
++
++        return $return;
++      }
++      catch (\Exception $e) {
++        if (isset($transaction)) {
++          $transaction->rollBack();
++        }
++        if (isset($session) && $session->isInTransaction() && $session_started) {
++          $session->abortTransaction();
++        }
++        Error::logException(\Drupal::logger($this->entityTypeId), $e);
++        throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
+       }
+-      Error::logException(\Drupal::logger($this->entityTypeId), $e);
+-      throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
+     }
+   }
+ 
+@@ -822,7 +1491,18 @@ public function save(EntityInterface $entity) {
+    */
+   public function restore(EntityInterface $entity) {
+     try {
+-      $transaction = $this->database->startTransaction();
++      if ($this->database->driver() == 'mongodb') {
++        $session = $this->database->getMongodbSession();
++        $session_started = FALSE;
++        if (!$session->isInTransaction()) {
++          $session->startTransaction();
++          $session_started = TRUE;
++        }
++      }
++      else {
++        $transaction = $this->database->startTransaction();
++      }
++
+       // Insert the entity data in the base and data tables only for default
+       // revisions.
+       /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+@@ -846,127 +1526,749 @@ public function restore(EntityInterface $entity) {
+           ->fields((array) $record)
+           ->execute();
+ 
+-        if ($this->revisionDataTable) {
+-          $this->saveToSharedTables($entity, $this->revisionDataTable);
+-        }
++        if ($this->revisionDataTable) {
++          $this->saveToSharedTables($entity, $this->revisionDataTable);
++        }
++      }
++
++      // Insert the entity data in the dedicated tables.
++      $this->saveToDedicatedTables($entity, FALSE, []);
++
++      // Ignore replica server temporarily.
++      \Drupal::service('database.replica_kill_switch')->trigger();
++
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->commitTransaction();
++      }
++    }
++    catch (\Exception $e) {
++      if (isset($transaction)) {
++        $transaction->rollBack();
++      }
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->abortTransaction();
++      }
++      Error::logException(\Drupal::logger($this->entityTypeId), $e);
++      throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
++    }
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
++    $full_save = empty($names);
++    $update = !$full_save || !$entity->isNew();
++
++    if ($this->database->driver() == 'mongodb') {
++      // MongoDB does not support auto-increments fields. So we need to add them
++      // ourselves.
++      if ($entity->id() === NULL) {
++        $entity->set($this->idKey, $this->getMongoSequences()->nextEntityId($this->baseTable));
++      }
++
++      if ($this->entityType->isRevisionable() && $entity->isNewRevision()) {
++        if ($entity->getRevisionId() === NULL) {
++          $entity->set($this->entityType->getKey('revision'), $this->getMongoSequences()->nextRevisionId($this->baseTable));
++        }
++        else {
++          // Make sure that the revision_id is not already in use.
++          if ($this->loadRevision($entity->getRevisionId())) {
++            $entity->set($this->entityType->getKey('revision'), $this->getMongoSequences()->nextRevisionId($this->baseTable));
++          }
++
++          if ($this->getMongoSequences()->currentRevisionId($this->baseTable) < $entity->getRevisionId()) {
++            $this->getMongoSequences()->setRevisionId($this->baseTable, $entity->getRevisionId());
++          }
++        }
++      }
++
++      // Get the current revision ID, so that it can be set correctly in the base
++      // table.
++      if ($this->entityType->isRevisionable() && !$entity->isDefaultRevision()) {
++        $entity_id = $entity->id();
++        if (is_int($entity_id) || ctype_digit($entity_id)) {
++          $entity_id = (int) $entity_id;
++        }
++        $result = $this->database->select($this->baseTable)
++          ->fields($this->baseTable, [$this->jsonStorageCurrentRevisionTable])
++          ->condition($this->idKey, $entity_id)
++          ->execute()
++          ->fetchCol();
++        foreach ($result as $current_revisions) {
++          foreach ($current_revisions as $current_revision) {
++            if (isset($current_revision[$this->revisionKey])) {
++              $current_revision_id = $current_revision[$this->revisionKey];
++            }
++          }
++        }
++      }
++
++      if ($this->entityType->isTranslatable() && empty($entity->get($this->langcodeKey)->value)) {
++        $entity->set($this->langcodeKey, $entity->language()->getId());
++      }
++
++      $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->baseTable);
++      $fields = (array) $record;
++
++      if ($update) {
++        $query = $this->database->update($this->baseTable)->condition($this->idKey, $record->{$this->idKey});
++      }
++      else {
++        $query = $this->database->insert($this->baseTable);
++      }
++
++      $embedded_tables = [];
++      if ($this->jsonStorageAllRevisionsTable) {
++        $embedded_tables[] = ['table' => $this->jsonStorageAllRevisionsTable, 'update action' => 'append'];
++      }
++      // Not sure about the change on the next line. It fixes the EntityDuplicateTest.
++      if ($this->jsonStorageCurrentRevisionTable && ($entity->isDefaultRevision() || ($entity->getRevisionId() == $entity->getLoadedRevisionId()))) {
++        $embedded_tables[] = ['table' => $this->jsonStorageCurrentRevisionTable, 'update action' => 'replace'];
++      }
++      if ($this->jsonStorageLatestRevisionTable && ($entity->isNewRevision() || ($entity->getRevisionId() >= $this->getLatestRevisionId($entity->id())))) {
++        $embedded_tables[] = ['table' => $this->jsonStorageLatestRevisionTable, 'update action' => 'replace'];
++      }
++      if ($this->jsonStorageTranslationsTable) {
++        $embedded_tables[] = ['table' => $this->jsonStorageTranslationsTable, 'update action' => 'replace'];
++      }
++
++      // Get the dedicated table data for the all revisions, current revision,
++      // latest revision and translations tables.
++      $records_allDedicatedTables = $this->getEmbeddedDedicatedTablesRecords($entity, $names);
++      if (empty($embedded_tables)) {
++        // Get the dedicated table data for the base table.
++        $records_dedicatedTables = isset($records_allDedicatedTables[$this->baseTable]) && is_array($records_allDedicatedTables[$this->baseTable]) ? $records_allDedicatedTables[$this->baseTable] : [];
++
++        $record_baseTable = (array) $record;
++        foreach ($records_dedicatedTables as $dedicated_table_name => $records_dedicatedTable) {
++          $record_baseTable[$dedicated_table_name] = NULL;
++          foreach ($records_dedicatedTable as $record_dedicatedTable) {
++            // The base table idKey does not have to be off the same type as the
++            // dedicated table entity_id (integer vs. string).
++            if (($record_baseTable[$this->idKey] == $record_dedicatedTable['entity_id']) &&
++              (empty($this->bundleKey) || ($record_baseTable[$this->bundleKey] === $record_dedicatedTable['bundle'])) &&
++              (empty($this->langcodeKey) || ($record_baseTable[$this->langcodeKey] === $record_dedicatedTable['langcode']))) {
++              if (!$record_baseTable[$dedicated_table_name] instanceof EmbeddedTableData) {
++                $record_baseTable[$dedicated_table_name] = $query->embeddedTableData('replace')->fields($record_dedicatedTable);
++              }
++              else {
++                $record_baseTable[$dedicated_table_name]->values($record_dedicatedTable);
++              }
++            }
++          }
++          $fields[$dedicated_table_name] = $record_baseTable[$dedicated_table_name] ?? NULL;
++        }
++
++        // Dedicated fields with no values set must be set to NULL.
++        $dedicated_table_names = $this->getEmbeddedDedicatedTableNames($entity, $names);
++        if (is_array($dedicated_table_names[$this->baseTable])) {
++          foreach ($dedicated_table_names[$this->baseTable] as $dedicated_table_name) {
++            if (!isset($fields[$dedicated_table_name])) {
++              $fields[$dedicated_table_name] = NULL;
++            }
++          }
++        }
++      }
++      else {
++        foreach ($embedded_tables as $embedded_table) {
++          $embedded_table_name = $embedded_table['table'];
++
++          // Get the dedicated table data for the embedded table.
++          $records_dedicatedTables = isset($records_allDedicatedTables[$embedded_table_name]) && is_array($records_allDedicatedTables[$embedded_table_name]) ? $records_allDedicatedTables[$embedded_table_name] : [];
++
++          // Get the embedded table data.
++          $records_embeddedTable = $this->getEmbeddedTableRecords($entity, $embedded_table_name);
++
++          $data_embeddedTable = NULL;
++          foreach ($records_embeddedTable as $record_embeddedTable) {
++            // Add the dedicated table data to the embedded table row data.
++            foreach ($records_dedicatedTables as $dedicated_table_name => $records_dedicatedTable) {
++              $record_embeddedTable[$dedicated_table_name] = NULL;
++              foreach ($records_dedicatedTable as $record_dedicatedTable) {
++                // The base table idKey does not have to be off the same type as
++                // the dedicated table entity_id (integer vs. string).
++                if ((empty($this->revisionKey) || ($record_embeddedTable[$this->revisionKey] == $record_dedicatedTable['revision_id'])) &&
++                  (empty($this->bundleKey) || ($record_embeddedTable[$this->bundleKey] === $record_dedicatedTable['bundle'])) &&
++                  (empty($this->langcodeKey) || ($record_embeddedTable[$this->langcodeKey] === $record_dedicatedTable['langcode']))) {
++                  if (!$record_embeddedTable[$dedicated_table_name] instanceof EmbeddedTableData) {
++                    $record_embeddedTable[$dedicated_table_name] = $query->embeddedTableData()->fields($record_dedicatedTable);
++                  }
++                  else {
++                    $record_embeddedTable[$dedicated_table_name]->values($record_dedicatedTable);
++                  }
++                }
++              }
++            }
++
++            // Create the embedded table rows.
++            if (!$data_embeddedTable instanceof EmbeddedTableData) {
++              if ($update && $embedded_table['update action'] === 'append') {
++                $action = 'append';
++              }
++              elseif ($update && $embedded_table['update action'] === 'replace') {
++                $action = 'replace';
++              }
++              else {
++                $action = '';
++              }
++              $data_embeddedTable = $query->embeddedTableData($action)->fields($record_embeddedTable);
++            }
++            else {
++              $data_embeddedTable->values($record_embeddedTable);
++            }
++          }
++          $fields[$embedded_table_name] = $data_embeddedTable ?? NULL;
++        }
++      }
++
++      if ($update) {
++        // Make sure that the revision_id in the base table has the value of the
++        // current revision.
++        if (!empty($this->revisionKey) && !empty($fields[$this->revisionKey]) && !empty($current_revision_id)) {
++          $fields[$this->revisionKey] = (int) $current_revision_id;
++        }
++
++        $query->fields($fields);
++        $query->execute();
++
++        if ($this->entityType->isRevisionable()) {
++          // When updating an entity with revisions and without creating a new
++          // revision creates a problem with MongoDB. The embedded table holding
++          // all the revision data can on update do only one change to the
++          // embedded table data. The new revision data is added to the embedded
++          // table data. In the embedded table holding the all revision data
++          // there are now two sets of revision data for the same revision. When
++          // querying the entity for revision data the query will fail, because
++          // there are two sets of revision data. The older revision data needs
++          // to be removed.
++          $this->cleanupEntityAllRevisionData($entity->id());
++        }
++      }
++      else {
++        $query->fields($fields);
++        $insert_id = $query->execute();
++
++        // Even if this is a new entity the ID key might have been set, in which
++        // case we should not override the provided ID. An ID key that is not set
++        // to any value is interpreted as NULL (or DEFAULT) and thus overridden.
++        if (!isset($record->{$this->idKey})) {
++          $record->{$this->idKey} = $insert_id;
++        }
++        $entity->{$this->idKey} = (string) $record->{$this->idKey};
++      }
++    }
++    else {
++      if ($full_save) {
++        $shared_table_fields = TRUE;
++        $dedicated_table_fields = TRUE;
++      }
++      else {
++        $table_mapping = $this->getTableMapping();
++        $shared_table_fields = FALSE;
++        $dedicated_table_fields = [];
++
++        // Collect the name of fields to be written in dedicated tables and check
++        // whether shared table records need to be updated.
++        foreach ($names as $name) {
++          $storage_definition = $this->fieldStorageDefinitions[$name];
++          if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
++            $shared_table_fields = TRUE;
++          }
++          elseif ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
++            $dedicated_table_fields[] = $name;
++          }
++        }
++      }
++
++      // Update shared table records if necessary.
++      if ($shared_table_fields) {
++        $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->baseTable);
++        // Create the storage record to be saved.
++        if ($update) {
++          $default_revision = $entity->isDefaultRevision();
++          if ($default_revision) {
++            $id = $record->{$this->idKey};
++            // Remove the ID from the record to enable updates on SQL variants
++            // that prevent updating serial columns, for example, mssql.
++            unset($record->{$this->idKey});
++            $this->database
++              ->update($this->baseTable)
++              ->fields((array) $record)
++              ->condition($this->idKey, $id)
++              ->execute();
++          }
++          if ($this->revisionTable) {
++            if ($full_save) {
++              $entity->{$this->revisionKey} = $this->saveRevision($entity);
++            }
++            else {
++              $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable);
++              // Remove the revision ID from the record to enable updates on SQL
++              // variants that prevent updating serial columns, for example,
++              // mssql.
++              unset($record->{$this->revisionKey});
++              $entity->preSaveRevision($this, $record);
++              $this->database
++                ->update($this->revisionTable)
++                ->fields((array) $record)
++                ->condition($this->revisionKey, $entity->getRevisionId())
++                ->execute();
++            }
++          }
++          if ($default_revision && $this->dataTable) {
++            $this->saveToSharedTables($entity);
++          }
++          if ($this->revisionDataTable) {
++            $new_revision = $full_save && $entity->isNewRevision();
++            $this->saveToSharedTables($entity, $this->revisionDataTable, $new_revision);
++          }
++        }
++        else {
++          $insert_id = $this->database
++            ->insert($this->baseTable)
++            ->fields((array) $record)
++            ->execute();
++          // Even if this is a new entity the ID key might have been set, in which
++          // case we should not override the provided ID. An ID key that is not set
++          // to any value is interpreted as NULL (or DEFAULT) and thus overridden.
++          if (!isset($record->{$this->idKey})) {
++            $record->{$this->idKey} = $insert_id;
++          }
++          $entity->{$this->idKey} = (string) $record->{$this->idKey};
++          if ($this->revisionTable) {
++            $record->{$this->revisionKey} = $this->saveRevision($entity);
++          }
++          if ($this->dataTable) {
++            $this->saveToSharedTables($entity);
++          }
++          if ($this->revisionDataTable) {
++            $this->saveToSharedTables($entity, $this->revisionDataTable);
++          }
++        }
++      }
++
++      // Update dedicated table records if necessary.
++      if ($dedicated_table_fields) {
++        $names = is_array($dedicated_table_fields) ? $dedicated_table_fields : [];
++        $this->saveToDedicatedTables($entity, $update, $names);
++      }
++    }
++  }
++
++  /**
++   * Helper method for getting the latest revision ID.
++   */
++  public function getLatestRevisionId($entity_id) {
++    if (!$this->entityType->isRevisionable()) {
++      return NULL;
++    }
++
++    if (!isset($this->latestRevisionIds[$entity_id][LanguageInterface::LANGCODE_DEFAULT])) {
++      // Create for MongoDB a specific implementation for getting the latest
++      // revision id. MongoDB stores all revision data in a single document/row.
++      // As such there is no need for an aggregate query.
++      $all_revisions = $this->database->select($this->getBaseTable(), 't')
++        ->fields('t', [$this->jsonStorageAllRevisionsTable])
++        ->condition($this->entityType->getKey('id'), (int) $entity_id)
++        ->execute()
++        ->fetchField();
++
++      $latest_revision_id = 0;
++      $revision_key = $this->entityType->getKey('revision');
++      if (!empty($all_revisions) && is_array($all_revisions)) {
++        foreach ($all_revisions as $revision) {
++          if (isset($revision[$revision_key]) && ($revision[$revision_key] > $latest_revision_id)) {
++            $latest_revision_id = $revision[$revision_key];
++          }
++        }
++      }
++
++      $this->latestRevisionIds[$entity_id][LanguageInterface::LANGCODE_DEFAULT] = $latest_revision_id;
++    }
++
++    return $this->latestRevisionIds[$entity_id][LanguageInterface::LANGCODE_DEFAULT];
++  }
++
++  /**
++   * Removes the unneeded revisions from the all_revisions table.
++   *
++   * @param string|int $entity_id
++   *   The table name to save to. Defaults to the data table.
++   */
++  protected function cleanupEntityAllRevisionData($entity_id) {
++    try {
++      // Only do this if the entity is revisionable.
++      if ($this->entityType->isRevisionable()) {
++        $table_mapping = $this->getTableMapping();
++        // Get the field name for the default revision field.
++        $revision_default_field = $table_mapping->getColumnNames($this->entityType->getRevisionMetadataKey('revision_default'))['value'];
++
++        // Make sure that the entity_id is of the correct type (integer or string).
++        $base_table_entity_id_data = $this->database->tableInformation()->getTableField($this->baseTable, $this->idKey);
++        if (isset($base_table_entity_id_data['type']) && in_array($base_table_entity_id_data['type'], ['int', 'serial'])) {
++          $entity_id = (int) $entity_id;
++        }
++        else {
++          $entity_id = (string) $entity_id;
++        }
++
++        // Get the non-revisionable (translatable and non-translatable) fields.
++        $non_revisionable_translatable_field_names = [];
++        $non_revisionable_non_translatable_field_names = [];
++        foreach ($this->fieldStorageDefinitions as $field_name => $field_definition) {
++          if (!$field_definition->isRevisionable() && !in_array($field_name, [$this->idKey, $this->revisionKey, $this->uuidKey, $this->bundleKey], TRUE)) {
++            if ($field_definition->isTranslatable()) {
++              $non_revisionable_translatable_field_names[] = $field_name;
++            }
++            else {
++              $non_revisionable_non_translatable_field_names[] = $field_name;
++            }
++          }
++        }
++
++        $prefixed_table = $this->database->getPrefix() . $this->baseTable;
++        $entity_data = $this->database->getConnection()->selectCollection($prefixed_table)->findOne(
++          [$this->idKey => ['$eq' => $entity_id]],
++          [
++            'projection' => [$this->jsonStorageAllRevisionsTable => 1, $this->jsonStorageCurrentRevisionTable => 1],
++            'session' => $this->database->getMongodbSession(),
++          ],
++        );
++
++        $non_revisionable_non_translatable_field_data = [];
++        $non_revisionable_translatable_field_data = [];
++        if (isset($entity_data->{$this->jsonStorageCurrentRevisionTable})) {
++          $current_revision_data = (array) $entity_data->{$this->jsonStorageCurrentRevisionTable};
++          foreach ($current_revision_data as $revision) {
++            // Get the current revision id for setting the default revision field.
++            if (isset($revision->{$this->revisionKey})) {
++              $current_revision_id = $revision->{$this->revisionKey};
++            }
++
++            // Get the non-revisionable non-translatable field values from the
++            // current revision.
++            foreach ($non_revisionable_non_translatable_field_names as $non_revisionable_non_translatable_field_name) {
++              if (isset($revision->{$non_revisionable_non_translatable_field_name})) {
++                $non_revisionable_non_translatable_field_data[$non_revisionable_non_translatable_field_name] = $revision->{$non_revisionable_non_translatable_field_name};
++              }
++            }
++
++            // Get the non-revisionable translatable field values from the
++            // current revision.
++            foreach ($non_revisionable_translatable_field_names as $non_revisionable_translatable_field_name) {
++              if (isset($revision->{$non_revisionable_translatable_field_name}) && isset($revision->{$this->langcodeKey})) {
++                if (!isset($non_revisionable_translatable_field_data[$non_revisionable_translatable_field_name])) {
++                  $non_revisionable_translatable_field_data[$non_revisionable_translatable_field_name] = [];
++                }
++                $non_revisionable_translatable_field_data[$non_revisionable_translatable_field_name][$revision->{$this->langcodeKey}] = $revision->{$non_revisionable_translatable_field_name};
++              }
++            }
++          }
++        }
++
++        $revisions_langcodes = [];
++        $new_all_revisions_data = [];
++        if (isset($entity_data->{$this->jsonStorageAllRevisionsTable})) {
++          $all_revisions_data = (array) $entity_data->{$this->jsonStorageAllRevisionsTable};
++          $all_revisions_data = array_reverse($all_revisions_data);
++          foreach ($all_revisions_data as $revision) {
++            // Update the values of non-revisionable non-translatable fields
++            // for all existing revisions.
++            foreach ($non_revisionable_non_translatable_field_data as $non_revisionable_non_translatable_field_name => $non_revisionable_non_translatable_field_value) {
++              $revision->{$non_revisionable_non_translatable_field_name} = $non_revisionable_non_translatable_field_value;
++            }
++
++            // @todo We got no testing for this.
++            // Update the values of non-revisionable translatable fields for
++            // all existing revisions.
++            foreach ($non_revisionable_translatable_field_data as $non_revisionable_translatable_field_name => $non_revisionable_translatable_field_values) {
++              if (isset($non_revisionable_translatable_field_values[$revision->{$this->langcodeKey}])) {
++                $revision->{$non_revisionable_translatable_field_name} = $non_revisionable_translatable_field_values[$revision->{$this->langcodeKey}];
++              }
++            }
++
++            $exists = FALSE;
++            foreach ($revisions_langcodes as $revision_langcode) {
++              if ($this->entityType->isTranslatable()) {
++                if (($revision_langcode['revision_id'] == $revision->{$this->revisionKey}) && ($revision_langcode['langcode'] == $revision->{$this->langcodeKey})) {
++                  $exists = TRUE;
++                }
++              }
++              else {
++                if (($revision_langcode['revision_id'] == $revision->{$this->revisionKey})) {
++                  $exists = TRUE;
++                }
++              }
++              if ($current_revision_id && isset($revision->{$this->revisionKey}) && isset($revision->{$revision_default_field})) {
++                if ($revision->{$this->revisionKey} == $current_revision_id) {
++                  $revision->{$revision_default_field} = TRUE;
++                }
++                else {
++                  // All revisions that are not the current revision should have
++                  // set the value of "revision_default" to FALSE.
++                  $revision->{$revision_default_field} = FALSE;
++                }
++              }
++            }
++            if (!$exists) {
++              $revisions_langcodes[] = [
++                'revision_id' => $revision->{$this->revisionKey},
++                'langcode' => $revision->{$this->langcodeKey} ?? 'und',
++              ];
++              $new_all_revisions_data[] = clone $revision;
++            }
++          }
++        }
++
++        $new_all_revisions_data = array_reverse($new_all_revisions_data);
++
++        $set = [];
++        $set[$this->jsonStorageAllRevisionsTable] = $new_all_revisions_data;
++        if (isset($current_revision_id)) {
++          $set[$this->revisionKey] = $current_revision_id;
++          // $this->entityKeys[$this->revisionKey] = $current_revision_id;
++        }
++
++        $this->database->getConnection()->selectCollection($prefixed_table)->updateOne(
++          [$this->idKey => ['$eq' => $entity_id]],
++          ['$set' => $set],
++          ['session' => $this->database->getMongodbSession()],
++        );
++      }
++    }
++    catch (\Exception) {
++      // Throw exception that we could not load the entity.
++    }
++  }
++
++  /**
++   * Get the fields to be saved from the embedded tables.
++   *
++   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
++   *   The entity object.
++   * @param string $table_name
++   *   The table name to save to. Defaults to the data table.
++   *
++   * @return array
++   *   The records to store for the shared table
++   */
++  protected function getEmbeddedTableRecords(ContentEntityInterface $entity, $table_name) {
++    $records = [];
++    foreach ($entity->getTranslationLanguages() as $langcode => $language) {
++      $translation = $entity->getTranslation($langcode);
++      $records[] = (array) $this->mapToStorageRecord($translation, $table_name);
++    }
++
++    return $records;
++  }
++
++  /**
++   * Get the fields to be saved from the embedded dedicated tables.
++   *
++   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
++   *   The entity object.
++   * @param string $names
++   *   The table names to save to. Defaults to the data table.
++   *
++   * @return array
++   *   The records to store for the shared table
++   */
++  protected function getEmbeddedDedicatedTableNames(ContentEntityInterface $entity, $names = []) {
++    $bundle = $entity->bundle();
++    $entity_type = $entity->getEntityTypeId();
++    $table_mapping = $this->getTableMapping();
++    $original = !empty($entity->original) ? $entity->original : NULL;
++
++    // Determine which fields should be actually stored.
++    $definitions = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
++    if ($names) {
++      $definitions = array_intersect_key($definitions, array_flip($names));
++    }
++
++    $dedicated_table_names = [];
++    if ($this->jsonStorageAllRevisionsTable) {
++      $dedicated_table_names[$this->jsonStorageAllRevisionsTable] = [];
++    }
++    if ($this->jsonStorageCurrentRevisionTable) {
++      $dedicated_table_names[$this->jsonStorageCurrentRevisionTable] = [];
++    }
++    if ($this->jsonStorageLatestRevisionTable) {
++      $dedicated_table_names[$this->jsonStorageLatestRevisionTable] = [];
++    }
++    if ($this->jsonStorageTranslationsTable) {
++      $dedicated_table_names[$this->jsonStorageTranslationsTable] = [];
++    }
++    if (!$this->jsonStorageAllRevisionsTable && !$this->jsonStorageCurrentRevisionTable && !$this->jsonStorageLatestRevisionTable && !$this->jsonStorageTranslationsTable) {
++      $dedicated_table_names[$this->baseTable] = [];
++    }
++
++    foreach ($definitions as $field_definition) {
++      $storage_definition = $field_definition->getFieldStorageDefinition();
++      if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
++        continue;
++      }
++
++      // @todo Test if the code that is below can be deleted.
++      // When updating an existing revision, keep the existing records if the
++      // field values did not change.
++      if (!$entity->isNewRevision() && $original && !$this->hasFieldValueChanged($field_definition, $entity, $original)) {
++        continue;
++      }
++
++      if ($this->jsonStorageAllRevisionsTable) {
++        $dedicated_table_names[$this->jsonStorageAllRevisionsTable][] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageAllRevisionsTable);
++      }
++
++      if ($this->jsonStorageCurrentRevisionTable) {
++        $dedicated_table_names[$this->jsonStorageCurrentRevisionTable][] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageCurrentRevisionTable);
++      }
++
++      if ($this->jsonStorageLatestRevisionTable) {
++        $dedicated_table_names[$this->jsonStorageLatestRevisionTable][] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageLatestRevisionTable);
+       }
+ 
+-      // Insert the entity data in the dedicated tables.
+-      $this->saveToDedicatedTables($entity, FALSE, []);
++      if ($this->jsonStorageTranslationsTable) {
++        $dedicated_table_names[$this->jsonStorageTranslationsTable][] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageTranslationsTable);
++      }
+ 
+-      // Ignore replica server temporarily.
+-      \Drupal::service('database.replica_kill_switch')->trigger();
+-    }
+-    catch (\Exception $e) {
+-      if (isset($transaction)) {
+-        $transaction->rollBack();
++      if (!$this->jsonStorageAllRevisionsTable && !$this->jsonStorageCurrentRevisionTable && !$this->jsonStorageLatestRevisionTable && !$this->jsonStorageTranslationsTable) {
++        $dedicated_table_names[$this->baseTable][] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->baseTable);
+       }
+-      Error::logException(\Drupal::logger($this->entityTypeId), $e);
+-      throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
+     }
++
++    return $dedicated_table_names;
+   }
+ 
+   /**
+-   * {@inheritdoc}
++   * Saves values of fields that use embedded dedicated tables.
++   *
++   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
++   *   The entity.
++   * @param string[] $names
++   *   (optional) The names of the fields to be stored. Defaults to all the
++   *   available fields.
+    */
+-  protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
+-    $full_save = empty($names);
+-    $update = !$full_save || !$entity->isNew();
++  protected function getEmbeddedDedicatedTablesRecords(ContentEntityInterface $entity, $names = []) {
++    $vid = $entity->getRevisionId();
++    $id = $entity->id();
++    $bundle = $entity->bundle();
++    $entity_type = $entity->getEntityTypeId();
++    $translation_langcodes = array_keys($entity->getTranslationLanguages());
++    $table_mapping = $this->getTableMapping();
++
++    if (!isset($vid)) {
++      $vid = $id;
++    }
+ 
+-    if ($full_save) {
+-      $shared_table_fields = TRUE;
+-      $dedicated_table_fields = TRUE;
++    // Determine which fields should be actually stored.
++    $definitions = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
++    if ($names) {
++      $definitions = array_intersect_key($definitions, array_flip($names));
+     }
+-    else {
+-      $table_mapping = $this->getTableMapping();
+-      $shared_table_fields = FALSE;
+-      $dedicated_table_fields = [];
+ 
+-      // Collect the name of fields to be written in dedicated tables and check
+-      // whether shared table records need to be updated.
+-      foreach ($names as $name) {
+-        $storage_definition = $this->fieldStorageDefinitions[$name];
+-        if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
+-          $shared_table_fields = TRUE;
+-        }
+-        elseif ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+-          $dedicated_table_fields[] = $name;
+-        }
+-      }
++    $records = [];
++
++    if ($this->jsonStorageAllRevisionsTable) {
++      $records[$this->jsonStorageAllRevisionsTable] = [];
++    }
++    if ($this->jsonStorageCurrentRevisionTable) {
++      $records[$this->jsonStorageCurrentRevisionTable] = [];
++    }
++    if ($this->jsonStorageLatestRevisionTable) {
++      $records[$this->jsonStorageLatestRevisionTable] = [];
++    }
++    if ($this->jsonStorageTranslationsTable) {
++      $records[$this->jsonStorageTranslationsTable] = [];
++    }
++    if (!$this->jsonStorageAllRevisionsTable && !$this->jsonStorageCurrentRevisionTable && !$this->jsonStorageLatestRevisionTable && !$this->jsonStorageTranslationsTable) {
++      $records[$this->baseTable] = [];
+     }
+ 
+-    // Update shared table records if necessary.
+-    if ($shared_table_fields) {
+-      $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->baseTable);
+-      // Create the storage record to be saved.
+-      if ($update) {
+-        $default_revision = $entity->isDefaultRevision();
+-        if ($default_revision) {
+-          $id = $record->{$this->idKey};
+-          // Remove the ID from the record to enable updates on SQL variants
+-          // that prevent updating serial columns, for example, mssql.
+-          unset($record->{$this->idKey});
+-          $this->database
+-            ->update($this->baseTable)
+-            ->fields((array) $record)
+-            ->condition($this->idKey, $id)
+-            ->execute();
+-        }
+-        if ($this->revisionTable) {
+-          if ($full_save) {
+-            $entity->{$this->revisionKey} = $this->saveRevision($entity);
++    foreach ($definitions as $field_name => $field_definition) {
++      $storage_definition = $field_definition->getFieldStorageDefinition();
++      if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
++        continue;
++      }
++
++      $dedicated_all_revisions_table_name = NULL;
++      if ($this->jsonStorageAllRevisionsTable) {
++        $dedicated_all_revisions_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageAllRevisionsTable);
++      }
++
++      $dedicated_current_revision_table_name = NULL;
++      if ($this->jsonStorageCurrentRevisionTable) {
++        $dedicated_current_revision_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageCurrentRevisionTable);
++      }
++
++      $dedicated_latest_revision_table_name = NULL;
++      if ($this->jsonStorageLatestRevisionTable) {
++        $dedicated_latest_revision_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageLatestRevisionTable);
++      }
++
++      $dedicated_translations_table_name = NULL;
++      if ($this->jsonStorageTranslationsTable) {
++        $dedicated_translations_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->jsonStorageTranslationsTable);
++      }
++
++      $dedicated_base_table_name = NULL;
++      if (!$this->jsonStorageAllRevisionsTable && !$this->jsonStorageCurrentRevisionTable && !$this->jsonStorageLatestRevisionTable && !$this->jsonStorageTranslationsTable) {
++        $dedicated_base_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->baseTable);
++      }
++
++      // Prepare the multi-insert query.
++      $columns = ['entity_id', 'revision_id', 'bundle', 'delta', 'langcode'];
++      foreach ($storage_definition->getColumns() as $column => $attributes) {
++        $columns[] = $table_mapping->getFieldColumnName($storage_definition, $column);
++      }
++
++      // Save all non-translatable fields for all languages. They belong to
++      // every language. This is also needs for entity filter purposes.
++      foreach ($translation_langcodes as $langcode) {
++        $delta_count = 0;
++        $items = $entity->getTranslation($langcode)->get($field_name);
++        $items->filterEmptyItems();
++        foreach ($items as $delta => $item) {
++          // We now know we have something to insert.
++          $record = [
++            'entity_id' => $id,
++            'revision_id' => $vid,
++            'bundle' => $bundle,
++            'delta' => $delta,
++            'langcode' => $langcode,
++          ];
++          foreach ($storage_definition->getColumns() as $column => $attributes) {
++            $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
++            $value = $item->$column;
++            if (!empty($attributes['serialize'])) {
++              $value = serialize($value);
++            }
++            $record[$column_name] = SqlContentEntityStorageSchema::castValue($attributes, $value);
+           }
+-          else {
+-            $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable);
+-            // Remove the revision ID from the record to enable updates on SQL
+-            // variants that prevent updating serial columns, for example,
+-            // mssql.
+-            unset($record->{$this->revisionKey});
+-            $entity->preSaveRevision($this, $record);
+-            $this->database
+-              ->update($this->revisionTable)
+-              ->fields((array) $record)
+-              ->condition($this->revisionKey, $entity->getRevisionId())
+-              ->execute();
++          if (isset($records[$this->jsonStorageAllRevisionsTable]) && is_array($records[$this->jsonStorageAllRevisionsTable])) {
++            $records[$this->jsonStorageAllRevisionsTable][$dedicated_all_revisions_table_name][] = $record;
++          }
++          if (isset($records[$this->jsonStorageCurrentRevisionTable]) && is_array($records[$this->jsonStorageCurrentRevisionTable])) {
++            $records[$this->jsonStorageCurrentRevisionTable][$dedicated_current_revision_table_name][] = $record;
++          }
++          if (isset($records[$this->jsonStorageLatestRevisionTable]) && is_array($records[$this->jsonStorageLatestRevisionTable])) {
++            $records[$this->jsonStorageLatestRevisionTable][$dedicated_latest_revision_table_name][] = $record;
++          }
++          if (isset($records[$this->jsonStorageTranslationsTable]) && is_array($records[$this->jsonStorageTranslationsTable])) {
++            $records[$this->jsonStorageTranslationsTable][$dedicated_translations_table_name][] = $record;
++          }
++          if (isset($records[$this->baseTable]) && is_array($records[$this->baseTable])) {
++            $records[$this->baseTable][$dedicated_base_table_name][] = $record;
++          }
++
++          if ($storage_definition->getCardinality() != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && ++$delta_count == $storage_definition->getCardinality()) {
++            break;
+           }
+-        }
+-        if ($default_revision && $this->dataTable) {
+-          $this->saveToSharedTables($entity);
+-        }
+-        if ($this->revisionDataTable) {
+-          $new_revision = $full_save && $entity->isNewRevision();
+-          $this->saveToSharedTables($entity, $this->revisionDataTable, $new_revision);
+-        }
+-      }
+-      else {
+-        $insert_id = $this->database
+-          ->insert($this->baseTable)
+-          ->fields((array) $record)
+-          ->execute();
+-        // Even if this is a new entity the ID key might have been set, in which
+-        // case we should not override the provided ID. An ID key that is not set
+-        // to any value is interpreted as NULL (or DEFAULT) and thus overridden.
+-        if (!isset($record->{$this->idKey})) {
+-          $record->{$this->idKey} = $insert_id;
+-        }
+-        $entity->{$this->idKey} = (string) $record->{$this->idKey};
+-        if ($this->revisionTable) {
+-          $record->{$this->revisionKey} = $this->saveRevision($entity);
+-        }
+-        if ($this->dataTable) {
+-          $this->saveToSharedTables($entity);
+-        }
+-        if ($this->revisionDataTable) {
+-          $this->saveToSharedTables($entity, $this->revisionDataTable);
+         }
+       }
+     }
+ 
+-    // Update dedicated table records if necessary.
+-    if ($dedicated_table_fields) {
+-      $names = is_array($dedicated_table_fields) ? $dedicated_table_fields : [];
+-      $this->saveToDedicatedTables($entity, $update, $names);
+-    }
++    return $records;
+   }
+ 
+   /**
+@@ -1556,16 +2858,53 @@ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $
+   public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
+     $table_mapping = $this->getTableMapping();
+     if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+-      // Mark all data associated with the field for deletion.
+-      $table = $table_mapping->getDedicatedDataTableName($storage_definition);
+-      $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+-      $this->database->update($table)
+-        ->fields(['deleted' => 1])
+-        ->execute();
+-      if ($this->entityType->isRevisionable()) {
+-        $this->database->update($revision_table)
++      if ($this->database->driver() == 'mongodb') {
++        $revisionable = $this->entityType->isRevisionable();
++        $translatable = $this->entityType->isTranslatable();
++
++        $dedicated_tables = [];
++        if ($revisionable) {
++          $dedicated_tables[$this->getJsonStorageAllRevisionsTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageAllRevisionsTable());
++          $dedicated_tables[$this->getJsonStorageCurrentRevisionTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageCurrentRevisionTable());
++          $dedicated_tables[$this->getJsonStorageLatestRevisionTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageLatestRevisionTable());
++        }
++        if (!$revisionable && $translatable) {
++          $dedicated_tables[$this->getJsonStorageTranslationsTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageTranslationsTable());
++        }
++        if (!$revisionable && !$translatable) {
++          $dedicated_tables[$this->getBaseTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getBaseTable());
++        }
++
++        foreach ($dedicated_tables as $embedded_to_table => $dedicated_table) {
++          $prefixed_table = $this->database->getPrefix() . $this->getBaseTable();
++          if ($embedded_to_table == $this->getBaseTable()) {
++            $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++              ["$dedicated_table" => ['$exists' => TRUE]],
++              ['$set' => ["$dedicated_table.$[].deleted" => TRUE]],
++              ['session' => $this->database->getMongodbSession()],
++            );
++          }
++          else {
++            $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++              ["$embedded_to_table.$[].$dedicated_table" => ['$exists' => TRUE]],
++              ['$set' => ["$embedded_to_table.$[].$dedicated_table.$[].deleted" => TRUE]],
++              ['session' => $this->database->getMongodbSession()],
++            );
++          }
++        }
++      }
++      else {
++        // Mark all data associated with the field for deletion.
++        $table = $table_mapping->getDedicatedDataTableName($storage_definition);
++        $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
++        $this->database->update($table)
+           ->fields(['deleted' => 1])
+           ->execute();
++        if ($this->entityType->isRevisionable()) {
++          $this->database->update($revision_table)
++            ->fields(['deleted' => 1])
++            ->execute();
++        }
+       }
+     }
+ 
+@@ -1609,17 +2948,113 @@ public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definiti
+     $storage_definition = $field_definition->getFieldStorageDefinition();
+     // Mark field data as deleted.
+     if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+-      $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
+-      $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+-      $this->database->update($table_name)
+-        ->fields(['deleted' => 1])
+-        ->condition('bundle', $field_definition->getTargetBundle())
+-        ->execute();
+-      if ($this->entityType->isRevisionable()) {
+-        $this->database->update($revision_name)
++      if ($this->database->driver() == 'mongodb') {
++        $prefixed_table = $this->database->getPrefix() . $this->getBaseTable();
++
++        if ($this->entityType->isRevisionable()) {
++          $all_revisions_table = $this->getJsonStorageAllRevisionsTable();
++          $dedicated_all_revisions_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $all_revisions_table);
++          $current_revision_table = $this->getJsonStorageCurrentRevisionTable();
++          $dedicated_current_revision_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $current_revision_table);
++          $latest_revision_table = $this->getJsonStorageLatestRevisionTable();
++          $dedicated_latest_revision_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $latest_revision_table);
++
++          $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++            [
++              "$current_revision_table.$dedicated_current_revision_table" => ['$exists' => TRUE],
++            ],
++            [
++              '$set' => [
++                "$current_revision_table.$[].$dedicated_current_revision_table.$[field].deleted" => TRUE,
++              ],
++            ],
++            [
++              'arrayFilters' => [["field.bundle" => $field_definition->getTargetBundle()]],
++              'session' => $this->database->getMongodbSession(),
++            ],
++          );
++
++          $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++            [
++              "$latest_revision_table.$dedicated_latest_revision_table" => ['$exists' => TRUE],
++            ],
++            [
++              '$set' => [
++                "$latest_revision_table.$[].$dedicated_latest_revision_table.$[field].deleted" => TRUE,
++              ],
++            ],
++            [
++              'arrayFilters' => [["field.bundle" => $field_definition->getTargetBundle()]],
++              'session' => $this->database->getMongodbSession(),
++            ],
++          );
++
++          $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++            [],
++            [
++              '$set' => [
++                "$all_revisions_table.$[dedicated].$dedicated_all_revisions_table.$[].deleted" => TRUE,
++              ],
++            ],
++            [
++              'arrayFilters' => [
++                ["dedicated.$dedicated_all_revisions_table" => ['$exists' => TRUE]],
++              ],
++              'session' => $this->database->getMongodbSession(),
++            ],
++          );
++        }
++        elseif ($this->entityType->isTranslatable()) {
++          $translations_table = $this->getJsonStorageTranslationsTable();
++          $dedicated_translations_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $translations_table);
++
++          $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++            [
++              "$translations_table.$dedicated_translations_table" => ['$exists' => TRUE],
++            ],
++            [
++              '$set' => [
++                "$translations_table.$[].$dedicated_translations_table.$[field].deleted" => TRUE,
++              ],
++            ],
++            [
++              'arrayFilters' => [["field.bundle" => $field_definition->getTargetBundle()]],
++              'session' => $this->database->getMongodbSession(),
++            ],
++          );
++        }
++        else {
++          $base_table = $this->getBaseTable();
++          $dedicated_base_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $base_table);
++          $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++            [
++              $dedicated_base_table => ['$exists' => TRUE],
++            ],
++            [
++              '$set' => [
++                "$dedicated_base_table.$[field].deleted" => TRUE,
++              ],
++            ],
++            [
++              'arrayFilters' => [["field.bundle" => $field_definition->getTargetBundle()]],
++              'session' => $this->database->getMongodbSession(),
++            ],
++          );
++        }
++      }
++      else {
++        $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
++        $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
++        $this->database->update($table_name)
+           ->fields(['deleted' => 1])
+           ->condition('bundle', $field_definition->getTargetBundle())
+           ->execute();
++        if ($this->entityType->isRevisionable()) {
++          $this->database->update($revision_name)
++            ->fields(['deleted' => 1])
++            ->condition('bundle', $field_definition->getTargetBundle())
++            ->execute();
++        }
+       }
+     }
+   }
+@@ -1641,49 +3076,114 @@ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definit
+     // Check whether the whole field storage definition is gone, or just some
+     // bundle fields.
+     $storage_definition = $field_definition->getFieldStorageDefinition();
++    $is_deleted = $storage_definition->isDeleted();
+     $table_mapping = $this->getTableMapping();
+     $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $storage_definition->isDeleted());
+ 
+-    // Get the entities which we want to purge first.
+-    $entity_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC]);
+-    $or = $entity_query->orConditionGroup();
+-    foreach ($storage_definition->getColumns() as $column_name => $data) {
+-      $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
+-    }
+-    $entity_query
+-      ->distinct(TRUE)
+-      ->fields('t', ['entity_id'])
+-      ->condition('bundle', $field_definition->getTargetBundle())
+-      ->range(0, $batch_size);
+-
+     // Create a map of field data table column names to field column names.
+     $column_map = [];
+     foreach ($storage_definition->getColumns() as $column_name => $data) {
+       $column_map[$table_mapping->getFieldColumnName($storage_definition, $column_name)] = $column_name;
+     }
+ 
+-    $entities = [];
+-    $items_by_entity = [];
+-    foreach ($entity_query->execute() as $row) {
+-      $item_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC])
+-        ->fields('t')
+-        ->condition('entity_id', $row['entity_id'])
+-        ->condition('deleted', 1)
+-        ->orderBy('delta');
++    if ($this->database->driver() == 'mongodb') {
++      $dedicated_tables = [];
++      if ($this->entityType->isRevisionable()) {
++        $dedicated_tables[$this->getJsonStorageAllRevisionsTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageAllRevisionsTable(), $is_deleted);
++        $dedicated_tables[$this->getJsonStorageCurrentRevisionTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageCurrentRevisionTable(), $is_deleted);
++        $dedicated_tables[$this->getJsonStorageLatestRevisionTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageLatestRevisionTable(), $is_deleted);
++      }
++      elseif (!$this->entityType->isRevisionable() && $this->entityType->isTranslatable()) {
++        $dedicated_tables[$this->getJsonStorageTranslationsTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageTranslationsTable(), $is_deleted);
++      }
++      elseif (!$this->entityType->isRevisionable() && !$this->entityType->isTranslatable()) {
++        $dedicated_tables[$this->getBaseTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getBaseTable(), $is_deleted);
++      }
++
++      reset($dedicated_tables);
++      $embedded_to_table = key($dedicated_tables);
++      $dedicated_table = current($dedicated_tables);
++
++      // Get the entities which we want to purge first.
++      $entity_query = $this->database->select($this->getBaseTable(), 't');
++      if ($embedded_to_table == $this->getBaseTable()) {
++        $entity_query->isNotNull($dedicated_table);
++        $entity_query->condition("$dedicated_table.bundle", $field_definition->getTargetBundle());
++        $entity_query->fields('t', ["$dedicated_table"]);
++      }
++      else {
++        $entity_query->isNotNull("$embedded_to_table.$dedicated_table");
++        $entity_query->condition("$embedded_to_table.$dedicated_table.bundle", $field_definition->getTargetBundle());
++      }
++      $entity_query->range(0, $batch_size);
+ 
+-      foreach ($item_query->execute() as $item_row) {
+-        if (!isset($entities[$item_row['revision_id']])) {
+-          // Create entity with the right revision id and entity id combination.
+-          $item_row['entity_type'] = $this->entityTypeId;
+-          // @todo Replace this by an entity object created via an entity
+-          //   factory. https://www.drupal.org/node/1867228.
+-          $entities[$item_row['revision_id']] = _field_create_entity_from_ids((object) $item_row);
++      $entities = [];
++      $items_by_entity = [];
++      foreach ($entity_query->execute() as $row) {
++        if ($embedded_to_table == $this->getBaseTable()) {
++          $dedicated_table_rows = $row->$dedicated_table;
++        }
++        else {
++          $dedicated_table_rows = [];
++          $embedded_to_table_rows = $row->$embedded_to_table;
++          foreach ($embedded_to_table_rows as $embedded_to_table_row) {
++            $dedicated_table_rows += $embedded_to_table_row[$dedicated_table];
++          }
++        }
++        if (is_array($dedicated_table_rows)) {
++          foreach ($dedicated_table_rows as $dedicated_table_row) {
++            if (!isset($entities[$dedicated_table_row['revision_id']])) {
++              // Create entity with the right revision id and entity id combination.
++              $dedicated_table_row['entity_type'] = $this->entityTypeId;
++              // @todo Replace this by an entity object created via an entity
++              // factory, see https://www.drupal.org/node/1867228.
++              $entities[$dedicated_table_row['revision_id']] = _field_create_entity_from_ids((object) $dedicated_table_row);
++            }
++            $item = [];
++            foreach ($column_map as $db_column => $field_column) {
++              $item[$field_column] = $dedicated_table_row[$db_column];
++            }
++            $items_by_entity[$dedicated_table_row['revision_id']][] = $item;
++          }
+         }
+-        $item = [];
+-        foreach ($column_map as $db_column => $field_column) {
+-          $item[$field_column] = $item_row[$db_column];
++      }
++    }
++    else {
++      // Get the entities which we want to purge first.
++      $entity_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC]);
++      $or = $entity_query->orConditionGroup();
++      foreach ($storage_definition->getColumns() as $column_name => $data) {
++        $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
++      }
++      $entity_query
++        ->distinct(TRUE)
++        ->fields('t', ['entity_id'])
++        ->condition('bundle', $field_definition->getTargetBundle())
++        ->range(0, $batch_size);
++
++      $entities = [];
++      $items_by_entity = [];
++      foreach ($entity_query->execute() as $row) {
++        $item_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC])
++          ->fields('t')
++          ->condition('entity_id', $row['entity_id'])
++          ->condition('deleted', 1)
++          ->orderBy('delta');
++
++        foreach ($item_query->execute() as $item_row) {
++          if (!isset($entities[$item_row['revision_id']])) {
++            // Create entity with the right revision id and entity id combination.
++            $item_row['entity_type'] = $this->entityTypeId;
++            // @todo Replace this by an entity object created via an entity
++            //   factory. https://www.drupal.org/node/1867228.
++            $entities[$item_row['revision_id']] = _field_create_entity_from_ids((object) $item_row);
++          }
++          $item = [];
++          foreach ($column_map as $db_column => $field_column) {
++            $item[$field_column] = $item_row[$db_column];
++          }
++          $items_by_entity[$item_row['revision_id']][] = $item;
+         }
+-        $items_by_entity[$item_row['revision_id']][] = $item;
+       }
+     }
+ 
+@@ -1702,18 +3202,68 @@ protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefiniti
+     $storage_definition = $field_definition->getFieldStorageDefinition();
+     $is_deleted = $storage_definition->isDeleted();
+     $table_mapping = $this->getTableMapping();
+-    $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
+-    $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
+-    $revision_id = $this->entityType->isRevisionable() ? $entity->getRevisionId() : $entity->id();
+-    $this->database->delete($table_name)
+-      ->condition('revision_id', $revision_id)
+-      ->condition('deleted', 1)
+-      ->execute();
+-    if ($this->entityType->isRevisionable()) {
+-      $this->database->delete($revision_name)
++
++    if ($this->database->driver() == 'mongodb') {
++      $id = $this->entityType->isRevisionable() ? $entity->getRevisionId() : $entity->id();
++      $id_key = $this->entityType->isRevisionable() ? $this->revisionKey : $this->idKey;
++
++      $dedicated_tables = [];
++      if ($this->entityType->isRevisionable()) {
++        $dedicated_tables[$this->getJsonStorageAllRevisionsTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageAllRevisionsTable(), $is_deleted);
++        $dedicated_tables[$this->getJsonStorageCurrentRevisionTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageCurrentRevisionTable(), $is_deleted);
++        $dedicated_tables[$this->getJsonStorageLatestRevisionTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageLatestRevisionTable(), $is_deleted);
++      }
++      elseif (!$this->entityType->isRevisionable() && $this->entityType->isTranslatable()) {
++        $dedicated_tables[$this->getJsonStorageTranslationsTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageTranslationsTable(), $is_deleted);
++      }
++      elseif (!$this->entityType->isRevisionable() && !$this->entityType->isTranslatable()) {
++        $dedicated_tables[$this->getBaseTable()] = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getBaseTable(), $is_deleted);
++      }
++
++      $field_info = $this->database->tableInformation()->getTableField($this->getBaseTable(), $id_key);
++      if (isset($field_info['type']) && in_array($field_info['type'], ['int', 'serial'], TRUE)) {
++        $id = (int) $id;
++      }
++
++      $prefixed_table = $this->database->getPrefix() . $this->getBaseTable();
++      foreach ($dedicated_tables as $embedded_to_table => $dedicated_table) {
++        if ($embedded_to_table == $this->getBaseTable()) {
++          $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++            [
++              $dedicated_table => ['$exists' => TRUE],
++              $id_key => $id,
++            ],
++            ['$unset' => [$dedicated_table => ""]],
++            ['session' => $this->database->getMongodbSession()],
++          );
++        }
++        else {
++          $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++            [
++              "$embedded_to_table.$dedicated_table" => ['$exists' => TRUE],
++              $id_key => $id,
++            ],
++            ['$unset' => ["$embedded_to_table.$dedicated_table" => ""]],
++            ['session' => $this->database->getMongodbSession()],
++          );
++        }
++      }
++    }
++    else {
++      $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
++      $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
++      $revision_id = $this->entityType->isRevisionable() ? $entity->getRevisionId() : $entity->id();
++
++      $this->database->delete($table_name)
+         ->condition('revision_id', $revision_id)
+         ->condition('deleted', 1)
+         ->execute();
++      if ($this->entityType->isRevisionable()) {
++        $this->database->delete($revision_name)
++          ->condition('revision_id', $revision_id)
++          ->condition('deleted', 1)
++          ->execute();
++      }
+     }
+   }
+ 
+@@ -1735,39 +3285,87 @@ public function countFieldData($storage_definition, $as_bool = FALSE) {
+     $table_mapping = $this->getTableMapping($storage_definitions);
+ 
+     if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+-      $is_deleted = $storage_definition->isDeleted();
+-      if ($this->entityType->isRevisionable()) {
+-        $table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
++      if ($this->database->driver() == 'mongodb') {
++        $query = $this->database->select($this->getBaseTable(), 't');
++        $or = $query->orConditionGroup();
++
++        $is_deleted = $storage_definition->isDeleted();
++        if ($this->entityType->isRevisionable()) {
++          $dedicated_all_revisions_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageAllRevisionsTable(), $is_deleted);
++          $or->isNotNull($this->getJsonStorageAllRevisionsTable() . '.' . $dedicated_all_revisions_table_name);
++        }
++        elseif ($this->entityType->isTranslatable()) {
++          $dedicated_translations_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getJsonStorageTranslationsTable(), $is_deleted);
++          $or->isNotNull($this->getJsonStorageTranslationsTable() . '.' . $dedicated_translations_table_name);
++        }
++        else {
++          $dedicated_base_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->getBaseTable(), $is_deleted);
++          $or->isNotNull($dedicated_base_table_name);
++        }
++
++        $query
++          ->condition($or)
++          ->fields('t', [$this->idKey]);
+       }
+       else {
+-        $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
+-      }
+-      $query = $this->database->select($table_name, 't');
+-      $or = $query->orConditionGroup();
+-      foreach ($storage_definition->getColumns() as $column_name => $data) {
+-        $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
+-      }
+-      $query->condition($or);
+-      if (!$as_bool) {
+-        $query
+-          ->fields('t', ['entity_id'])
+-          ->distinct(TRUE);
++        $is_deleted = $storage_definition->isDeleted();
++        if ($this->entityType->isRevisionable()) {
++          $table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
++        }
++        else {
++          $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
++        }
++        $query = $this->database->select($table_name, 't');
++        $or = $query->orConditionGroup();
++        foreach ($storage_definition->getColumns() as $column_name => $data) {
++          $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
++        }
++        $query->condition($or);
++        if (!$as_bool) {
++          $query
++            ->fields('t', ['entity_id'])
++            ->distinct(TRUE);
++        }
+       }
+     }
+     elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
+-      // Ascertain the table this field is mapped too.
+-      $field_name = $storage_definition->getName();
+-      $table_name = $table_mapping->getFieldTableName($field_name);
+-      $query = $this->database->select($table_name, 't');
+-      $or = $query->orConditionGroup();
+-      foreach (array_keys($storage_definition->getColumns()) as $property_name) {
+-        $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $property_name));
+-      }
+-      $query->condition($or);
+-      if (!$as_bool) {
++      if ($this->database->driver() == 'mongodb') {
++        $query = $this->database->select($this->getBaseTable(), 't');
++        $or = $query->orConditionGroup();
++
++        foreach (array_keys($storage_definition->getColumns()) as $property_name) {
++          if ($this->entityType->isRevisionable()) {
++            $or->isNotNull($this->getJsonStorageAllRevisionsTable() . '.' . $table_mapping->getFieldColumnName($storage_definition, $property_name));
++            $or->isNotNull($this->getJsonStorageCurrentRevisionTable() . '.' . $table_mapping->getFieldColumnName($storage_definition, $property_name));
++            $or->isNotNull($this->getJsonStorageLatestRevisionTable() . '.' . $table_mapping->getFieldColumnName($storage_definition, $property_name));
++          }
++          elseif ($this->entityType->isTranslatable()) {
++            $or->isNotNull($this->getJsonStorageTranslationsTable() . '.' . $table_mapping->getFieldColumnName($storage_definition, $property_name));
++          }
++          else {
++            $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $property_name));
++          }
++        }
++
+         $query
+-          ->fields('t', [$this->idKey])
+-          ->distinct(TRUE);
++          ->condition($or)
++          ->fields('t', [$this->idKey]);
++      }
++      else {
++        // Ascertain the table this field is mapped too.
++        $field_name = $storage_definition->getName();
++        $table_name = $table_mapping->getFieldTableName($field_name);
++        $query = $this->database->select($table_name, 't');
++        $or = $query->orConditionGroup();
++        foreach (array_keys($storage_definition->getColumns()) as $property_name) {
++          $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $property_name));
++        }
++        $query->condition($or);
++        if (!$as_bool) {
++          $query
++            ->fields('t', [$this->idKey])
++            ->distinct(TRUE);
++        }
+       }
+     }
+ 
+@@ -1780,7 +3378,7 @@ public function countFieldData($storage_definition, $as_bool = FALSE) {
+       if ($as_bool) {
+         $query
+           ->range(0, 1)
+-          ->addExpression('1');
++          ->addExpressionConstant('1');
+       }
+       else {
+         // Otherwise count the number of rows.
+@@ -1791,4 +3389,17 @@ public function countFieldData($storage_definition, $as_bool = FALSE) {
+     return $as_bool ? (bool) $count : (int) $count;
+   }
+ 
++  /**
++   * Helper method to get the MongoDB table information service.
++   *
++   * @return \Drupal\mongodb\Driver\Database\mongodb\TableInformation
++   *   The MongoDB table information service.
++   */
++  protected function getMongoSequences() {
++    if (!isset($this->mongoSequences)) {
++      $this->mongoSequences = \Drupal::service('mongodb.sequences');
++    }
++    return $this->mongoSequences;
++  }
++
+ }
+diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+index 3b3abae2a04d0646681da85643a71a0c3885be79..41bb19f36dba82d34714f68715586b309f711acf 100644
+--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
++++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+@@ -3,6 +3,7 @@
+ namespace Drupal\Core\Entity\Sql;
+ 
+ use Drupal\Core\Database\Connection;
++use Drupal\Core\Database\DatabaseExceptionWrapper;
+ use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+ use Drupal\Core\Entity\ContentEntityTypeInterface;
+ use Drupal\Core\Entity\EntityFieldManagerInterface;
+@@ -199,7 +200,7 @@ protected function getTableMapping(EntityTypeInterface $entity_type, ?array $sto
+       $field_storage_definitions = $storage_definitions ?: $this->fieldStorageDefinitions;
+     }
+ 
+-    return $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions);
++    return $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions, '', ($this->database->driver() == 'mongodb'));
+   }
+ 
+   /**
+@@ -385,17 +386,39 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+     $this->checkEntityType($entity_type);
+     $schema_handler = $this->database->schema();
+ 
+-    // Delete entity and field tables.
+-    $table_names = $this->getTableNames($entity_type, $this->fieldStorageDefinitions, $this->getTableMapping($entity_type));
+-    foreach ($table_names as $table_name) {
+-      if ($schema_handler->tableExists($table_name)) {
+-        $schema_handler->dropTable($table_name);
++    if ($this->database->driver() == 'mongodb') {
++      // Delete entity base table. Deleting the base table also deletes all
++      // embedded tables.
++      if ($schema_handler->tableExists($this->storage->getBaseTable())) {
++        $schema_handler->dropTable($this->storage->getBaseTable());
++      }
++
++      // Delete dedicated field tables.
++      $table_mapping = $this->getTableMapping($entity_type, $this->fieldStorageDefinitions);
++      foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
++        // If we have a field having dedicated storage we need to drop it,
++        // otherwise we just remove the related schema data.
++        if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
++          $this->deleteDedicatedTableSchema($field_storage_definition);
++        }
++        elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
++          $this->deleteFieldSchemaData($field_storage_definition);
++        }
+       }
+     }
++    else {
++      // Delete entity and field tables.
++      $table_names = $this->getTableNames($entity_type, $this->fieldStorageDefinitions, $this->getTableMapping($entity_type));
++      foreach ($table_names as $table_name) {
++        if ($schema_handler->tableExists($table_name)) {
++          $schema_handler->dropTable($table_name);
++        }
++      }
+ 
+-    // Delete the field schema data.
+-    foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
+-      $this->deleteFieldSchemaData($field_storage_definition);
++      // Delete the field schema data.
++      foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
++        $this->deleteFieldSchemaData($field_storage_definition);
++      }
+     }
+ 
+     // Delete the entity schema.
+@@ -416,22 +439,53 @@ public function onFieldableEntityTypeCreate(EntityTypeInterface $entity_type, ar
+ 
+     // Create entity tables.
+     $schema = $this->getEntitySchema($entity_type, TRUE);
+-    foreach ($schema as $table_name => $table_schema) {
+-      if (!$schema_handler->tableExists($table_name)) {
+-        $schema_handler->createTable($table_name, $table_schema);
++
++    if ($this->database->driver() == 'mongodb') {
++      // Create the base table first.
++      $base_table = $entity_type->getBaseTable();
++      if (!empty($schema[$base_table]) && !$schema_handler->tableExists($base_table)) {
++        $schema_handler->createTable($base_table, $schema[$base_table]);
+       }
+-    }
+ 
+-    // Create dedicated field tables.
+-    $table_mapping = $this->getTableMapping($this->entityType);
+-    foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
+-      if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
+-        $this->createDedicatedTableSchema($field_storage_definition);
++      // Create now all embedded tables.
++      foreach ($schema as $table_name => $table_schema) {
++        if (($base_table != $table_name) && !$schema_handler->tableExists($table_name)) {
++          $schema_handler->createEmbeddedTable($base_table, $table_name, $table_schema);
++        }
++      }
++
++      // Create dedicated field tables.
++      // $table_mapping = $this->getTableMapping($entity_type, $this->fieldStorageDefinitions);
++      $table_mapping = $this->getTableMapping($entity_type);
++      foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
++        if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
++          $this->createDedicatedTableSchema($field_storage_definition);
++        }
++        elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
++          // The shared tables are already fully created, but we need to save the
++          // per-field schema definitions for later use.
++          $this->createSharedTableSchema($field_storage_definition, TRUE);
++        }
+       }
+-      elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
+-        // The shared tables are already fully created, but we need to save the
+-        // per-field schema definitions for later use.
+-        $this->createSharedTableSchema($field_storage_definition, TRUE);
++    }
++    else {
++      foreach ($schema as $table_name => $table_schema) {
++        if (!$schema_handler->tableExists($table_name)) {
++          $schema_handler->createTable($table_name, $table_schema);
++        }
++      }
++
++      // Create dedicated field tables.
++      $table_mapping = $this->getTableMapping($this->entityType);
++      foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
++        if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
++          $this->createDedicatedTableSchema($field_storage_definition);
++        }
++        elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
++          // The shared tables are already fully created, but we need to save the
++          // per-field schema definitions for later use.
++          $this->createSharedTableSchema($field_storage_definition, TRUE);
++        }
+       }
+     }
+ 
+@@ -451,12 +505,12 @@ public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, En
+    */
+   protected function preUpdateEntityTypeSchema(EntityTypeInterface $entity_type, EntityTypeInterface $original, array $field_storage_definitions, array $original_field_storage_definitions, ?array &$sandbox = NULL) {
+     $temporary_prefix = static::getTemporaryTableMappingPrefix($entity_type, $field_storage_definitions);
+-    $sandbox['temporary_table_mapping'] = $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions, $temporary_prefix);
+-    $sandbox['new_table_mapping'] = $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions);
+-    $sandbox['original_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions);
++    $sandbox['temporary_table_mapping'] = $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions, $temporary_prefix, ($this->database->driver() == 'mongodb'));
++    $sandbox['new_table_mapping'] = $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions, '', ($this->database->driver() == 'mongodb'));
++    $sandbox['original_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions, '', ($this->database->driver() == 'mongodb'));
+ 
+     $backup_prefix = static::getTemporaryTableMappingPrefix($original, $original_field_storage_definitions, 'old_');
+-    $sandbox['backup_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions, $backup_prefix);
++    $sandbox['backup_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions, $backup_prefix, ($this->database->driver() == 'mongodb'));
+     $sandbox['backup_prefix_key'] = substr($backup_prefix, 4);
+     $sandbox['backup_request_time'] = \Drupal::time()->getRequestTime();
+ 
+@@ -483,8 +537,16 @@ protected function preUpdateEntityTypeSchema(EntityTypeInterface $entity_type, E
+     $schema = array_intersect_key($schema, $temporary_table_names);
+ 
+     // Create entity tables.
+-    foreach ($schema as $table_name => $table_schema) {
+-      $this->database->schema()->createTable($temporary_table_names[$table_name], $table_schema);
++    if ($this->database->driver() == 'mongodb') {
++      $base_table = $temporary_table_names[$entity_type->getBaseTable()];
++      if (!empty($schema[$entity_type->getBaseTable()])) {
++        $this->database->schema()->createTable($base_table, $schema[$entity_type->getBaseTable()]);
++      }
++    }
++    else {
++      foreach ($schema as $table_name => $table_schema) {
++        $this->database->schema()->createTable($temporary_table_names[$table_name], $table_schema);
++      }
+     }
+ 
+     // Create dedicated field tables.
+@@ -494,8 +556,11 @@ protected function preUpdateEntityTypeSchema(EntityTypeInterface $entity_type, E
+ 
+         // Filter out tables which are not part of the table mapping.
+         $schema = array_intersect_key($schema, $temporary_table_names);
+-        foreach ($schema as $table_name => $table_schema) {
+-          $this->database->schema()->createTable($temporary_table_names[$table_name], $table_schema);
++
++        if ($this->database->driver() != 'mongodb') {
++          foreach ($schema as $table_name => $table_schema) {
++            $this->database->schema()->createTable($temporary_table_names[$table_name], $table_schema);
++          }
+         }
+       }
+     }
+@@ -547,7 +612,15 @@ protected function postUpdateEntityTypeSchema(EntityTypeInterface $entity_type,
+     // definitions.
+     try {
+       foreach ($sandbox['temporary_table_names'] as $current_table_name => $temp_table_name) {
+-        $this->database->schema()->renameTable($temp_table_name, $current_table_name);
++        if ($this->database->driver() == 'mongodb') {
++          // For MongoDB all entity data is stored in the base table.
++          if ($current_table_name == $entity_type->getBaseTable()) {
++            $this->database->schema()->renameTable($temp_table_name, $current_table_name);
++          }
++        }
++        else {
++          $this->database->schema()->renameTable($temp_table_name, $current_table_name);
++        }
+       }
+ 
+       // Store the updated entity schema.
+@@ -707,9 +780,20 @@ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $
+    * {@inheritdoc}
+    */
+   public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
++    try {
++      $has_data = $this->storage->countFieldData($storage_definition, TRUE);
++    }
++    catch (DatabaseExceptionWrapper $e) {
++      // This may happen when changing field storage schema, since we are not
++      // able to use a table mapping matching the passed storage definition.
++      // @todo Revisit this once we are able to instantiate the table mapping
++      //   properly. See https://www.drupal.org/node/2274017.
++      return;
++    }
++
+     // If the field storage does not have any data, we can safely delete its
+     // schema.
+-    if (!$this->storage->countFieldData($storage_definition, TRUE)) {
++    if (!$has_data) {
+       $this->performFieldSchemaOperation('delete', $storage_definition);
+       return;
+     }
+@@ -720,91 +804,274 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
+     }
+ 
+     $table_mapping = $this->getTableMapping($this->entityType, [$storage_definition]);
+-    $field_table_name = $table_mapping->getFieldTableName($storage_definition->getName());
+-
+     if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+-      // Move the table to a unique name while the table contents are being
+-      // deleted.
+-      $table = $table_mapping->getDedicatedDataTableName($storage_definition);
+-      $new_table = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE);
+-      $this->database->schema()->renameTable($table, $new_table);
+-      if ($this->entityType->isRevisionable()) {
+-        $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+-        $revision_new_table = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
+-        $this->database->schema()->renameTable($revision_table, $revision_new_table);
+-      }
+-    }
+-    else {
+-      // Move the field data from the shared table to a dedicated one in order
+-      // to allow it to be purged like any other field.
+-      $shared_table_field_columns = $table_mapping->getColumnNames($storage_definition->getName());
++      if ($this->database->driver() == 'mongodb') {
++        $base_table = $this->storage->getBaseTable();
++        $prefixed_table = $this->database->getPrefix() . $base_table;
++        $schema = $this->getDedicatedTableSchema($storage_definition);
++        $id_key = $this->entityType->getKey('id');
++
++        // Move the table to a unique name while the table contents are being
++        // deleted.
++        if ($this->entityType->isRevisionable()) {
++          // For MongoDB: All embedded table data needs to be renamed.
++          $all_revisions_table = $this->storage->getJsonStorageAllRevisionsTable();
++          $dedicated_all_revisions_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $all_revisions_table);
++          $dedicated_all_revisions_new_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $all_revisions_table, TRUE);
++          $this->database->schema()->createEmbeddedTable($all_revisions_table, $dedicated_all_revisions_new_table, $schema[$dedicated_all_revisions_table]);
++
++          $current_revision_table = $this->storage->getJsonStorageCurrentRevisionTable();
++          $dedicated_current_revision_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $current_revision_table);
++          $dedicated_current_revision_new_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $current_revision_table, TRUE);
++          // Check if there already exists a table with that name. If so, then delete it.
++          if ($this->database->schema()->tableExists($dedicated_current_revision_new_table)) {
++            $this->database->schema()->dropTable($dedicated_current_revision_new_table);
++          }
++          $this->database->schema()->createEmbeddedTable($current_revision_table, $dedicated_current_revision_new_table, $schema[$dedicated_current_revision_table]);
++
++          $latest_revision_table = $this->storage->getJsonStorageLatestRevisionTable();
++          $dedicated_latest_revision_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $latest_revision_table);
++          $dedicated_latest_revision_new_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $latest_revision_table, TRUE);
++          // Check if there already exists a table with that name. If so, then delete it.
++          if ($this->database->schema()->tableExists($dedicated_latest_revision_new_table)) {
++            $this->database->schema()->dropTable($dedicated_latest_revision_new_table);
++          }
++          $this->database->schema()->createEmbeddedTable($latest_revision_table, $dedicated_latest_revision_new_table, $schema[$dedicated_latest_revision_table]);
++
++          $this->database->schema()->dropTable($dedicated_all_revisions_table);
++          $this->database->schema()->dropTable($dedicated_current_revision_table);
++          $this->database->schema()->dropTable($dedicated_latest_revision_table);
++
++          $cursor = $this->database->getConnection()->selectCollection($prefixed_table)->find(
++            [
++              "$all_revisions_table.$dedicated_all_revisions_table" => ['$exists' => TRUE],
++            ],
++            [
++              'projection' => [
++                $id_key => 1,
++                $all_revisions_table => 1,
++                $current_revision_table => 1,
++                $latest_revision_table => 1,
++                '_id' => 0,
++              ],
++              'session' => $this->database->getMongodbSession(),
++            ],
++          );
++
++          foreach ($cursor as $entity) {
++            if (isset($entity->{$all_revisions_table})) {
++              foreach ($entity->{$all_revisions_table} as &$revision) {
++                if (isset($revision[$dedicated_all_revisions_table])) {
++                  $revision[$dedicated_all_revisions_new_table] = $revision[$dedicated_all_revisions_table];
++                  unset($revision[$dedicated_all_revisions_table]);
++                }
++              }
++            }
+ 
+-      // Refresh the table mapping to use the deleted storage definition.
+-      $deleted_storage_definition = $this->deletedFieldsRepository()->getFieldStorageDefinitions()[$storage_definition->getUniqueStorageIdentifier()];
+-      $table_mapping = $this->getTableMapping($this->entityType, [$deleted_storage_definition]);
++            if (isset($entity->{$current_revision_table})) {
++              foreach ($entity->{$current_revision_table} as &$revision) {
++                if (isset($revision[$dedicated_current_revision_table])) {
++                  $revision[$dedicated_current_revision_new_table] = $revision[$dedicated_current_revision_table];
++                  unset($revision[$dedicated_current_revision_table]);
++                }
++              }
++            }
+ 
+-      $dedicated_table_field_schema = $this->getDedicatedTableSchema($deleted_storage_definition);
+-      $dedicated_table_field_columns = $table_mapping->getColumnNames($deleted_storage_definition->getName());
++            if (isset($entity->{$latest_revision_table})) {
++              foreach ($entity->{$latest_revision_table} as &$revision) {
++                if (isset($revision[$dedicated_latest_revision_table])) {
++                  $revision[$dedicated_latest_revision_new_table] = $revision[$dedicated_latest_revision_table];
++                  unset($revision[$dedicated_latest_revision_table]);
++                }
++              }
++            }
+ 
+-      $dedicated_table_name = $table_mapping->getDedicatedDataTableName($deleted_storage_definition, TRUE);
+-      $dedicated_table_name_mapping[$table_mapping->getDedicatedDataTableName($deleted_storage_definition)] = $dedicated_table_name;
+-      if ($this->entityType->isRevisionable()) {
+-        $dedicated_revision_table_name = $table_mapping->getDedicatedRevisionTableName($deleted_storage_definition, TRUE);
+-        $dedicated_table_name_mapping[$table_mapping->getDedicatedRevisionTableName($deleted_storage_definition)] = $dedicated_revision_table_name;
+-      }
++            $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++              [$id_key => $entity->{$id_key}],
++              [
++                '$set' => [
++                  $all_revisions_table => $entity->{$all_revisions_table},
++                  $current_revision_table => $entity->{$current_revision_table},
++                  $latest_revision_table => $entity->{$latest_revision_table},
++                ],
++              ],
++              ['session' => $this->database->getMongodbSession()],
++            );
++          }
++        }
++        elseif ($this->entityType->isTranslatable()) {
++          // For MongoDB: All embedded table data needs to be renamed.
++          $translations_table = $this->storage->getJsonStorageTranslationsTable();
++          $dedicated_translations_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $translations_table);
++          $dedicated_translations_new_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $translations_table, TRUE);
++          $this->database->schema()->createEmbeddedTable($translations_table, $dedicated_translations_new_table, $schema[$dedicated_translations_table]);
++          $this->database->schema()->dropTable($dedicated_translations_table);
++
++          $cursor = $this->database->getConnection()->selectCollection($prefixed_table)->find(
++            ["$translations_table.$dedicated_translations_table" => ['$exists' => TRUE]],
++            [
++              'projection' => [
++                $id_key => 1,
++                $translations_table => 1,
++                '_id' => 0,
++              ],
++              'session' => $this->database->getMongodbSession(),
++            ],
++          );
++
++          foreach ($cursor as $entity) {
++            if (isset($entity->{$translations_table})) {
++              foreach ($entity->{$translations_table} as &$revision) {
++                if (isset($revision[$dedicated_translations_table])) {
++                  $revision[$dedicated_translations_new_table] = $revision[$dedicated_translations_table];
++                  unset($revision[$dedicated_translations_table]);
++                }
++              }
++            }
+ 
+-      // Create the dedicated field tables using "deleted" table names.
+-      foreach ($dedicated_table_field_schema as $name => $table) {
+-        if (!$this->database->schema()->tableExists($dedicated_table_name_mapping[$name])) {
+-          $this->database->schema()->createTable($dedicated_table_name_mapping[$name], $table);
++            $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++              [$id_key => $entity->{$id_key}],
++              [
++                '$set' => [
++                  $translations_table => $entity->{$translations_table},
++                ],
++              ],
++              ['session' => $this->database->getMongodbSession()],
++            );
++          }
+         }
+         else {
+-          throw new EntityStorageException('The field ' . $storage_definition->getName() . ' has already been deleted and it is in the process of being purged.');
++          // For MongoDB: All embedded table data needs to be renamed.
++          $base_table = $this->storage->getBaseTable();
++          $dedicated_base_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $base_table);
++          $dedicated_base_new_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $base_table, TRUE);
++
++          // Delete the old archived table before renaming or the renaming will fail. Two tables cannot have the same name.
++          if ($this->database->schema()->tableExists($dedicated_base_new_table)) {
++            $this->database->schema()->dropTable($dedicated_base_new_table);
++          }
++          $this->database->schema()->renameTable($dedicated_base_table, $dedicated_base_new_table);
+         }
+       }
+-
+-      try {
+-        if ($this->database->supportsTransactionalDDL()) {
+-          // If the database supports transactional DDL, we can go ahead and rely
+-          // on it. If not, we will have to rollback manually if something fails.
+-          $transaction = $this->database->startTransaction();
++      else {
++        // Move the table to a unique name while the table contents are being
++        // deleted.
++        $table = $table_mapping->getDedicatedDataTableName($storage_definition);
++        $new_table = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE);
++        $this->database->schema()->renameTable($table, $new_table);
++        if ($this->entityType->isRevisionable()) {
++          $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
++          $revision_new_table = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
++          $this->database->schema()->renameTable($revision_table, $revision_new_table);
++        }
++      }
++    }
++    else {
++      if ($this->database->driver() == 'mongodb') {
++        // Move the field data from the shared table to a dedicated one in order
++        // to allow it to be purged like any other field.
++        $shared_table_field_columns = $table_mapping->getColumnNames($storage_definition->getName());
++        foreach ($shared_table_field_columns as $shared_table_field_column) {
++          if ($this->entityType->isRevisionable()) {
++            $all_revisions_table = $table_mapping->getJsonStorageAllRevisionsTable();
++            if ($this->database->schema()->fieldExists($all_revisions_table, $shared_table_field_column)) {
++              $this->database->schema()->dropField($all_revisions_table, $shared_table_field_column);
++            }
++            $current_revision_table = $table_mapping->getJsonStorageCurrentRevisionTable();
++            if ($this->database->schema()->fieldExists($current_revision_table, $shared_table_field_column)) {
++              $this->database->schema()->dropField($current_revision_table, $shared_table_field_column);
++            }
++            $latest_revision_table = $table_mapping->getJsonStorageLatestRevisionTable();
++            if ($this->database->schema()->fieldExists($latest_revision_table, $shared_table_field_column)) {
++              $this->database->schema()->dropField($latest_revision_table, $shared_table_field_column);
++            }
++          }
++          elseif ($this->entityType->isTranslatable()) {
++            $translations_table = $table_mapping->getJsonStorageTranslationsTable();
++            if ($this->database->schema()->fieldExists($translations_table, $shared_table_field_column)) {
++              $this->database->schema()->dropField($translations_table, $shared_table_field_column);
++            }
++          }
++          $base_table = $table_mapping->getBaseTable();
++          if ($this->database->schema()->fieldExists($base_table, $shared_table_field_column)) {
++            $this->database->schema()->dropField($base_table, $shared_table_field_column);
++          }
++        }
++      }
++      else {
++        // Move the field data from the shared table to a dedicated one in order
++        // to allow it to be purged like any other field.
++        $shared_table_field_columns = $table_mapping->getColumnNames($storage_definition->getName());
++
++        // Refresh the table mapping to use the deleted storage definition.
++        $deleted_storage_definition = $this->deletedFieldsRepository()->getFieldStorageDefinitions()[$storage_definition->getUniqueStorageIdentifier()];
++        $table_mapping = $this->getTableMapping($this->entityType, [$deleted_storage_definition]);
++
++        $dedicated_table_field_schema = $this->getDedicatedTableSchema($deleted_storage_definition);
++        $dedicated_table_field_columns = $table_mapping->getColumnNames($deleted_storage_definition->getName());
++
++        $dedicated_table_name = $table_mapping->getDedicatedDataTableName($deleted_storage_definition, TRUE);
++        $dedicated_table_name_mapping[$table_mapping->getDedicatedDataTableName($deleted_storage_definition)] = $dedicated_table_name;
++        if ($this->entityType->isRevisionable()) {
++          $dedicated_revision_table_name = $table_mapping->getDedicatedRevisionTableName($deleted_storage_definition, TRUE);
++          $dedicated_table_name_mapping[$table_mapping->getDedicatedRevisionTableName($deleted_storage_definition)] = $dedicated_revision_table_name;
+         }
+ 
+-        // Copy the data from the base table.
+-        $this->database->insert($dedicated_table_name)
+-          ->from($this->getSelectQueryForFieldStorageDeletion($field_table_name, $shared_table_field_columns, $dedicated_table_field_columns))
+-          ->execute();
+-
+-        // Copy the data from the revision table.
+-        if (isset($dedicated_revision_table_name)) {
+-          if ($this->entityType->isTranslatable()) {
+-            $revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionDataTable() : $this->storage->getDataTable();
++        // Create the dedicated field tables using "deleted" table names.
++        foreach ($dedicated_table_field_schema as $name => $table) {
++          if (!$this->database->schema()->tableExists($dedicated_table_name_mapping[$name])) {
++            $this->database->schema()->createTable($dedicated_table_name_mapping[$name], $table);
+           }
+           else {
+-            $revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionTable() : $this->storage->getBaseTable();
++            throw new EntityStorageException('The field ' . $storage_definition->getName() . ' has already been deleted and it is in the process of being purged.');
+           }
+-          $this->database->insert($dedicated_revision_table_name)
+-            ->from($this->getSelectQueryForFieldStorageDeletion($revision_table, $shared_table_field_columns, $dedicated_table_field_columns, $field_table_name))
+-            ->execute();
+         }
+-      }
+-      catch (\Exception $e) {
+-        if ($this->database->supportsTransactionalDDL()) {
+-          if (isset($transaction)) {
+-            $transaction->rollBack();
++
++        try {
++          $field_table_name = $table_mapping->getFieldTableName($storage_definition->getName());
++
++          if ($this->database->supportsTransactionalDDL()) {
++            // If the database supports transactional DDL, we can go ahead and rely
++            // on it. If not, we will have to rollback manually if something fails.
++            $transaction = $this->database->startTransaction();
++          }
++
++          // Copy the data from the base table.
++          $this->database->insert($dedicated_table_name)
++            ->from($this->getSelectQueryForFieldStorageDeletion($field_table_name, $shared_table_field_columns, $dedicated_table_field_columns))
++            ->execute();
++
++          // Copy the data from the revision table.
++          if (isset($dedicated_revision_table_name)) {
++            if ($this->entityType->isTranslatable()) {
++              $revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionDataTable() : $this->storage->getDataTable();
++            }
++            else {
++              $revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionTable() : $this->storage->getBaseTable();
++            }
++            $this->database->insert($dedicated_revision_table_name)
++              ->from($this->getSelectQueryForFieldStorageDeletion($revision_table, $shared_table_field_columns, $dedicated_table_field_columns, $field_table_name))
++              ->execute();
+           }
+         }
+-        else {
+-          // Delete the dedicated tables.
+-          foreach ($dedicated_table_field_schema as $name => $table) {
+-            $this->database->schema()->dropTable($dedicated_table_name_mapping[$name]);
++        catch (\Exception $e) {
++          if ($this->database->supportsTransactionalDDL()) {
++            if (isset($transaction)) {
++              $transaction->rollBack();
++            }
+           }
++          else {
++            // Delete the dedicated tables.
++            foreach ($dedicated_table_field_schema as $name => $table) {
++              $this->database->schema()->dropTable($dedicated_table_name_mapping[$name]);
++            }
++          }
++          throw $e;
+         }
+-        throw $e;
+-      }
+ 
+-      // Delete the field from the shared tables.
+-      $this->deleteSharedTableSchema($storage_definition);
++        // Delete the field from the shared tables.
++        $this->deleteSharedTableSchema($storage_definition);
++      }
+     }
+     unset($this->fieldStorageDefinitions[$storage_definition->getName()]);
+   }
+@@ -841,13 +1108,13 @@ protected function getSelectQueryForFieldStorageDeletion($table_name, array $sha
+       // The bundle field is not stored in the revision table, so we need to
+       // join the data (or base) table and retrieve it from there.
+       if ($base_table && $base_table !== $table_name) {
+-        $join_condition = "[entity_table].[{$this->entityType->getKey('id')}] = [%alias].[{$this->entityType->getKey('id')}]";
++        $join_condition = $select->joinCondition()->compare("entity_table.{$this->entityType->getKey('id')}", "%alias.{$this->entityType->getKey('id')}");
+ 
+         // If the entity type is translatable, we also need to add the langcode
+         // to the join, otherwise we'll get duplicate rows for each language.
+         if ($this->entityType->isTranslatable()) {
+           $langcode = $this->entityType->getKey('langcode');
+-          $join_condition .= " AND [entity_table].[{$langcode}] = [%alias].[{$langcode}]";
++          $join_condition->compare("entity_table.{$langcode}", "%alias.{$langcode}");
+         }
+ 
+         $select->join($base_table, 'base_table', $join_condition);
+@@ -950,14 +1217,31 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
+ 
+       // Initialize the table schema.
+       $schema[$tables['base_table']] = $this->initializeBaseTable($entity_type);
+-      if (isset($tables['revision_table'])) {
+-        $schema[$tables['revision_table']] = $this->initializeRevisionTable($entity_type);
+-      }
+-      if (isset($tables['data_table'])) {
+-        $schema[$tables['data_table']] = $this->initializeDataTable($entity_type);
++
++      if ($this->database->driver() == 'mongodb') {
++        if (isset($tables['all_revisions_table'])) {
++          $schema[$tables['all_revisions_table']] = $this->initializeJsonStorageRevisionsTable($entity_type);
++        }
++        if (isset($tables['current_revision_table'])) {
++          $schema[$tables['current_revision_table']] = $this->initializeJsonStorageRevisionsTable($entity_type);
++        }
++        if (isset($tables['latest_revision_table'])) {
++          $schema[$tables['latest_revision_table']] = $this->initializeJsonStorageRevisionsTable($entity_type);
++        }
++        if (isset($tables['translations_table'])) {
++          $schema[$tables['translations_table']] = $this->initializeJsonStorageTranslationsTable($entity_type);
++        }
+       }
+-      if (isset($tables['revision_data_table'])) {
+-        $schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable($entity_type);
++      else {
++        if (isset($tables['revision_table'])) {
++          $schema[$tables['revision_table']] = $this->initializeRevisionTable($entity_type);
++        }
++        if (isset($tables['data_table'])) {
++          $schema[$tables['data_table']] = $this->initializeDataTable($entity_type);
++        }
++        if (isset($tables['revision_data_table'])) {
++          $schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable($entity_type);
++        }
+       }
+ 
+       // We need to act only on shared entity schema tables.
+@@ -979,31 +1263,50 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
+         }
+       }
+ 
+-      // Process tables after having gathered field information.
+-      if (isset($tables['data_table'])) {
+-        $this->processDataTable($entity_type, $schema[$tables['data_table']]);
+-      }
+-      if (isset($tables['revision_data_table'])) {
+-        $this->processRevisionDataTable($entity_type, $schema[$tables['revision_data_table']]);
++      if ($this->database->driver() == 'mongodb') {
++        // Not sure why the next method has been removed.
++        // $this->processBaseTable($entity_type, $schema[$tables['base_table']]);
++
++        if (isset($tables['all_revisions_table'])) {
++          $this->processJsonStorageRevisionsTable($entity_type, $schema[$tables['all_revisions_table']]);
++        }
++        if (isset($tables['current_revision_table'])) {
++          $this->processJsonStorageRevisionsTable($entity_type, $schema[$tables['current_revision_table']]);
++        }
++        if (isset($tables['latest_revision_table'])) {
++          $this->processJsonStorageRevisionsTable($entity_type, $schema[$tables['latest_revision_table']]);
++        }
++        if (isset($tables['translations_table'])) {
++          $this->processJsonStorageTranslationsTable($entity_type, $schema[$tables['translations_table']]);
++        }
+       }
++      else {
++        // Process tables after having gathered field information.
++        if (isset($tables['data_table'])) {
++          $this->processDataTable($entity_type, $schema[$tables['data_table']]);
++        }
++        if (isset($tables['revision_data_table'])) {
++          $this->processRevisionDataTable($entity_type, $schema[$tables['revision_data_table']]);
++        }
+ 
+-      // Add an index for the 'published' entity key.
+-      if (is_subclass_of($entity_type->getClass(), EntityPublishedInterface::class)) {
+-        $published_key = $entity_type->getKey('published');
+-        if ($published_key
++        // Add an index for the 'published' entity key.
++        if (is_subclass_of($entity_type->getClass(), EntityPublishedInterface::class)) {
++          $published_key = $entity_type->getKey('published');
++          if ($published_key
+             && isset($this->fieldStorageDefinitions[$published_key])
+             && !$this->fieldStorageDefinitions[$published_key]->hasCustomStorage()) {
+-          $published_field_table = $table_mapping->getFieldTableName($published_key);
+-          $id_key = $entity_type->getKey('id');
+-          if ($bundle_key = $entity_type->getKey('bundle')) {
+-            $key = "{$published_key}_{$bundle_key}";
+-            $columns = [$published_key, $bundle_key, $id_key];
+-          }
+-          else {
+-            $key = $published_key;
+-            $columns = [$published_key, $id_key];
++            $published_field_table = $table_mapping->getFieldTableName($published_key);
++            $id_key = $entity_type->getKey('id');
++            if ($bundle_key = $entity_type->getKey('bundle')) {
++              $key = "{$published_key}_{$bundle_key}";
++              $columns = [$published_key, $bundle_key, $id_key];
++            }
++            else {
++              $key = $published_key;
++              $columns = [$published_key, $id_key];
++            }
++            $schema[$published_field_table]['indexes'][$this->getEntityIndexName($entity_type, $key)] = $columns;
+           }
+-          $schema[$published_field_table]['indexes'][$this->getEntityIndexName($entity_type, $key)] = $columns;
+         }
+       }
+ 
+@@ -1023,13 +1326,25 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
+    *   A list of entity type tables, keyed by table key.
+    */
+   protected function getEntitySchemaTables(TableMappingInterface $table_mapping) {
+-    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+-    return array_filter([
+-      'base_table' => $table_mapping->getBaseTable(),
+-      'revision_table' => $table_mapping->getRevisionTable(),
+-      'data_table' => $table_mapping->getDataTable(),
+-      'revision_data_table' => $table_mapping->getRevisionDataTable(),
+-    ]);
++    if ($this->database->driver() == 'mongodb') {
++      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
++      return array_filter([
++        'base_table' => $table_mapping->getBaseTable(),
++        'all_revisions_table' => $table_mapping->getJsonStorageAllRevisionsTable(),
++        'current_revision_table' => $table_mapping->getJsonStorageCurrentRevisionTable(),
++        'latest_revision_table' => $table_mapping->getJsonStorageLatestRevisionTable(),
++        'translations_table' => $table_mapping->getJsonStorageTranslationsTable(),
++      ]);
++    }
++    else {
++      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
++      return array_filter([
++        'base_table' => $table_mapping->getBaseTable(),
++        'revision_table' => $table_mapping->getRevisionTable(),
++        'data_table' => $table_mapping->getDataTable(),
++        'revision_data_table' => $table_mapping->getRevisionDataTable(),
++      ]);
++    }
+   }
+ 
+   /**
+@@ -1433,6 +1748,59 @@ protected function initializeRevisionDataTable(ContentEntityTypeInterface $entit
+     return $schema;
+   }
+ 
++  /**
++   * Initializes common information for a JSON storage all revisions table.
++   *
++   * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
++   *   The entity type.
++   *
++   * @return array
++   *   A partial schema array for the all revisions table.
++   */
++  protected function initializeJsonStorageRevisionsTable(ContentEntityTypeInterface $entity_type) {
++    $entity_type_id = $entity_type->id();
++
++    $schema = [
++      'description' => "The all revisions table for $entity_type_id entities.",
++      'indexes' => [],
++    ];
++
++    if ($entity_type->isTranslatable()) {
++      $schema['primary key'] = [$entity_type->getKey('revision'), $entity_type->getKey('langcode')];
++    }
++    else {
++      $schema['primary key'] = [$entity_type->getKey('revision')];
++    }
++
++    $this->addTableDefaults($schema);
++
++    return $schema;
++  }
++
++  /**
++   * Initializes common information for a JSON storage translations table.
++   *
++   * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
++   *   The entity type.
++   *
++   * @return array
++   *   A partial schema array for the translations table.
++   */
++  protected function initializeJsonStorageTranslationsTable(ContentEntityTypeInterface $entity_type) {
++    $entity_type_id = $entity_type->id();
++
++    $schema = [
++      'description' => "The translations table for $entity_type_id entities.",
++      'indexes' => [],
++    ];
++
++    $schema['primary key'] = [$entity_type->getKey('id'), $entity_type->getKey('langcode')];
++
++    $this->addTableDefaults($schema);
++
++    return $schema;
++  }
++
+   /**
+    * Adds defaults to a table schema definition.
+    *
+@@ -1476,6 +1844,33 @@ protected function processRevisionDataTable(ContentEntityTypeInterface $entity_t
+     $schema['fields'][$entity_type->getKey('default_langcode')]['not null'] = TRUE;
+   }
+ 
++  /**
++   * Processes the gathered schema for a JSON storage all revisions table.
++   *
++   * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
++   *   The entity type.
++   * @param array &$schema
++   *   The table schema, passed by reference.
++   */
++  protected function processJsonStorageRevisionsTable(ContentEntityTypeInterface $entity_type, array &$schema) {
++    // Change the field "revision_id" from serial to integer. Serial primary key
++    // fields are auto-incremented. This is something we do not want from an
++    // embedded table.
++    if ($entity_type->hasKey('revision')) {
++      $schema['fields'][$entity_type->getKey('revision')]['type'] = 'int';
++    }
++  }
++
++  /**
++   * Processes the gathered schema for a JSON storage translations table.
++   *
++   * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
++   *   The entity type.
++   * @param array &$schema
++   *   The table schema, passed by reference.
++   */
++  protected function processJsonStorageTranslationsTable(ContentEntityTypeInterface $entity_type, array &$schema) {}
++
+   /**
+    * Processes the specified entity key.
+    *
+@@ -1545,14 +1940,31 @@ protected function performFieldSchemaOperation($operation, FieldStorageDefinitio
+    *   the dedicated tables.
+    */
+   protected function createDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, $only_save = FALSE) {
++    $table_mapping = $this->getTableMapping($this->entityType, [$storage_definition]);
+     $schema = $this->getDedicatedTableSchema($storage_definition);
+ 
+     if (!$only_save) {
+-      foreach ($schema as $name => $table) {
+-        // Check if the table exists because it might already have been
+-        // created as part of the earlier entity type update event.
+-        if (!$this->database->schema()->tableExists($name)) {
+-          $this->database->schema()->createTable($name, $table);
++      if ($this->database->driver() == 'mongodb') {
++        foreach ($this->getEntitySchemaTables($table_mapping) as $table_name) {
++          $dedicated_table_name = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $table_name);
++          if (isset($schema[$dedicated_table_name])) {
++            // Check if the table exists because it might already have been
++            // created as part of the earlier entity type update event.
++            if ($this->database->schema()->tableExists($dedicated_table_name)) {
++              $this->database->schema()->dropTable($dedicated_table_name);
++            }
++
++            $this->database->schema()->createEmbeddedTable($table_name, $dedicated_table_name, $schema[$dedicated_table_name]);
++          }
++        }
++      }
++      else {
++        foreach ($schema as $name => $table) {
++          // Check if the table exists because it might already have been
++          // created as part of the earlier entity type update event.
++          if (!$this->database->schema()->tableExists($name)) {
++            $this->database->schema()->createTable($name, $table);
++          }
+         }
+       }
+     }
+@@ -1645,16 +2057,60 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor
+    */
+   protected function deleteDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition) {
+     $table_mapping = $this->getTableMapping($this->entityType, [$storage_definition]);
+-    $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $storage_definition->isDeleted());
+-    if ($this->database->schema()->tableExists($table_name)) {
+-      $this->database->schema()->dropTable($table_name);
++
++    if ($this->database->driver() == 'mongodb') {
++      // When switching from dedicated to shared field table layout we need need
++      // to delete the field tables with their regular names. When this happens
++      // original definitions will be defined.
++      $table_mapping = $this->getTableMapping($this->entityType, [$storage_definition]);
++      if ($all_revisions_table = $table_mapping->getJsonStorageAllRevisionsTable()) {
++        $dedicated_all_revisions_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $all_revisions_table, $storage_definition->isDeleted());
++        if ($this->database->schema()->tableExists($dedicated_all_revisions_table)) {
++          $this->database->schema()->dropTable($dedicated_all_revisions_table);
++        }
++      }
++
++      if ($current_revision_table = $table_mapping->getJsonStorageCurrentRevisionTable()) {
++        $dedicated_current_revision_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $current_revision_table, $storage_definition->isDeleted());
++        if ($this->database->schema()->tableExists($dedicated_current_revision_table)) {
++          $this->database->schema()->dropTable($dedicated_current_revision_table);
++        }
++      }
++
++      if ($latest_revision_table = $table_mapping->getJsonStorageLatestRevisionTable()) {
++        $dedicated_latest_revision_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $latest_revision_table, $storage_definition->isDeleted());
++        if ($this->database->schema()->tableExists($dedicated_latest_revision_table)) {
++          $this->database->schema()->dropTable($dedicated_latest_revision_table);
++        }
++      }
++
++      if ($translations_table = $table_mapping->getJsonStorageTranslationsTable()) {
++        $dedicated_translations_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $translations_table, $storage_definition->isDeleted());
++        if ($this->database->schema()->tableExists($dedicated_translations_table)) {
++          $this->database->schema()->dropTable($dedicated_translations_table);
++        }
++      }
++
++      if (!$this->entityType->isRevisionable() && !$this->entityType->isTranslatable()) {
++        $dedicated_base_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $table_mapping->getBaseTable(), $storage_definition->isDeleted());
++        if ($this->database->schema()->tableExists($dedicated_base_table)) {
++          $this->database->schema()->dropTable($dedicated_base_table);
++        }
++      }
+     }
+-    if ($this->entityType->isRevisionable()) {
+-      $revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $storage_definition->isDeleted());
+-      if ($this->database->schema()->tableExists($revision_table_name)) {
+-        $this->database->schema()->dropTable($revision_table_name);
++    else {
++      $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $storage_definition->isDeleted());
++      if ($this->database->schema()->tableExists($table_name)) {
++        $this->database->schema()->dropTable($table_name);
++      }
++      if ($this->entityType->isRevisionable()) {
++        $revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $storage_definition->isDeleted());
++        if ($this->database->schema()->tableExists($revision_table_name)) {
++          $this->database->schema()->dropTable($revision_table_name);
++        }
+       }
+     }
++
+     $this->deleteFieldSchemaData($storage_definition);
+   }
+ 
+@@ -1753,8 +2209,23 @@ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $s
+       // indexes and create all the new ones, except for all the priors that
+       // exist unchanged.
+       $table_mapping = $this->getTableMapping($this->entityType, [$storage_definition]);
+-      $table = $table_mapping->getDedicatedDataTableName($original);
+-      $revision_table = $table_mapping->getDedicatedRevisionTableName($original);
++      if ($this->database->driver() == 'mongodb') {
++        if ($this->entityType->isRevisionable()) {
++          $dedicated_all_revisions_table = $table_mapping->getJsonStorageDedicatedTableName($original, $this->storage->getJsonStorageAllRevisionsTable());
++          $dedicated_current_revision_table = $table_mapping->getJsonStorageDedicatedTableName($original, $this->storage->getJsonStorageCurrentRevisionTable());
++          $dedicated_latest_revision_table = $table_mapping->getJsonStorageDedicatedTableName($original, $this->storage->getJsonStorageLatestRevisionTable());
++        }
++        elseif ($this->entityType->isTranslatable()) {
++          $dedicated_translations_table = $table_mapping->getJsonStorageDedicatedTableName($original, $this->storage->getJsonStorageTranslationsTable());
++        }
++        else {
++          $dedicated_base_table = $table_mapping->getJsonStorageDedicatedTableName($original, $this->storage->getBaseTable());
++        }
++      }
++      else {
++        $table = $table_mapping->getDedicatedDataTableName($original);
++        $revision_table = $table_mapping->getDedicatedRevisionTableName($original);
++      }
+ 
+       // Get the field schemas.
+       $schema = $storage_definition->getSchema();
+@@ -1766,12 +2237,43 @@ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $s
+       foreach ($original_schema['indexes'] as $name => $columns) {
+         if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
+           $real_name = $this->getFieldIndexName($storage_definition, $name);
+-          $this->database->schema()->dropIndex($table, $real_name);
+-          $this->database->schema()->dropIndex($revision_table, $real_name);
++          if ($this->database->driver() == 'mongodb') {
++            if ($this->entityType->isRevisionable()) {
++              $this->database->schema()->dropIndex($dedicated_all_revisions_table, $real_name);
++              $this->database->schema()->dropIndex($dedicated_current_revision_table, $real_name);
++              $this->database->schema()->dropIndex($dedicated_latest_revision_table, $real_name);
++            }
++            elseif ($this->entityType->isTranslatable()) {
++              $this->database->schema()->dropIndex($dedicated_translations_table, $real_name);
++            }
++            else {
++              $this->database->schema()->dropIndex($dedicated_base_table, $real_name);
++            }
++          }
++          else {
++            $this->database->schema()->dropIndex($table, $real_name);
++            $this->database->schema()->dropIndex($revision_table, $real_name);
++          }
++        }
++      }
++
++      if ($this->database->driver() == 'mongodb') {
++        if ($this->entityType->isRevisionable()) {
++          $dedicated_all_revisions_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getJsonStorageAllRevisionsTable());
++          $dedicated_current_revision_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getJsonStorageCurrentRevisionTable());
++          $dedicated_latest_revision_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getJsonStorageLatestRevisionTable());
++        }
++        elseif ($this->entityType->isTranslatable()) {
++          $dedicated_translations_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getJsonStorageTranslationsTable());
+         }
++        else {
++          $dedicated_base_table = $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getBaseTable());
++        }
++      }
++      else {
++        $table = $table_mapping->getDedicatedDataTableName($storage_definition);
++        $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+       }
+-      $table = $table_mapping->getDedicatedDataTableName($storage_definition);
+-      $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+       foreach ($schema['indexes'] as $name => $columns) {
+         if (!isset($original_schema['indexes'][$name]) || $columns != $original_schema['indexes'][$name]) {
+           $real_name = $this->getFieldIndexName($storage_definition, $name);
+@@ -1780,10 +2282,16 @@ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $s
+             // Indexes can be specified as either a column name or an array with
+             // column name and length. Allow for either case.
+             if (is_array($column_name)) {
+-              $real_columns[] = [
+-                $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
+-                $column_name[1],
+-              ];
++              if ($this->database->driver() == 'mongodb') {
++                // MongoDB cannot do anything with the length parameter.
++                $real_columns[] = $table_mapping->getFieldColumnName($storage_definition, (is_array($column_name) ? reset($column_name) : $column_name));
++              }
++              else {
++                $real_columns[] = [
++                  $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
++                  $column_name[1],
++                ];
++              }
+             }
+             else {
+               $real_columns[] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
+@@ -1791,8 +2299,23 @@ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $s
+           }
+           // Check if the index exists because it might already have been
+           // created as part of the earlier entity type update event.
+-          $this->addIndex($table, $real_name, $real_columns, $actual_schema[$table]);
+-          $this->addIndex($revision_table, $real_name, $real_columns, $actual_schema[$revision_table]);
++          if ($this->database->driver() == 'mongodb') {
++            if ($this->entityType->isRevisionable()) {
++              $this->addIndex($dedicated_all_revisions_table, $real_name, $real_columns, $actual_schema[$dedicated_all_revisions_table]);
++              $this->addIndex($dedicated_current_revision_table, $real_name, $real_columns, $actual_schema[$dedicated_current_revision_table]);
++              $this->addIndex($dedicated_latest_revision_table, $real_name, $real_columns, $actual_schema[$dedicated_latest_revision_table]);
++            }
++            elseif ($this->entityType->isTranslatable()) {
++              $this->addIndex($dedicated_translations_table, $real_name, $real_columns, $actual_schema[$dedicated_translations_table]);
++            }
++            else {
++              $this->addIndex($dedicated_base_table, $real_name, $real_columns, $actual_schema[$dedicated_base_table]);
++            }
++          }
++          else {
++            $this->addIndex($table, $real_name, $real_columns, $actual_schema[$table]);
++            $this->addIndex($revision_table, $real_name, $real_columns, $actual_schema[$revision_table]);
++          }
+         }
+       }
+       $this->saveFieldSchemaData($storage_definition, $this->getDedicatedTableSchema($storage_definition));
+@@ -2319,6 +2842,16 @@ protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $stor
+       ],
+     ];
+ 
++    if ($this->database->driver() == 'mongodb') {
++      // MongoDB stores boolean values as a boolean not as an integer.
++      $data_schema['fields']['deleted'] = [
++        'type' => 'bool',
++        'not null' => TRUE,
++        'default' => FALSE,
++        'description' => 'A boolean indicating whether this data item has been deleted',
++      ];
++    }
++
+     // Check that the schema does not include forbidden column names.
+     $schema = $storage_definition->getSchema();
+     $properties = $storage_definition->getPropertyDefinitions();
+@@ -2387,19 +2920,69 @@ protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $stor
+       }
+     }
+ 
+-    $dedicated_table_schema = [$table_mapping->getDedicatedDataTableName($storage_definition) => $data_schema];
++    if ($this->database->driver() == 'mongodb') {
++      // For MongoDB all dedicated tables are embedded tables. Therefor they do
++      // not need a primary key index.
++      unset($data_schema['primary key']);
++      // Removing the added indexes. No doing so can result in the error:
++      // "too many indexes".
++      unset($data_schema['unique keys']);
++      unset($data_schema['indexes']);
++
++      if ($entity_type->isRevisionable()) {
++        // Adding an index for every field can create too many indexes on a single
++        // table. For MongoDB the maximum is 64.
++        // $data_schema['indexes']['primary_key'] = ['entity_id', 'revision_id', 'deleted', 'delta', 'langcode'];
++        $data_schema['fields']['revision_id']['not null'] = TRUE;
++        $data_schema['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
++
++        $dedicated_all_revisions_schema = $data_schema;
++        $dedicated_all_revisions_schema['description'] = "Revision archive storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
++
++        $dedicated_current_revision_schema = $data_schema;
++        $dedicated_current_revision_schema['description'] = "Current revision storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
++
++        $dedicated_latest_revision_schema = $data_schema;
++        $dedicated_latest_revision_schema['description'] = "Latest revision storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
++
++        return [
++          $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getJsonStorageAllRevisionsTable()) => $dedicated_all_revisions_schema,
++          $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getJsonStorageCurrentRevisionTable()) => $dedicated_current_revision_schema,
++          $table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getJsonStorageLatestRevisionTable()) => $dedicated_latest_revision_schema,
++        ];
++      }
++      elseif ($entity_type->isTranslatable()) {
++        // Adding an index for every field can create too many indexes on a single
++        // table. For MongoDB the maximum is 64.
++        // $data_schema['indexes']['primary_key'] = ['entity_id', 'deleted', 'delta', 'langcode'];
++        $data_schema['description'] = "Translations storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
++
++        return [$table_mapping->getJsonStorageDedicatedTableName($storage_definition, $this->storage->getJsonStorageTranslationsTable()) => $data_schema];
++      }
++      else {
++        // Adding an index for every field can create too many indexes on a single
++        // table. For MongoDB the maximum is 64.
++        // $data_schema['indexes']['primary_key'] = ['entity_id', 'deleted', 'delta'];
++        $data_schema['description'] = "Storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
+ 
+-    // If the entity type is revisionable, construct the revision table.
+-    if ($entity_type->isRevisionable()) {
+-      $revision_schema = $data_schema;
+-      $revision_schema['description'] = $description_revision;
+-      $revision_schema['primary key'] = ['entity_id', 'revision_id', 'deleted', 'delta', 'langcode'];
+-      $revision_schema['fields']['revision_id']['not null'] = TRUE;
+-      $revision_schema['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
+-      $dedicated_table_schema += [$table_mapping->getDedicatedRevisionTableName($storage_definition) => $revision_schema];
++        return [$table_mapping->getJsonStorageDedicatedTableName($storage_definition, $entity_type->getBaseTable()) => $data_schema];
++      }
+     }
++    else {
++      $dedicated_table_schema = [$table_mapping->getDedicatedDataTableName($storage_definition) => $data_schema];
++
++      // If the entity type is revisionable, construct the revision table.
++      if ($entity_type->isRevisionable()) {
++        $revision_schema = $data_schema;
++        $revision_schema['description'] = $description_revision;
++        $revision_schema['primary key'] = ['entity_id', 'revision_id', 'deleted', 'delta', 'langcode'];
++        $revision_schema['fields']['revision_id']['not null'] = TRUE;
++        $revision_schema['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
++        $dedicated_table_schema += [$table_mapping->getDedicatedRevisionTableName($storage_definition) => $revision_schema];
++      }
+ 
+-    return $dedicated_table_schema;
++      return $dedicated_table_schema;
++    }
+   }
+ 
+   /**
+diff --git a/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
+index 72e49111bac93145c18577db7022aaef7bf2076e..4bf5fff7d4b19931800a0784316317d0e11bb64d 100644
+--- a/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
++++ b/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
+@@ -57,16 +57,33 @@ public function onRouterRebuild($event) {
+   protected function menuLinksRebuild() {
+     if ($this->lock->acquire(__FUNCTION__)) {
+       try {
+-        $transaction = $this->connection->startTransaction();
++        if ($this->connection->driver() == 'mongodb') {
++          $session = $this->connection->getMongodbSession();
++          $session_started = FALSE;
++          if (!$session->isInTransaction()) {
++            $session->startTransaction();
++            $session_started = TRUE;
++          }
++        }
++        else {
++          $transaction = $this->connection->startTransaction();
++        }
+         // Ensure the menu links are up to date.
+         $this->menuLinkManager->rebuild();
+         // Ignore any database replicas temporarily.
+         $this->replicaKillSwitch->trigger();
++
++        if (isset($session) && $session->isInTransaction() && $session_started) {
++          $session->commitTransaction();
++        }
+       }
+       catch (\Exception $e) {
+         if (isset($transaction)) {
+           $transaction->rollBack();
+         }
++        if (isset($session) && $session->isInTransaction() && $session_started) {
++          $session->abortTransaction();
++        }
+         Error::logException($this->logger, $e);
+       }
+ 
+diff --git a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
+index 81682b8278ea3dab9fc3d5176de147bbeba7ed22..c6144d3e491cb45f0c07903365908bdbf9df1f15 100644
+--- a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
++++ b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
+@@ -161,6 +161,34 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
+     $database['driver'] = $driver;
+     $database = array_merge($database, $this->databaseDriverList->get($driver)->getAutoloadInfo());
+ 
++    // For MongoDB there are always multiple hosts. They are in the
++    // settings.php file stored in the hosts array. Change the FORM API values
++    // to the hosts array for the settings.php file.
++    if ($driver == 'Drupal\mongodb\Driver\Database\mongodb') {
++      $hosts = [];
++      foreach ([1, 2, 3] as $i) {
++        if (!empty($database['host' . $i]['host'])) {
++          // Add the port setting when it is given and it is not the default
++          // port.
++          if (isset($database['host' . $i]['port']) && ($database['host' . $i]['port'] != 27017)) {
++            $hosts[] = [
++              'host' => $database['host' . $i]['host'],
++              'port' => $database['host' . $i]['port'],
++            ];
++          }
++          else {
++            $hosts[] = [
++              'host' => $database['host' . $i]['host'],
++            ];
++          }
++        }
++        unset($database['host' . $i]);
++      }
++      if (!empty($hosts)) {
++        $database['hosts'] = $hosts;
++      }
++    }
++
+     $form_state->set('database', $database);
+ 
+     foreach ($this->getDatabaseErrors($database, $form_state->getValue('settings_file')) as $name => $message) {
+diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
+index 34f94c0abffd3e9f1df91e26277b6d03799c12f0..629c66e5283b542f8fbbb06d8186e0c3e6ae81aa 100644
+--- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
++++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
+@@ -5,6 +5,8 @@
+ use Drupal\Component\Datetime\TimeInterface;
+ use Drupal\Component\Serialization\SerializationInterface;
+ use Drupal\Core\Database\Connection;
++use Drupal\mongodb\Driver\Database\mongodb\Statement;
++use MongoDB\BSON\UTCDateTime;
+ 
+ /**
+  * Defines a default key/value store implementation for expiring items.
+@@ -42,17 +44,34 @@ public function __construct(
+    * {@inheritdoc}
+    */
+   public function has($key) {
+-    try {
+-      return (bool) $this->connection->query('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :key AND [expire] > :now', [
+-        ':collection' => $this->collection,
+-        ':key' => $key,
+-        ':now' => $this->time->getRequestTime(),
+-      ])->fetchField();
+-    }
+-    catch (\Exception $e) {
+-      $this->catchException($e);
++    if ($this->connection->driver() == 'mongodb') {
++      $prefixed_table = $this->connection->getPrefix() . $this->table;
++      $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++        ['collection' => ['$eq' => $this->collection], 'expire' => ['$gt' => new UTCDateTime($this->time->getRequestTime() * 1000)], 'name' => ['$eq' => (string) $key]],
++        [
++          'projection' => ['_id' => 1],
++          'session' => $this->connection->getMongodbSession(),
++        ]
++      );
++
++      if ($cursor && !empty($cursor->toArray())) {
++        return TRUE;
++      }
+       return FALSE;
+     }
++    else {
++      try {
++        return (bool) $this->connection->query('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :key AND [expire] > :now', [
++          ':collection' => $this->collection,
++          ':key' => $key,
++          ':now' => $this->time->getRequestTime(),
++        ])->fetchField();
++      }
++      catch (\Exception $e) {
++        $this->catchException($e);
++        return FALSE;
++      }
++    }
+   }
+ 
+   /**
+@@ -60,13 +79,31 @@ public function has($key) {
+    */
+   public function getMultiple(array $keys) {
+     try {
+-      $values = $this->connection->query(
+-        'SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [expire] > :now AND [name] IN ( :keys[] ) AND [collection] = :collection',
+-        [
+-          ':now' => $this->time->getRequestTime(),
+-          ':keys[]' => $keys,
+-          ':collection' => $this->collection,
+-        ])->fetchAllKeyed();
++      if ($this->connection->driver() == 'mongodb') {
++        foreach ($keys as &$key) {
++          $key = (string) $key;
++        }
++        $prefixed_table = $this->connection->getPrefix() . $this->table;
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          ['collection' => ['$eq' => $this->collection], 'expire' => ['$gt' => new UTCDateTime($this->time->getRequestTime() * 1000)], 'name' => ['$in' => $keys]],
++          [
++            'projection' => ['name' => 1, 'value' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ]
++        );
++
++        $statement = new Statement($this->connection, $cursor, ['name', 'value']);
++        $values = $statement->execute()->fetchAllKeyed();
++      }
++      else {
++        $values = $this->connection->query(
++          'SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [expire] > :now AND [name] IN ( :keys[] ) AND [collection] = :collection',
++          [
++            ':now' => $this->time->getRequestTime(),
++            ':keys[]' => $keys,
++            ':collection' => $this->collection,
++          ])->fetchAllKeyed();
++      }
+       return array_map([$this->serializer, 'decode'], $values);
+     }
+     catch (\Exception $e) {
+@@ -84,12 +121,27 @@ public function getMultiple(array $keys) {
+    */
+   public function getAll() {
+     try {
+-      $values = $this->connection->query(
+-        'SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [expire] > :now',
+-        [
+-          ':collection' => $this->collection,
+-          ':now' => $this->time->getRequestTime(),
+-        ])->fetchAllKeyed();
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . $this->table;
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          ['collection' => ['$eq' => (string) $this->collection], 'expire' => ['$gt' => new UTCDateTime($this->time->getRequestTime() * 1000)]],
++          [
++            'projection' => ['name' => 1, 'value' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ]
++        );
++
++        $statement = new Statement($this->connection, $cursor, ['name', 'value']);
++        $values = $statement->execute()->fetchAllKeyed();
++      }
++      else {
++        $values = $this->connection->query(
++          'SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [expire] > :now',
++          [
++            ':collection' => $this->collection,
++            ':now' => $this->time->getRequestTime(),
++          ])->fetchAllKeyed();
++      }
+       return array_map([$this->serializer, 'decode'], $values);
+     }
+     catch (\Exception $e) {
+@@ -111,9 +163,13 @@ public function getAll() {
+    *   The time to live for items, in seconds.
+    */
+   protected function doSetWithExpire($key, $value, $expire) {
++    if (($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $this->connection->merge($this->table)
+       ->keys([
+-        'name' => $key,
++        'name' => (string) $key,
+         'collection' => $this->collection,
+       ])
+       ->fields([
+@@ -201,8 +257,8 @@ public function deleteMultiple(array $keys) {
+   /**
+    * Defines the schema for the key_value_expire table.
+    */
+-  public static function schemaDefinition() {
+-    return [
++  public function schemaDefinition() {
++    $schema = [
+       'description' => 'Generic key/value storage table with an expiration.',
+       'fields' => [
+         'collection' => [
+@@ -238,6 +294,12 @@ public static function schemaDefinition() {
+         'expire' => ['expire'],
+       ],
+     ];
++
++    if ($this->connection->driver() == 'mongodb') {
++      $schema['fields']['expire']['type'] = 'date';
++    }
++
++    return $schema;
+   }
+ 
+ }
+diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
+index 44d5d9df13fba12f5a31f374c59b1acde6d46f34..b931b7a759e3e06a93c3953e522af9ad3222ac3f 100644
+--- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
++++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
+@@ -2,11 +2,13 @@
+ 
+ namespace Drupal\Core\KeyValueStore;
+ 
++use Drupal\Component\Assertion\Inspector;
+ use Drupal\Component\Serialization\SerializationInterface;
+ use Drupal\Core\Database\Query\Merge;
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\DatabaseException;
+ use Drupal\Core\DependencyInjection\DependencySerializationTrait;
++use Drupal\mongodb\Driver\Database\mongodb\Statement;
+ 
+ /**
+  * Defines a default key/value store implementation.
+@@ -39,6 +41,15 @@ class DatabaseStorage extends StorageBase {
+    */
+   protected $table;
+ 
++  /**
++   * Indicator for the existence of the database table.
++   *
++   *  This variable is only used by the database driver for MongoDB.
++   *
++   * @var bool
++   */
++  protected $tableExists = FALSE;
++
+   /**
+    * Overrides Drupal\Core\KeyValueStore\StorageBase::__construct().
+    *
+@@ -62,16 +73,33 @@ public function __construct($collection, SerializationInterface $serializer, Con
+    * {@inheritdoc}
+    */
+   public function has($key) {
+-    try {
+-      return (bool) $this->connection->query('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :key', [
+-        ':collection' => $this->collection,
+-        ':key' => $key,
+-      ])->fetchField();
+-    }
+-    catch (\Exception $e) {
+-      $this->catchException($e);
++    if ($this->connection->driver() == 'mongodb') {
++      $prefixed_table = $this->connection->getPrefix() . $this->table;
++      $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++        ['collection' => ['$eq' => (string) $this->collection], 'name' => ['$eq' => (string) $key]],
++        [
++          'projection' => ['_id' => 1],
++          'session' => $this->connection->getMongodbSession(),
++        ]
++      );
++
++      if ($cursor && !empty($cursor->toArray())) {
++        return TRUE;
++      }
+       return FALSE;
+     }
++    else {
++      try {
++        return (bool) $this->connection->query('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :key', [
++          ':collection' => $this->collection,
++          ':key' => $key,
++        ])->fetchField();
++      }
++      catch (\Exception $e) {
++        $this->catchException($e);
++        return FALSE;
++      }
++    }
+   }
+ 
+   /**
+@@ -80,7 +108,33 @@ public function has($key) {
+   public function getMultiple(array $keys) {
+     $values = [];
+     try {
+-      $result = $this->connection->query('SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [name] IN ( :keys[] ) AND [collection] = :collection', [':keys[]' => $keys, ':collection' => $this->collection])->fetchAllAssoc('name');
++      if ($this->connection->driver() == 'mongodb') {
++        if (empty($keys)) {
++          return [];
++        }
++
++        // Check that key values are string values.
++        assert(Inspector::assertAllStrings($keys), 'All keys must be strings.');
++
++        $prefixed_table = $this->connection->getPrefix() . $this->table;
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          [
++            'collection' => ['$eq' => (string) $this->collection],
++            'name' => ['$in' => $keys],
++          ],
++          [
++            'projection' => ['name' => 1, 'value' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ],
++        );
++
++        $statement = new Statement($this->connection, $cursor, ['name', 'value']);
++        $result = $statement->execute()->fetchAllAssoc('name');
++
++      }
++      else {
++        $result = $this->connection->query('SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [name] IN ( :keys[] ) AND [collection] = :collection', [':keys[]' => $keys, ':collection' => $this->collection])->fetchAllAssoc('name');
++      }
+       foreach ($keys as $key) {
+         if (isset($result[$key])) {
+           $values[$key] = $this->serializer->decode($result[$key]->value);
+@@ -100,7 +154,22 @@ public function getMultiple(array $keys) {
+    */
+   public function getAll() {
+     try {
+-      $result = $this->connection->query('SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection', [':collection' => $this->collection]);
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . $this->table;
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          ['collection' => ['$eq' => (string) $this->collection]],
++          [
++            'projection' => ['name' => 1, 'value' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ]
++        );
++
++        $statement = new Statement($this->connection, $cursor, ['name', 'value']);
++        $result = $statement->execute();
++      }
++      else {
++        $result = $this->connection->query('SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection', [':collection' => $this->collection]);
++      }
+     }
+     catch (\Exception $e) {
+       $this->catchException($e);
+@@ -140,6 +209,10 @@ protected function doSet($key, $value) {
+    * {@inheritdoc}
+    */
+   public function set($key, $value) {
++    if (($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     try {
+       $this->doSet($key, $value);
+     }
+@@ -168,6 +241,10 @@ public function set($key, $value) {
+    *   TRUE if the data was set, FALSE if it already existed.
+    */
+   public function doSetIfNotExists($key, $value) {
++    if (($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $result = $this->connection->merge($this->table)
+       ->insertFields([
+         'collection' => $this->collection,
+@@ -175,7 +252,7 @@ public function doSetIfNotExists($key, $value) {
+         'value' => $this->serializer->encode($value),
+       ])
+       ->condition('collection', $this->collection)
+-      ->condition('name', $key)
++      ->condition('name', (string) $key)
+       ->execute();
+     return $result == Merge::STATUS_INSERT;
+   }
+@@ -202,11 +279,15 @@ public function setIfNotExists($key, $value) {
+    * {@inheritdoc}
+    */
+   public function rename($key, $new_key) {
++    if (($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     try {
+       $this->connection->update($this->table)
+         ->fields(['name' => $new_key])
+         ->condition('collection', $this->collection)
+-        ->condition('name', $key)
++        ->condition('name', (string) $key)
+         ->execute();
+     }
+     catch (\Exception $e) {
+@@ -218,6 +299,14 @@ public function rename($key, $new_key) {
+    * {@inheritdoc}
+    */
+   public function deleteMultiple(array $keys) {
++    if (($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      $this->tableExists = $this->ensureTableExists();
++
++      foreach ($keys as &$key) {
++        $key = (string) $key;
++      }
++    }
++
+     // Delete in chunks when a large array is passed.
+     while ($keys) {
+       try {
+@@ -236,6 +325,10 @@ public function deleteMultiple(array $keys) {
+    * {@inheritdoc}
+    */
+   public function deleteAll() {
++    if (($this->connection->driver() == 'mongodb') && !$this->tableExists) {
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     try {
+       $this->connection->delete($this->table)
+         ->condition('collection', $this->collection)
+@@ -290,8 +383,8 @@ protected function catchException(\Exception $e) {
+   /**
+    * Defines the schema for the key_value table.
+    */
+-  public static function schemaDefinition() {
+-    return [
++  public function schemaDefinition() {
++    $schema = [
+       'description' => 'Generic key-value storage table. See the state system for an example.',
+       'fields' => [
+         'collection' => [
+@@ -317,6 +410,12 @@ public static function schemaDefinition() {
+       ],
+       'primary key' => ['collection', 'name'],
+     ];
++
++    if ($this->connection->driver() == 'mongodb') {
++      $schema['fields']['expire']['type'] = 'date';
++    }
++
++    return $schema;
+   }
+ 
+ }
+diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php
+index 4215a9b5d93ece54ddca639f50fd4ae691136bd2..cb835f9577487a2946645f6f211c9d00d6c87e5c 100644
+--- a/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php
++++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php
+@@ -5,6 +5,7 @@
+ use Drupal\Component\Datetime\TimeInterface;
+ use Drupal\Component\Serialization\SerializationInterface;
+ use Drupal\Core\Database\Connection;
++use MongoDB\BSON\UTCDateTime;
+ 
+ /**
+  * Defines the key/value store factory for the database backend.
+@@ -50,8 +51,12 @@ public function get($collection) {
+    */
+   public function garbageCollection() {
+     try {
++      $now = $this->time->getRequestTime();
++      if ($this->connection->driver() == 'mongodb') {
++        $now = new UTCDateTime($now * 1000);
++      }
+       $this->connection->delete('key_value_expire')
+-        ->condition('expire', $this->time->getRequestTime(), '<')
++        ->condition('expire', $now, '<')
+         ->execute();
+     }
+     catch (\Exception $e) {
+diff --git a/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
+index b877cf48592d62cb2218e8cb224f1eca1ee6fe2e..485b2f9484d4c88decf8e219e56e039c79b5145d 100644
+--- a/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
++++ b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
+@@ -134,7 +134,7 @@ public function checkNodeAccess(array $tree) {
+       else {
+         $access_result->addCacheContexts(['user.node_grants:view']);
+         if (!$this->moduleHandler->hasImplementations('node_grants') && !$this->account->hasPermission('view any unpublished content')) {
+-          $query->condition('status', NodeInterface::PUBLISHED);
++          $query->condition('status', (bool) NodeInterface::PUBLISHED);
+         }
+       }
+ 
+diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
+index cc25a39915252927f58a6915f55be7dae94fbb42..d3545d0db2a3d0ab1942a120fc586446ba927cb8 100644
+--- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
++++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
+@@ -53,6 +53,15 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
+    */
+   protected $table;
+ 
++  /**
++   * Indicator for the existence of the database table.
++   *
++   * This variable is only used by the database driver for MongoDB.
++   *
++   * @var bool
++   */
++  protected $tableExists = FALSE;
++
+   /**
+    * Additional database connection options to use in queries.
+    *
+@@ -212,6 +221,12 @@ protected function purgeMultiple(array $ids) {
+    *   failed.
+    */
+   protected function safeExecuteSelect(SelectInterface $query) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     try {
+       return $query->execute();
+     }
+@@ -256,6 +271,12 @@ public function save(array $link) {
+    *   depth.
+    */
+   protected function doSave(array $link) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $affected_menus = [];
+ 
+     // Get the existing definition if it exists. This does not use
+@@ -285,7 +306,17 @@ protected function doSave(array $link) {
+     }
+ 
+     try {
+-      $transaction = $this->connection->startTransaction();
++      if ($this->connection->driver() == 'mongodb') {
++        $session = $this->connection->getMongodbSession();
++        $session_started = FALSE;
++        if (!$session->isInTransaction()) {
++          $session->startTransaction();
++          $session_started = TRUE;
++        }
++      }
++      else {
++        $transaction = $this->connection->startTransaction();
++      }
+       if (!$original) {
+         // Generate a new mlid.
+         $link['mlid'] = $this->connection->insert($this->table, $this->options)
+@@ -296,18 +327,25 @@ protected function doSave(array $link) {
+       // We may be moving the link to a new menu.
+       $affected_menus[$fields['menu_name']] = $fields['menu_name'];
+       $query = $this->connection->update($this->table, $this->options);
+-      $query->condition('mlid', $link['mlid']);
++      $query->condition('mlid', (int) $link['mlid']);
+       $query->fields($fields)
+         ->execute();
+       if ($original) {
+         $this->updateParentalStatus($original);
+       }
+       $this->updateParentalStatus($link);
++
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->commitTransaction();
++      }
+     }
+     catch (\Exception $e) {
+       if (isset($transaction)) {
+         $transaction->rollBack();
+       }
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->abortTransaction();
++      }
+       throw $e;
+     }
+     return $affected_menus;
+@@ -426,6 +464,12 @@ public function getSubtreeHeight($id) {
+    *   Returns the relative depth.
+    */
+   protected function doFindChildrenRelativeDepth(array $original) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->select($this->table, NULL, $this->options);
+     $query->addField($this->table, 'depth');
+     $query->condition('menu_name', $original['menu_name']);
+@@ -433,7 +477,7 @@ protected function doFindChildrenRelativeDepth(array $original) {
+     $query->range(0, 1);
+ 
+     for ($i = 1; $i <= static::MAX_DEPTH && $original["p$i"]; $i++) {
+-      $query->condition("p$i", $original["p$i"]);
++      $query->condition("p$i", (int) $original["p$i"]);
+     }
+ 
+     $max_depth = $this->safeExecuteSelect($query)->fetchField();
+@@ -502,6 +546,12 @@ protected function setParents(array &$fields, $parent, array $original) {
+    *   The original menu link.
+    */
+   protected function moveChildren($fields, $original) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->update($this->table, $this->options);
+ 
+     $query->fields(['menu_name' => $fields['menu_name']]);
+@@ -586,11 +636,17 @@ protected function findParent($link, $original) {
+    *   The link to get a parent ID from.
+    */
+   protected function updateParentalStatus(array $link) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     // If parent is empty, there is nothing to update.
+     if (!empty($link['parent'])) {
+       // Check if at least one visible child exists in the table.
+       $query = $this->connection->select($this->table, NULL, $this->options);
+-      $query->addExpression('1');
++      $query->addExpressionConstant('1');
+       $query->range(0, 1);
+       $query
+         ->condition('menu_name', $link['menu_name'])
+@@ -633,6 +689,12 @@ protected function prepareLink(array $link, $intersect = FALSE) {
+    * {@inheritdoc}
+    */
+   public function loadByProperties(array $properties) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->select($this->table, NULL, $this->options);
+     $query->fields($this->table, $this->definitionFields());
+     foreach ($properties as $name => $value) {
+@@ -653,6 +715,12 @@ public function loadByProperties(array $properties) {
+    * {@inheritdoc}
+    */
+   public function loadByRoute($route_name, array $route_parameters = [], $menu_name = NULL) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     // Sort the route parameters so that the query string will be the same.
+     asort($route_parameters);
+     // Since this will be urlencoded, it's safe to store and match against a
+@@ -685,6 +753,12 @@ public function loadMultiple(array $ids) {
+     $missing_ids = array_diff($ids, array_keys($this->definitions));
+ 
+     if ($missing_ids) {
++      if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++        // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++        // without the correct validation.
++        $this->tableExists = $this->ensureTableExists();
++      }
++
+       $query = $this->connection->select($this->table, NULL, $this->options);
+       $query->fields($this->table, $this->definitionFields());
+       $query->condition('id', $missing_ids, 'IN');
+@@ -731,6 +805,12 @@ protected function loadFull($id) {
+    *   The loaded menu link definitions.
+    */
+   protected function loadFullMultiple(array $ids) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->select($this->table, NULL, $this->options);
+     $query->fields($this->table);
+     $query->condition('id', $ids, 'IN');
+@@ -749,6 +829,12 @@ protected function loadFullMultiple(array $ids) {
+    * {@inheritdoc}
+    */
+   public function getRootPathIds($id) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $subquery = $this->connection->select($this->table, NULL, $this->options);
+     // @todo Consider making this dynamic based on static::MAX_DEPTH or from the
+     //   schema if that is generated using static::MAX_DEPTH.
+@@ -773,15 +859,21 @@ public function getRootPathIds($id) {
+    * {@inheritdoc}
+    */
+   public function getExpanded($menu_name, array $parents) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     // @todo Go back to tracking in state or some other way which menus have
+     //   expanded links? https://www.drupal.org/node/2302187
+     do {
+       $query = $this->connection->select($this->table, NULL, $this->options);
+       $query->fields($this->table, ['id']);
+       $query->condition('menu_name', $menu_name);
+-      $query->condition('expanded', 1);
+-      $query->condition('has_children', 1);
+-      $query->condition('enabled', 1);
++      $query->condition('expanded', TRUE);
++      $query->condition('has_children', TRUE);
++      $query->condition('enabled', TRUE);
+       $query->condition('parent', $parents, 'IN');
+       $query->condition('id', $parents, 'NOT IN');
+       $result = $this->safeExecuteSelect($query)->fetchAllKeyed(0, 0);
+@@ -858,6 +950,12 @@ public function loadTreeData($menu_name, MenuTreeParameters $parameters) {
+    *   depth-first.
+    */
+   protected function loadLinks($menu_name, MenuTreeParameters $parameters) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->select($this->table, NULL, $this->options);
+     $query->fields($this->table);
+ 
+@@ -876,7 +974,7 @@ protected function loadLinks($menu_name, MenuTreeParameters $parameters) {
+       // tree. In other words: we exclude everything unreachable from the
+       // custom root.
+       for ($i = 1; $i <= $root['depth']; $i++) {
+-        $query->condition("p$i", $root["p$i"]);
++        $query->condition("p$i", (int) $root["p$i"]);
+       }
+ 
+       // When specifying a custom root, the menu is determined by that root.
+@@ -917,10 +1015,10 @@ protected function loadLinks($menu_name, MenuTreeParameters $parameters) {
+       $query->condition('parent', $parameters->expandedParents, 'IN');
+     }
+     if (isset($parameters->minDepth) && $parameters->minDepth > 1) {
+-      $query->condition('depth', $parameters->minDepth, '>=');
++      $query->condition('depth', (int) $parameters->minDepth, '>=');
+     }
+     if (isset($parameters->maxDepth)) {
+-      $query->condition('depth', $parameters->maxDepth, '<=');
++      $query->condition('depth', (int) $parameters->maxDepth, '<=');
+     }
+     // Add custom query conditions, if any were passed.
+     if (!empty($parameters->conditions)) {
+@@ -1005,6 +1103,12 @@ public function loadSubtreeData($id, $max_relative_depth = NULL) {
+    * {@inheritdoc}
+    */
+   public function menuNameInUse($menu_name) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->select($this->table, NULL, $this->options);
+     $query->addField($this->table, 'mlid');
+     $query->condition('menu_name', $menu_name);
+@@ -1016,6 +1120,12 @@ public function menuNameInUse($menu_name) {
+    * {@inheritdoc}
+    */
+   public function getMenuNames() {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->select($this->table, NULL, $this->options);
+     $query->addField($this->table, 'menu_name');
+     $query->distinct();
+@@ -1026,6 +1136,12 @@ public function getMenuNames() {
+    * {@inheritdoc}
+    */
+   public function countMenuLinks($menu_name = NULL) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->select($this->table, NULL, $this->options);
+     if ($menu_name) {
+       $query->condition('menu_name', $menu_name);
+@@ -1041,11 +1157,18 @@ public function getAllChildIds($id) {
+     if (!$root) {
+       return [];
+     }
++
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $query = $this->connection->select($this->table, NULL, $this->options);
+     $query->fields($this->table, ['id']);
+     $query->condition('menu_name', $root['menu_name']);
+     for ($i = 1; $i <= $root['depth']; $i++) {
+-      $query->condition("p$i", $root["p$i"]);
++      $query->condition("p$i", (int) $root["p$i"]);
+     }
+     // The next p column should not be empty. This excludes the root link.
+     $query->condition("p$i", 0, '>');
+@@ -1432,6 +1555,12 @@ protected static function schemaDefinition() {
+    *   A list of menu link IDs that no longer exist.
+    */
+   protected function findNoLongerExistingLinks(array $definitions) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     if ($definitions) {
+       $query = $this->connection->select($this->table, NULL, $this->options);
+       $query->addField($this->table, 'id');
+@@ -1455,6 +1584,12 @@ protected function findNoLongerExistingLinks(array $definitions) {
+    *   A list of menu link IDs to be purged.
+    */
+   protected function doDeleteMultiple(array $ids) {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $this->connection->delete($this->table, $this->options)
+       ->condition('id', $ids, 'IN')
+       ->execute();
+diff --git a/core/lib/Drupal/Core/Queue/Batch.php b/core/lib/Drupal/Core/Queue/Batch.php
+index 1b71959ba20341d843fba1900ff6c4a1bb76a7fd..1c146c41fb667b527e759b9bbdc78d950ef2e126 100644
+--- a/core/lib/Drupal/Core/Queue/Batch.php
++++ b/core/lib/Drupal/Core/Queue/Batch.php
+@@ -26,7 +26,14 @@ class Batch extends DatabaseQueue {
+    */
+   public function claimItem($lease_time = 0) {
+     try {
+-      $item = $this->connection->queryRange('SELECT [data], [item_id] FROM {queue} q WHERE [name] = :name ORDER BY [item_id] ASC', 0, 1, [':name' => $this->name])->fetchObject();
++      $item = $this->connection->select('queue', 'q')
++        ->fields('q', ['data', 'item_id'])
++        ->condition('name', $this->name)
++        ->orderBy('item_id', 'ASC')
++        ->range(0, 1)
++        ->execute()
++        ->fetchObject();
++
+       if ($item) {
+         $item->data = unserialize($item->data);
+         return $item;
+diff --git a/core/lib/Drupal/Core/Queue/DatabaseQueue.php b/core/lib/Drupal/Core/Queue/DatabaseQueue.php
+index ca2b8339d5cac8a13fe466ae0a3a4db2cd76c26a..da5c7263ff23f893db2c0f56e16fa14cbd207310 100644
+--- a/core/lib/Drupal/Core/Queue/DatabaseQueue.php
++++ b/core/lib/Drupal/Core/Queue/DatabaseQueue.php
+@@ -34,6 +34,15 @@ class DatabaseQueue implements ReliableQueueInterface, QueueGarbageCollectionInt
+    */
+   protected $connection;
+ 
++  /**
++   * Indicator for the existence of the database table.
++   *
++   *  This variable is only used by the database driver for MongoDB.
++   *
++   * @var bool
++   */
++  protected $tableExists = FALSE;
++
+   /**
+    * Constructs a \Drupal\Core\Queue\DatabaseQueue object.
+    *
+@@ -51,23 +60,34 @@ public function __construct($name, Connection $connection) {
+    * {@inheritdoc}
+    */
+   public function createItem($data) {
+-    $try_again = FALSE;
+-    try {
+-      $id = $this->doCreateItem($data);
+-    }
+-    catch (\Exception $e) {
+-      // If there was an exception, try to create the table.
+-      if (!$try_again = $this->ensureTableExists()) {
+-        // If the exception happened for other reason than the missing table,
+-        // propagate the exception.
+-        throw $e;
++    if ($this->connection->driver() == 'mongodb') {
++      // For MongoDB the table needs to exist. Otherwise MongoDB creates one
++      // without the correct validation.
++      if (!$this->tableExists) {
++        $this->tableExists = $this->ensureTableExists();
+       }
++
++      return $this->doCreateItem($data);
+     }
+-    // Now that the table has been created, try again if necessary.
+-    if ($try_again) {
+-      $id = $this->doCreateItem($data);
++    else {
++      $try_again = FALSE;
++      try {
++        $id = $this->doCreateItem($data);
++      }
++      catch (\Exception $e) {
++        // If there was an exception, try to create the table.
++        if (!$try_again = $this->ensureTableExists()) {
++          // If the exception happened for other reason than the missing table,
++          // propagate the exception.
++          throw $e;
++        }
++      }
++      // Now that the table has been created, try again if necessary.
++      if ($try_again) {
++        $id = $this->doCreateItem($data);
++      }
++      return $id;
+     }
+-    return $id;
+   }
+ 
+   /**
+@@ -101,8 +121,17 @@ protected function doCreateItem($data) {
+    */
+   public function numberOfItems() {
+     try {
+-      return (int) $this->connection->query('SELECT COUNT([item_id]) FROM {' . static::TABLE_NAME . '} WHERE [name] = :name', [':name' => $this->name])
+-        ->fetchField();
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . static::TABLE_NAME;
++        return $this->connection->getConnection()->selectCollection($prefixed_table)->count(
++          ['name' => ['$eq' => $this->name]],
++          ['session' => $this->connection->getMongodbSession()]
++        );
++      }
++      else {
++        return (int) $this->connection->query('SELECT COUNT([item_id]) FROM {' . static::TABLE_NAME . '} WHERE [name] = :name', [':name' => $this->name])
++          ->fetchField();
++      }
+     }
+     catch (\Exception $e) {
+       $this->catchException($e);
+@@ -121,7 +150,20 @@ public function claimItem($lease_time = 30) {
+     // are no unclaimed items left.
+     while (TRUE) {
+       try {
+-        $item = $this->connection->queryRange('SELECT [data], [created], [item_id] FROM {' . static::TABLE_NAME . '} q WHERE [expire] = 0 AND [name] = :name ORDER BY [created], [item_id] ASC', 0, 1, [':name' => $this->name])->fetchObject();
++        if ($this->connection->driver() == 'mongodb') {
++          $item = $this->connection->select(static::TABLE_NAME, 'b')
++            ->fields('b', ['data', 'created', 'item_id'])
++            ->condition('expire', 0)
++            ->condition('name', $this->name)
++            ->orderBy('created')
++            ->orderBy('item_id')
++            ->range(0, 1)
++            ->execute()
++            ->fetchObject();
++        }
++        else {
++          $item = $this->connection->queryRange('SELECT [data], [created], [item_id] FROM {' . static::TABLE_NAME . '} q WHERE [expire] = 0 AND [name] = :name ORDER BY [created], [item_id] ASC', 0, 1, [':name' => $this->name])->fetchObject();
++        }
+       }
+       catch (\Exception $e) {
+         $this->catchException($e);
+@@ -143,7 +185,7 @@ public function claimItem($lease_time = 30) {
+         ->fields([
+           'expire' => \Drupal::time()->getCurrentTime() + $lease_time,
+         ])
+-        ->condition('item_id', $item->item_id)
++        ->condition('item_id', (int) $item->item_id)
+         ->condition('expire', 0);
+       // If there are affected rows, this update succeeded.
+       if ($update->execute()) {
+@@ -162,7 +204,7 @@ public function releaseItem($item) {
+         ->fields([
+           'expire' => 0,
+         ])
+-        ->condition('item_id', $item->item_id);
++        ->condition('item_id', (int) $item->item_id);
+       return (bool) $update->execute();
+     }
+     catch (\Exception $e) {
+@@ -189,7 +231,7 @@ public function delayItem($item, int $delay) {
+         ->fields([
+           'expire' => $expire,
+         ])
+-        ->condition('item_id', $item->item_id);
++        ->condition('item_id', (int) $item->item_id);
+       return (bool) $update->execute();
+     }
+     catch (\Exception $e) {
+@@ -205,7 +247,7 @@ public function delayItem($item, int $delay) {
+   public function deleteItem($item) {
+     try {
+       $this->connection->delete(static::TABLE_NAME)
+-        ->condition('item_id', $item->item_id)
++        ->condition('item_id', (int) $item->item_id)
+         ->execute();
+     }
+     catch (\Exception $e) {
+diff --git a/core/lib/Drupal/Core/Recipe/ConfigConfigurator.php b/core/lib/Drupal/Core/Recipe/ConfigConfigurator.php
+index ad837d5dc35cebd5e0efae3c3ee449993baa6494..56cc746cc898e9784f47008ef7a7a0629bb48ee7 100644
+--- a/core/lib/Drupal/Core/Recipe/ConfigConfigurator.php
++++ b/core/lib/Drupal/Core/Recipe/ConfigConfigurator.php
+@@ -7,6 +7,8 @@
+ use Drupal\Core\Config\FileStorage;
+ use Drupal\Core\Config\NullStorage;
+ use Drupal\Core\Config\StorageInterface;
++use Drupal\Core\Database\Connection;
++use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+ 
+ /**
+  * @internal
+@@ -14,10 +16,19 @@
+  */
+ final class ConfigConfigurator {
+ 
++  use DependencySerializationTrait;
++
+   public readonly ?string $recipeConfigDirectory;
+ 
+   private readonly bool|array $strict;
+ 
++  /**
++   * The database connection.
++   *
++   * @var \Drupal\Core\Database\Connection
++   */
++  protected Connection $connection;
++
+   /**
+    * @param array $config
+    *   Config options for a recipe.
+@@ -25,11 +36,14 @@ final class ConfigConfigurator {
+    *   The path to the recipe.
+    * @param \Drupal\Core\Config\StorageInterface $active_configuration
+    *   The active configuration storage.
++   * @param \Drupal\Core\Database\Connection $connection
++   *   The database connection.
+    */
+-  public function __construct(public readonly array $config, string $recipe_directory, StorageInterface $active_configuration) {
++  public function __construct(public readonly array $config, string $recipe_directory, StorageInterface $active_configuration, Connection $connection) {
+     $this->recipeConfigDirectory = is_dir($recipe_directory . '/config') ? $recipe_directory . '/config' : NULL;
+     // @todo Consider defaulting this to FALSE in https://drupal.org/i/3478669.
+     $this->strict = $config['strict'] ?? TRUE;
++    $this->connection = $connection;
+ 
+     $recipe_storage = $this->getConfigStorage();
+     if ($this->strict === TRUE) {
+@@ -96,6 +110,17 @@ public function getConfigStorage(): StorageInterface {
+     $storages = [];
+ 
+     if ($this->recipeConfigDirectory) {
++      $directories = explode('/', $this->recipeConfigDirectory);
++      array_pop($directories);
++      $key = array_pop($directories);
++
++      /** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */
++      $module_list = \Drupal::service('extension.list.module');
++      $database_override_path = $module_list->getPath($this->connection->getProvider()) . '/config/overrides/recipes/' . $key;
++      if (is_dir($database_override_path)) {
++        $storages[] = new FileStorage($database_override_path);
++      }
++
+       // Config provided by the recipe should take priority over config from
+       // extensions.
+       $storages[] = new FileStorage($this->recipeConfigDirectory);
+@@ -117,10 +142,25 @@ public function getConfigStorage(): StorageInterface {
+           default => throw new \RuntimeException("$extension is not a theme or module")
+         };
+ 
+-        $storage = new RecipeConfigStorageWrapper(
+-          new FileStorage($path . '/config/install'),
+-          new FileStorage($path . '/config/optional'),
+-        );
++        // Config item can be overridden by the current database driver. Those
++        // overridden config items are stored in the module of the current
++        // database driver in the "config/override" directory.
++        $database_override_path = $module_list->getPath($this->connection->getProvider()) . '/config/overrides/' . $extension;
++        if (is_dir($database_override_path)) {
++          $storage = new RecipeConfigStorageWrapper(
++            new FileStorage($path . '/config/install'),
++            new FileStorage($path . '/config/optional'),
++            new FileStorage($database_override_path . '/install'),
++            new FileStorage($database_override_path . '/optional'),
++          );
++        }
++        else {
++          $storage = new RecipeConfigStorageWrapper(
++            new FileStorage($path . '/config/install'),
++            new FileStorage($path . '/config/optional'),
++          );
++        }
++
+         // If we get here, $names is either '*', or a list of config names
+         // provided by the current extension. In the latter case, we only want
+         // to import the config that is in the list, so use an
+diff --git a/core/lib/Drupal/Core/Recipe/RecipeConfigStorageWrapper.php b/core/lib/Drupal/Core/Recipe/RecipeConfigStorageWrapper.php
+index 9af54bfcb733b8e0d472189eefb8814cddcffd40..df7e81bd17a3d543aa0ee9828d25258daffa3210 100644
+--- a/core/lib/Drupal/Core/Recipe/RecipeConfigStorageWrapper.php
++++ b/core/lib/Drupal/Core/Recipe/RecipeConfigStorageWrapper.php
+@@ -20,6 +20,10 @@ final class RecipeConfigStorageWrapper implements StorageInterface {
+    *   First config storage to wrap.
+    * @param \Drupal\Core\Config\StorageInterface $storageB
+    *   Second config storage to wrap.
++   * @param \Drupal\Core\Config\StorageInterface $storageDatabaseOverrideA
++   *   First database override config storage to wrap.
++   * @param \Drupal\Core\Config\StorageInterface $storageDatabaseOverrideB
++   *   Second database override config storage to wrap.
+    * @param string $collection
+    *   (optional) The collection to store configuration in. Defaults to the
+    *   default collection.
+@@ -27,6 +31,8 @@ final class RecipeConfigStorageWrapper implements StorageInterface {
+   public function __construct(
+     protected readonly StorageInterface $storageA,
+     protected readonly StorageInterface $storageB,
++    protected readonly ?StorageInterface $storageDatabaseOverrideA = NULL,
++    protected readonly ?StorageInterface $storageDatabaseOverrideB = NULL,
+     protected readonly string $collection = StorageInterface::DEFAULT_COLLECTION,
+   ) {
+   }
+@@ -66,6 +72,10 @@ public static function createStorageFromArray(array $storages): StorageInterface
+    * {@inheritdoc}
+    */
+   public function exists($name): bool {
++    if ($this->storageDatabaseOverrideA && $this->storageDatabaseOverrideB) {
++      return $this->storageA->exists($name) || $this->storageB->exists($name) || $this->storageDatabaseOverrideA->exists($name) || $this->storageDatabaseOverrideB->exists($name);
++    }
++
+     return $this->storageA->exists($name) || $this->storageB->exists($name);
+   }
+ 
+@@ -73,7 +83,17 @@ public function exists($name): bool {
+    * {@inheritdoc}
+    */
+   public function read($name): array|bool {
+-    return $this->storageA->read($name) ?: $this->storageB->read($name);
++    if ($this->storageDatabaseOverrideA && ($data = $this->storageDatabaseOverrideA->read($name))) {
++      return $data;
++    }
++    if ($data = $this->storageA->read($name)) {
++      return $data;
++    }
++    if ($this->storageDatabaseOverrideB && ($data = $this->storageDatabaseOverrideB->read($name))) {
++      return $data;
++    }
++
++    return $this->storageB->read($name);
+   }
+ 
+   /**
+@@ -82,6 +102,10 @@ public function read($name): array|bool {
+   public function readMultiple(array $names): array {
+     // If both storageA and storageB contain the same configuration, the value
+     // for storageA takes precedence.
++    if ($this->storageDatabaseOverrideA && $this->storageDatabaseOverrideB) {
++      return array_merge($this->storageB->readMultiple($names), $this->storageDatabaseOverrideB->readMultiple($names), $this->storageA->readMultiple($names), $this->storageDatabaseOverrideA->readMultiple($names));
++    }
++
+     return array_merge($this->storageB->readMultiple($names), $this->storageA->readMultiple($names));
+   }
+ 
+@@ -124,6 +148,10 @@ public function decode($raw): array {
+    * {@inheritdoc}
+    */
+   public function listAll($prefix = ''): array {
++    if ($this->storageDatabaseOverrideA && $this->storageDatabaseOverrideB) {
++      return array_unique(array_merge($this->storageA->listAll($prefix), $this->storageB->listAll($prefix), $this->storageDatabaseOverrideA->listAll($prefix), $this->storageDatabaseOverrideB->listAll($prefix)));
++    }
++
+     return array_unique(array_merge($this->storageA->listAll($prefix), $this->storageB->listAll($prefix)));
+   }
+ 
+@@ -138,9 +166,21 @@ public function deleteAll($prefix = ''): bool {
+    * {@inheritdoc}
+    */
+   public function createCollection($collection): static {
++    if ($this->storageDatabaseOverrideA && $this->storageDatabaseOverrideB) {
++      return new static(
++        $this->storageA->createCollection($collection),
++        $this->storageB->createCollection($collection),
++        $this->storageDatabaseOverrideA->createCollection($collection),
++        $this->storageDatabaseOverrideB->createCollection($collection),
++        $collection
++      );
++    }
++
+     return new static(
+       $this->storageA->createCollection($collection),
+       $this->storageB->createCollection($collection),
++      NULL,
++      NULL,
+       $collection
+     );
+   }
+@@ -149,6 +189,10 @@ public function createCollection($collection): static {
+    * {@inheritdoc}
+    */
+   public function getAllCollectionNames(): array {
++    if ($this->storageDatabaseOverrideA && $this->storageDatabaseOverrideB) {
++      return array_unique(array_merge($this->storageA->getAllCollectionNames(), $this->storageB->getAllCollectionNames(), $this->storageDatabaseOverrideA->getAllCollectionNames(), $this->storageDatabaseOverrideB->getAllCollectionNames()));
++    }
++
+     return array_unique(array_merge($this->storageA->getAllCollectionNames(), $this->storageB->getAllCollectionNames()));
+   }
+ 
+diff --git a/core/lib/Drupal/Core/Recipe/Recipe.php b/core/lib/Drupal/Core/Recipe/Recipe.php
+index 888f54e4f42cfdea0afd0142c1c011dfa824efa3..770b09b83d870891703c98793c2443061c60538a 100644
+--- a/core/lib/Drupal/Core/Recipe/Recipe.php
++++ b/core/lib/Drupal/Core/Recipe/Recipe.php
+@@ -91,7 +91,7 @@ public static function createFromDirectory(string $path): static {
+ 
+     $recipes = new RecipeConfigurator(is_array($recipe_data['recipes']) ? $recipe_data['recipes'] : [], dirname($path));
+     $install = new InstallConfigurator($recipe_data['install'], \Drupal::service('extension.list.module'), \Drupal::service('extension.list.theme'));
+-    $config = new ConfigConfigurator($recipe_data['config'], $path, \Drupal::service('config.storage'));
++    $config = new ConfigConfigurator($recipe_data['config'], $path, \Drupal::service('config.storage'), \Drupal::database());
+     $input = new InputConfigurator($recipe_data['input'] ?? [], $recipes, basename($path), \Drupal::typedDataManager());
+     $content = new Finder($path . '/content');
+     return new static($recipe_data['name'], $recipe_data['description'], $recipe_data['type'], $recipes, $install, $config, $input, $content, $path, $recipe_data['extra'] ?? []);
+diff --git a/core/lib/Drupal/Core/Routing/MatcherDumper.php b/core/lib/Drupal/Core/Routing/MatcherDumper.php
+index 78619fdd7213fa355420313fa022d6df452e7fa9..20fa7758402bda8a63355255c236af586d91b9b7 100644
+--- a/core/lib/Drupal/Core/Routing/MatcherDumper.php
++++ b/core/lib/Drupal/Core/Routing/MatcherDumper.php
+@@ -91,7 +91,17 @@ public function dump(array $options = []): string {
+     // stale data. The transaction makes it atomic to avoid unstable router
+     // states due to random failures.
+     try {
+-      $transaction = $this->connection->startTransaction();
++      if ($this->connection->driver() == 'mongodb') {
++        $session = $this->connection->getMongodbSession();
++        $session_started = FALSE;
++        if (!$session->isInTransaction()) {
++          $session->startTransaction();
++          $session_started = TRUE;
++        }
++      }
++      else {
++        $transaction = $this->connection->startTransaction();
++      }
+       // We don't use truncate, because it is not guaranteed to be transaction
+       // safe.
+       try {
+@@ -142,11 +152,17 @@ public function dump(array $options = []): string {
+         $insert->execute();
+       }
+ 
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->commitTransaction();
++      }
+     }
+     catch (\Exception $e) {
+       if (isset($transaction)) {
+         $transaction->rollBack();
+       }
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->abortTransaction();
++      }
+       Error::logException($this->logger, $e);
+       throw $e;
+     }
+diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php
+index 132f89631b0809fcfa36f44507878786a50a3d75..21bb482f7ab4a6e25accd7421a9c9648bafe5b91 100644
+--- a/core/lib/Drupal/Core/Routing/RouteProvider.php
++++ b/core/lib/Drupal/Core/Routing/RouteProvider.php
+@@ -10,6 +10,7 @@
+ use Drupal\Core\Path\CurrentPathStack;
+ use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
+ use Drupal\Core\State\StateInterface;
++use Drupal\mongodb\Driver\Database\mongodb\Statement;
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\Routing\Exception\RouteNotFoundException;
+@@ -231,8 +232,23 @@ public function preLoadRoutes($names) {
+       }
+       else {
+         try {
+-          $result = $this->connection->query('SELECT [name], [route] FROM {' . $this->connection->escapeTable($this->tableName) . '} WHERE [name] IN ( :names[] )', [':names[]' => $routes_to_load]);
+-          $routes = $result->fetchAllKeyed();
++          if ($this->connection->driver() == 'mongodb') {
++            $prefixed_table = $this->connection->getPrefix() . $this->tableName;
++            $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++              ['name' => ['$in' => $routes_to_load]],
++              [
++                'projection' => ['name' => 1, 'route' => 1, '_id' => 0],
++                'session' => $this->connection->getMongodbSession(),
++              ]
++            );
++
++            $statement = new Statement($this->connection, $cursor, ['name', 'route']);
++            $routes = $statement->execute()->fetchAllKeyed();
++          }
++          else {
++            $result = $this->connection->query('SELECT [name], [route] FROM {' . $this->connection->escapeTable($this->tableName) . '} WHERE [name] IN ( :names[] )', [':names[]' => $routes_to_load]);
++            $routes = $result->fetchAllKeyed();
++          }
+ 
+           $this->cache->set($cid, $routes, Cache::PERMANENT, ['routes']);
+         }
+@@ -367,11 +383,26 @@ protected function getRoutesByPath($path) {
+     // trailing wildcard parts as long as the pattern matches, since we
+     // dump the route pattern without those optional parts.
+     try {
+-      $routes = $this->connection->query("SELECT [name], [route], [fit] FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE [pattern_outline] IN ( :patterns[] ) AND [number_parts] >= :count_parts", [
+-        ':patterns[]' => $ancestors,
+-        ':count_parts' => count($parts),
+-      ])
+-        ->fetchAll(\PDO::FETCH_ASSOC);
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . $this->tableName;
++        $cursor = $this->connection->getConnection()->selectCollection($prefixed_table)->find(
++          ['pattern_outline' => ['$in' => $ancestors], 'number_parts' => ['$gte' => count($parts)]],
++          [
++            'projection' => ['name' => 1, 'route' => 1, 'fit' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ]
++        );
++
++        $statement = new Statement($this->connection, $cursor, ['name', 'route', 'fit']);
++        $routes = $statement->execute()->fetchAll(\PDO::FETCH_ASSOC);
++      }
++      else {
++        $routes = $this->connection->query("SELECT [name], [route], [fit] FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE [pattern_outline] IN ( :patterns[] ) AND [number_parts] >= :count_parts", [
++          ':patterns[]' => $ancestors,
++          ':count_parts' => count($parts),
++        ])
++          ->fetchAll(\PDO::FETCH_ASSOC);
++      }
+     }
+     catch (\Exception) {
+       $routes = [];
+diff --git a/core/lib/Drupal/Core/Session/SessionHandler.php b/core/lib/Drupal/Core/Session/SessionHandler.php
+index fe1247158cd143850eca8c024eafa503907fd8dd..e04c84256542ae37e45cffe70d8a63be4b87b6e6 100644
+--- a/core/lib/Drupal/Core/Session/SessionHandler.php
++++ b/core/lib/Drupal/Core/Session/SessionHandler.php
+@@ -7,6 +7,8 @@
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\DatabaseException;
+ use Drupal\Core\DependencyInjection\DependencySerializationTrait;
++use MongoDB\BSON\Binary;
++use MongoDB\BSON\UTCDateTime;
+ use Symfony\Component\HttpFoundation\RequestStack;
+ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+ 
+@@ -17,6 +19,15 @@ class SessionHandler extends AbstractProxy implements \SessionHandlerInterface {
+ 
+   use DependencySerializationTrait;
+ 
++  /**
++   * Indicator for the existence of the database table.
++   *
++   * This variable is only used by the database driver for MongoDB.
++   *
++   * @var bool
++   */
++  protected $tableExists = FALSE;
++
+   /**
+    * Constructs a new SessionHandler instance.
+    *
+@@ -47,14 +58,31 @@ public function open(string $save_path, string $name): bool {
+   public function read(#[\SensitiveParameter] string $sid): string|false {
+     $data = '';
+     if (!empty($sid)) {
+-      try {
+-        // Read the session data from the database.
+-        $query = $this->connection
+-          ->queryRange('SELECT [session] FROM {sessions} WHERE [sid] = :sid', 0, 1, [':sid' => Crypt::hashBase64($sid)]);
+-        $data = (string) $query->fetchField();
++      // Read the session data from the database.
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . 'sessions';
++        $result = $this->connection->getConnection()->selectCollection($prefixed_table)->findOne(
++          ['sid' => ['$eq' => Crypt::hashBase64($sid)]],
++          [
++            'projection' => ['session' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ],
++        );
++
++        // Get the session data.
++        if (isset($result->session) && ($result->session instanceof Binary)) {
++          $data = $result->session->getData();
++        }
+       }
+-      // Swallow the error if the table hasn't been created yet.
+-      catch (\Exception) {
++      else {
++        try {
++          $query = $this->connection
++            ->queryRange('SELECT [session] FROM {sessions} WHERE [sid] = :sid', 0, 1, [':sid' => Crypt::hashBase64($sid)]);
++          $data = (string) $query->fetchField();
++        }
++        // Swallow the error if the table hasn't been created yet.
++        catch (\Exception) {
++        }
+       }
+     }
+     return $data;
+@@ -64,6 +92,12 @@ public function read(#[\SensitiveParameter] string $sid): string|false {
+    * {@inheritdoc}
+    */
+   public function write(#[\SensitiveParameter] string $sid, string $value): bool {
++    if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++      // For MongoDB the table need to exists. Otherwise MongoDB creates one
++      // without the correct validation.
++      $this->tableExists = $this->ensureTableExists();
++    }
++
+     $try_again = FALSE;
+     $request = $this->requestStack->getCurrentRequest();
+     $fields = [
+@@ -108,6 +142,12 @@ public function close(): bool {
+    */
+   public function destroy(#[\SensitiveParameter] string $sid): bool {
+     try {
++      if ($this->connection->driver() == 'mongodb' && !$this->tableExists) {
++        // For MongoDB the table need to exists. Otherwise MongoDB creates one
++        // without the correct validation.
++        $this->tableExists = $this->ensureTableExists();
++      }
++
+       // Delete session data.
+       $this->connection->delete('sessions')
+         ->condition('sid', Crypt::hashBase64($sid))
+@@ -129,9 +169,19 @@ public function gc(int $lifetime): int|false {
+     // for three weeks before deleting them, you need to set gc_maxlifetime
+     // to '1814400'. At that value, only after a user doesn't log in after
+     // three weeks (1814400 seconds) will their session be removed.
++    $timestamp = $this->time->getRequestTime() - $lifetime;
++    if ($this->connection->driver() == 'mongodb') {
++      $timestamp = new UTCDateTime($timestamp * 1000);
++
++      if (!$this->tableExists) {
++        // For MongoDB the table need to exists. Otherwise MongoDB creates one
++        // without the correct validation.
++        $this->tableExists = $this->ensureTableExists();
++      }
++    }
+     try {
+       return $this->connection->delete('sessions')
+-        ->condition('timestamp', $this->time->getRequestTime() - $lifetime, '<')
++        ->condition('timestamp', $timestamp, '<')
+         ->execute();
+     }
+     // Swallow the error if the table hasn't been created yet.
+@@ -197,6 +247,10 @@ protected function schemaDefinition(): array {
+       ],
+     ];
+ 
++    if ($this->connection->driver() == 'mongodb') {
++      $schema['fields']['timestamp']['type'] = 'date';
++    }
++
+     return $schema;
+   }
+ 
+diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php
+index 1004b1f6621f5e5a67a577ee5f13ce999a67ddff..657c2e2bff0d720409742d56ab85fd12a45a3d62 100644
+--- a/core/lib/Drupal/Core/Session/SessionManager.php
++++ b/core/lib/Drupal/Core/Session/SessionManager.php
+@@ -202,7 +202,7 @@ public function delete($uid) {
+     // The sessions table may not have been created yet.
+     try {
+       $this->connection->delete('sessions')
+-        ->condition('uid', $uid)
++        ->condition('uid', (int) $uid)
+         ->execute();
+     }
+     catch (\Exception) {
+diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
+index b0449e30f2a585b3f4f8cd09b4a79dcc58249197..50308ac070b3bbbae929ddb1d04375eb794a82a3 100644
+--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
++++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
+@@ -37,7 +37,9 @@ public function validate($value, Constraint $constraint): void {
+     if ($typed_data instanceof BinaryInterface && !is_resource($value)) {
+       $valid = FALSE;
+     }
+-    if ($typed_data instanceof BooleanInterface && !(is_bool($value) || $value === 0 || $value === '0' || $value === 1 || $value == '1')) {
++    // With MongoDB a boolean with the value FALSE is stored as an empty
++    // string.
++    if ($typed_data instanceof BooleanInterface && !(is_bool($value) || $value === 0 || $value === '0' || $value === 1 || $value == '1' || $value == '')) {
+       $valid = FALSE;
+     }
+     if ($typed_data instanceof FloatInterface && filter_var($value, FILTER_VALIDATE_FLOAT) === FALSE) {
+diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
+index 149030512117a95fb680b252e9bd0a8abce67bcc..f74e4cdc3497cad03f6b1955b63cae3dadb0310d 100644
+--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
++++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
+@@ -53,8 +53,11 @@ public function validate($items, Constraint $constraint): void {
+     $field_label = $items->getFieldDefinition()->getLabel();
+     $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
+     $property_name = $field_storage_definitions[$field_name]->getMainPropertyName();
++    $property_schema = $field_storage_definitions[$field_name]->getSchema();
++    $property_type = $property_schema['columns'][$property_name]['type'] ?? NULL;
+ 
+     $id_key = $entity_type->getKey('id');
++    $id_key_type = $field_storage_definitions[$id_key]->getType();
+     $is_multiple = $field_storage_definitions[$field_name]->isMultiple();
+     $is_new = $entity->isNew();
+     $item_values = array_column($items->getValue(), $property_name);
+@@ -66,7 +69,12 @@ public function validate($items, Constraint $constraint): void {
+       ->accessCheck(FALSE)
+       ->groupBy("$field_name.$property_name");
+     if (!$is_new) {
+-      $entity_id = $entity->id();
++      if ($id_key_type == 'integer') {
++        $entity_id = (int) $entity->id();
++      }
++      else {
++        $entity_id = $entity->id();
++      }
+       $query->condition($id_key, $entity_id, '<>');
+     }
+ 
+@@ -76,7 +84,12 @@ public function validate($items, Constraint $constraint): void {
+     else {
+       $or_group = $query->orConditionGroup();
+       foreach ($item_values as $item_value) {
+-        $or_group->condition($field_name, \Drupal::database()->escapeLike($item_value), 'LIKE');
++        if ($property_type === 'int') {
++          $or_group->condition($field_name, $item_value);
++        }
++        else {
++          $or_group->condition($field_name, \Drupal::database()->escapeLike($item_value), 'LIKE');
++        }
+       }
+       $query->condition($or_group);
+     }
+diff --git a/core/modules/block_content/src/BlockContentViewsData.php b/core/modules/block_content/src/BlockContentViewsData.php
+index 06c9de77f2286dcb4329bf4633f761903a029fe9..ceb16e4d061777d13532869024291c7c17a0f3a3 100644
+--- a/core/modules/block_content/src/BlockContentViewsData.php
++++ b/core/modules/block_content/src/BlockContentViewsData.php
+@@ -16,14 +16,23 @@ public function getViewsData() {
+ 
+     $data = parent::getViewsData();
+ 
+-    $data['block_content_field_data']['id']['field']['id'] = 'field';
++    if ($this->connection->driver() == 'mongodb') {
++      $data_table = 'block_content';
++      $revision_table = 'block_content';
++    }
++    else {
++      $data_table = 'block_content_field_data';
++      $revision_table = 'block_content_field_revision';
++    }
+ 
+-    $data['block_content_field_data']['info']['field']['id'] = 'field';
+-    $data['block_content_field_data']['info']['field']['link_to_entity default'] = TRUE;
++    $data[$data_table]['id']['field']['id'] = 'field';
+ 
+-    $data['block_content_field_data']['type']['field']['id'] = 'field';
++    $data[$data_table]['info']['field']['id'] = 'field';
++    $data[$data_table]['info']['field']['link_to_entity default'] = TRUE;
+ 
+-    $data['block_content_field_data']['table']['wizard_id'] = 'block_content';
++    $data[$data_table]['type']['field']['id'] = 'field';
++
++    $data[$data_table]['table']['wizard_id'] = 'block_content';
+ 
+     $data['block_content']['block_content_listing_empty'] = [
+       'title' => $this->t('Empty block library behavior'),
+@@ -33,8 +42,8 @@ public function getViewsData() {
+       ],
+     ];
+     // Advertise this table as a possible base table.
+-    $data['block_content_field_revision']['table']['base']['help'] = $this->t('Block Content revision is a history of changes to block content.');
+-    $data['block_content_field_revision']['table']['base']['defaults']['title'] = 'info';
++    $data[$revision_table]['table']['base']['help'] = $this->t('Block Content revision is a history of changes to block content.');
++    $data[$revision_table]['table']['base']['defaults']['title'] = 'info';
+ 
+     return $data;
+   }
+diff --git a/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php b/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php
+index ce592d27fb34ff77a7eab5f947e6ac7bfa2f9957..5ebd4b61f8ea2057476d2d2d7e050ed8e90f11fa 100644
+--- a/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php
++++ b/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php
+@@ -49,11 +49,11 @@ public function query() {
+ 
+     // Add in the property, which is either title or body. Cast the bid to text
+     // so PostgreSQL can make the join.
+-    $query->leftJoin(static::I18N_STRING_TABLE, 'i18n', '[i18n].[objectid] = CAST([b].[bid] AS CHAR(255))');
++    $query->leftJoin(static::I18N_STRING_TABLE, 'i18n', $query->joinCondition()->where('[i18n].[objectid] = CAST([b].[bid] AS CHAR(255))'));
+     $query->condition('i18n.type', 'block');
+ 
+     // Add in the translation for the property.
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+     return $query;
+   }
+ 
+diff --git a/core/modules/block/src/Plugin/migrate/source/Block.php b/core/modules/block/src/Plugin/migrate/source/Block.php
+index de6060c968d580a7cd102eb9ceb5f51d665d8e10..d33aa619513e122efc9583a45f05a50877ef45a2 100644
+--- a/core/modules/block/src/Plugin/migrate/source/Block.php
++++ b/core/modules/block/src/Plugin/migrate/source/Block.php
+@@ -127,7 +127,7 @@ public function prepareRow(Row $row) {
+       ->fields('br', ['rid'])
+       ->condition('module', $module)
+       ->condition('delta', $delta);
+-    $query->join($this->userRoleTable, 'ur', '[br].[rid] = [ur].[rid]');
++    $query->join($this->userRoleTable, 'ur', $query->joinCondition()->compare('br.rid', 'ur.rid'));
+     $roles = $query->execute()
+       ->fetchCol();
+     $row->setSourceProperty('roles', $roles);
+diff --git a/core/modules/block/src/Plugin/migrate/source/d6/BlockTranslation.php b/core/modules/block/src/Plugin/migrate/source/d6/BlockTranslation.php
+index 6cfa1d6ffcd6041fc67d9159f3dbd5587717344f..7ccc64b80cbb30ee7200da7e4bfe1fcb33fbde3d 100644
+--- a/core/modules/block/src/Plugin/migrate/source/d6/BlockTranslation.php
++++ b/core/modules/block/src/Plugin/migrate/source/d6/BlockTranslation.php
+@@ -32,7 +32,11 @@ public function query() {
+     $query = $this->select('i18n_blocks', 'i18n')
+       ->fields('i18n')
+       ->fields('b', ['bid', 'module', 'delta', 'theme', 'title']);
+-    $query->innerJoin($this->blockTable, 'b', ('[b].[module] = [i18n].[module] AND [b].[delta] = [i18n].[delta]'));
++    $query->innerJoin($this->blockTable, 'b',
++      $query->joinCondition()
++        ->compare('b.module', 'i18n.module')
++        ->compare('b.delta', 'i18n.delta')
++    );
+     return $query;
+   }
+ 
+diff --git a/core/modules/block/src/Plugin/migrate/source/d7/BlockTranslation.php b/core/modules/block/src/Plugin/migrate/source/d7/BlockTranslation.php
+index a826576642a51aab2621a4360a9655afe4558f1e..f4cfbed988606c8d8f19da50fee9258d6dcc40c2 100644
+--- a/core/modules/block/src/Plugin/migrate/source/d7/BlockTranslation.php
++++ b/core/modules/block/src/Plugin/migrate/source/d7/BlockTranslation.php
+@@ -54,8 +54,8 @@ public function query() {
+         'plural',
+       ])
+       ->condition('i18n_mode', 1);
+-    $query->leftJoin($this->blockTable, 'b', ('[b].[delta] = [i18n].[objectid]'));
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->leftJoin($this->blockTable, 'b', $query->joinCondition()->compare('b.delta', 'i18n.objectid'));
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+ 
+     // The i18n_string module adds a status column to locale_target. It was
+     // originally 'status' in a later revision it was named 'i18n_status'.
+diff --git a/core/modules/comment/comment.install b/core/modules/comment/comment.install
+index b170eed6d442f89379031975160fdd078b01a639..374b9cf2174312632c099cd9254dd1401adbb63b 100644
+--- a/core/modules/comment/comment.install
++++ b/core/modules/comment/comment.install
+@@ -108,6 +108,10 @@ function comment_schema(): array {
+     ],
+   ];
+ 
++  if (\Drupal::database()->driver() == 'mongodb') {
++    $schema['comment_entity_statistics']['fields']['last_comment_timestamp']['type'] = 'date';
++  }
++
+   return $schema;
+ }
+ 
+diff --git a/core/modules/comment/src/CommentManager.php b/core/modules/comment/src/CommentManager.php
+index ba26943fe470479fbe91ca424b840ff395889032..e3d70048d61e526fd3ae361f2621cc5ecc4d9f23 100644
+--- a/core/modules/comment/src/CommentManager.php
++++ b/core/modules/comment/src/CommentManager.php
+@@ -18,6 +18,7 @@
+ use Drupal\field\Entity\FieldConfig;
+ use Drupal\user\RoleInterface;
+ use Drupal\user\UserInterface;
++use MongoDB\BSON\UTCDateTime;
+ 
+ /**
+  * Comment manager contains common functions to manage comment fields.
+@@ -218,14 +219,17 @@ public function getCountNewComments(EntityInterface $entity, $field_name = NULL,
+         }
+       }
+       $timestamp = ($timestamp > HISTORY_READ_LIMIT ? $timestamp : HISTORY_READ_LIMIT);
++      if (\Drupal::database()->databaseType() == 'mongodb') {
++        $timestamp = new UTCDateTime($timestamp * 1000);
++      }
+ 
+       // Use the timestamp to retrieve the number of new comments.
+       $query = $this->entityTypeManager->getStorage('comment')->getQuery()
+         ->accessCheck(TRUE)
+         ->condition('entity_type', $entity->getEntityTypeId())
+-        ->condition('entity_id', $entity->id())
++        ->condition('entity_id', (int) $entity->id())
+         ->condition('created', $timestamp, '>')
+-        ->condition('status', CommentInterface::PUBLISHED);
++        ->condition('status', (bool) CommentInterface::PUBLISHED);
+       if ($field_name) {
+         // Limit to a particular field.
+         $query->condition('field_name', $field_name);
+diff --git a/core/modules/comment/src/CommentStatistics.php b/core/modules/comment/src/CommentStatistics.php
+index 785f2b4c807eb4e1b8f4284c9f4e83a6fb2f93d6..3c16501dfc29eaafc9ec174a71e50c69b170e8a6 100644
+--- a/core/modules/comment/src/CommentStatistics.php
++++ b/core/modules/comment/src/CommentStatistics.php
+@@ -205,7 +205,7 @@ public function update(CommentInterface $comment) {
+     }
+ 
+     $query = $this->database->select('comment_field_data', 'c');
+-    $query->addExpression('COUNT([cid])');
++    $query->addExpressionCount('cid');
+     $count = $query->condition('c.entity_id', $comment->getCommentedEntityId())
+       ->condition('c.entity_type', $comment->getCommentedEntityTypeId())
+       ->condition('c.field_name', $comment->getFieldName())
+diff --git a/core/modules/comment/src/CommentStorage.php b/core/modules/comment/src/CommentStorage.php
+index 4f668eac119964b96a3149a3a5162a30b94935a8..a9d3133b2b10860809673728c0170b1084d9e4b5 100644
+--- a/core/modules/comment/src/CommentStorage.php
++++ b/core/modules/comment/src/CommentStorage.php
+@@ -17,6 +17,8 @@
+ use Drupal\Core\Language\LanguageManagerInterface;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
+ 
++// cspell:ignore fieldcompare
++
+ /**
+  * Defines the storage handler class for comments.
+  *
+@@ -80,63 +82,175 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
+    * {@inheritdoc}
+    */
+   public function getMaxThread(CommentInterface $comment) {
+-    $query = $this->database->select($this->getDataTable(), 'c')
+-      ->condition('entity_id', $comment->getCommentedEntityId())
+-      ->condition('field_name', $comment->getFieldName())
+-      ->condition('entity_type', $comment->getCommentedEntityTypeId())
+-      ->condition('default_langcode', 1);
+-    $query->addExpression('MAX([thread])', 'thread');
+-    return $query->execute()
+-      ->fetchField();
++    if ($this->database->driver() == 'mongodb') {
++      $result = $this->database->select($this->getBaseTable(), 'c')
++        ->fields('c', ['comment_translations'])
++        ->condition('comment_translations.entity_id', (int) $comment->getCommentedEntityId())
++        ->condition('comment_translations.field_name', $comment->getFieldName())
++        ->condition('comment_translations.entity_type', $comment->getCommentedEntityTypeId())
++        ->condition('comment_translations.default_langcode', TRUE)
++        ->execute()
++        ->fetchAll();
++
++      $max_thread = '';
++      foreach ($result as $row) {
++        foreach ($row->comment_translations as $comment_translation) {
++          if (($comment_translation['entity_id'] == $comment->getCommentedEntityId()) &&
++            ($comment_translation['entity_type'] == $comment->getCommentedEntityTypeId()) &&
++            ($comment_translation['field_name'] == $comment->getFieldName()) &&
++            ($comment_translation['default_langcode'] == CommentInterface::PUBLISHED)
++          ) {
++            if ($comment_translation['thread'] > $max_thread) {
++              $max_thread = $comment_translation['thread'];
++            }
++          }
++        }
++      }
++      return $max_thread;
++    }
++    else {
++      $query = $this->database->select($this->getDataTable(), 'c')
++        ->condition('entity_id', $comment->getCommentedEntityId())
++        ->condition('field_name', $comment->getFieldName())
++        ->condition('entity_type', $comment->getCommentedEntityTypeId())
++        ->condition('default_langcode', 1);
++      $query->addExpressionMax('thread', 'thread');
++      return $query->execute()
++        ->fetchField();
++    }
+   }
+ 
+   /**
+    * {@inheritdoc}
+    */
+   public function getMaxThreadPerThread(CommentInterface $comment) {
+-    $query = $this->database->select($this->getDataTable(), 'c')
+-      ->condition('entity_id', $comment->getCommentedEntityId())
+-      ->condition('field_name', $comment->getFieldName())
+-      ->condition('entity_type', $comment->getCommentedEntityTypeId())
+-      ->condition('thread', $comment->getParentComment()->getThread() . '.%', 'LIKE')
+-      ->condition('default_langcode', 1);
+-    $query->addExpression('MAX([thread])', 'thread');
+-    return $query->execute()
+-      ->fetchField();
++    if ($this->database->driver() == 'mongodb') {
++      $result = $this->database->select($this->getBaseTable(), 'c')
++        ->fields('c', ['comment_translations'])
++        ->condition('comment_translations.entity_id', (int) $comment->getCommentedEntityId())
++        ->condition('comment_translations.field_name', $comment->getFieldName())
++        ->condition('comment_translations.entity_type', $comment->getCommentedEntityTypeId())
++        ->condition('comment_translations.thread', $comment->getParentComment()->getThread() . '.%', 'LIKE')
++        ->condition('comment_translations.default_langcode', TRUE)
++        ->execute()
++        ->fetchAll();
++
++      $max_thread = '';
++      foreach ($result as $row) {
++        foreach ($row->comment_translations as $comment_translation) {
++          if (($comment_translation['entity_id'] == $comment->getCommentedEntityId()) &&
++            ($comment_translation['entity_type'] == $comment->getCommentedEntityTypeId()) &&
++            ($comment_translation['field_name'] == $comment->getFieldName()) &&
++            ($comment_translation['default_langcode'] == CommentInterface::PUBLISHED)
++          ) {
++            $pattern = '/^' . $comment->getParentComment()->getThread() . '.*/';
++            if (($comment_translation['thread'] > $max_thread) && preg_match($pattern, $comment_translation['thread'])) {
++              $max_thread = $comment_translation['thread'];
++            }
++          }
++        }
++      }
++      return $max_thread;
++    }
++    else {
++      $query = $this->database->select($this->getDataTable(), 'c')
++        ->condition('entity_id', $comment->getCommentedEntityId())
++        ->condition('field_name', $comment->getFieldName())
++        ->condition('entity_type', $comment->getCommentedEntityTypeId())
++        ->condition('thread', $comment->getParentComment()->getThread() . '.%', 'LIKE')
++        ->condition('default_langcode', 1);
++      $query->addExpressionMax('thread', 'thread');
++      return $query->execute()
++        ->fetchField();
++    }
+   }
+ 
+   /**
+    * {@inheritdoc}
+    */
+   public function getDisplayOrdinal(CommentInterface $comment, $comment_mode, $divisor = 1) {
+-    // Count how many comments (c1) are before $comment (c2) in display order.
+-    // This is the 0-based display ordinal.
+-    $data_table = $this->getDataTable();
+-    $query = $this->database->select($data_table, 'c1');
+-    $query->innerJoin($data_table, 'c2', '[c2].[entity_id] = [c1].[entity_id] AND [c2].[entity_type] = [c1].[entity_type] AND [c2].[field_name] = [c1].[field_name]');
+-    $query->addExpression('COUNT(*)', 'count');
+-    $query->condition('c2.cid', $comment->id());
+-    if (!$this->currentUser->hasPermission('administer comments')) {
+-      $query->condition('c1.status', CommentInterface::PUBLISHED);
+-    }
++    if ($this->database->driver() == 'mongodb') {
++      // Count how many comments (c1) are before $comment (c2) in display order.
++      // This is the 0-based display ordinal.
++      $query = $this->database->select('comment', 'c1')
++        ->fields('c1', ['cid']);
++
++      // The comment_translations field must be added in a special way, because
++      // the join operation will overwrite its value.
++      $query->addPreJoinField('c1_comment_translations', 'comment_translations');
++
++      $query->addJoin('INNER', 'comment', 'c2', $query->joinCondition()
++        ->compare('c1.comment_translations.entity_id', 'c2.comment_translations.entity_id')
++        ->compare('c1.comment_translations.entity_type', 'c2.comment_translations.entity_type')
++        ->compare('c1.comment_translations.field_name', 'c2.comment_translations.field_name')
++      );
+ 
+-    if ($comment_mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
+-      // For rendering flat comments, cid is used for ordering comments due to
+-      // unpredictable behavior with timestamp, so we make the same assumption
+-      // here.
+-      $query->condition('c1.cid', $comment->id(), '<');
++      $query->condition('c2.comment_translations.cid', (int) $comment->id());
++      if (!$this->currentUser->hasPermission('administer comments')) {
++        $query->condition('c1_comment_translations.status', (bool) CommentInterface::PUBLISHED);
++      }
++
++      if ($comment_mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
++        // For rendering flat comments, cid is used for ordering comments due to
++        // unpredictable behavior with timestamp, so we make the same assumption
++        // here.
++        $query->condition('c1_comment_translations.cid', (int) $comment->id(), '<');
++      }
++      else {
++        // For threaded comments, the c.thread column is used for ordering. We can
++        // use the sorting code for comparison, but must remove the trailing
++        // slash.
++        $query->addSubstringField('c1_thread', 'c1_comment_translations.thread', 1, -2);
++
++        // The array "c2.comment_translations" is unwound and yet the MongoDB
++        // throws an exception that it is an array and not a string. For MongoDB
++        // it would be better to store the value thread as a string with a
++        // trailing slash and as an integer value.
++        $query->addSubstringField('c2_thread', 'c2.comment_translations.thread', 1, -2);
++        $query->condition('c2_thread', ['field' => 'c1_thread', 'operator' => '>'], 'FIELDCOMPARE');
++      }
++
++      $query->condition('c1_comment_translations.default_langcode', TRUE);
++      $query->condition('c2.comment_translations.default_langcode', TRUE);
++
++      $result = $query->execute()->fetchAll();
++      $ordinal = count($result);
+     }
+     else {
+-      // For threaded comments, the c.thread column is used for ordering. We can
+-      // use the sorting code for comparison, but must remove the trailing
+-      // slash.
+-      $query->where('SUBSTRING([c1].[thread], 1, (LENGTH([c1].[thread]) - 1)) < SUBSTRING([c2].[thread], 1, (LENGTH([c2].[thread]) - 1))');
+-    }
++      // Count how many comments (c1) are before $comment (c2) in display order.
++      // This is the 0-based display ordinal.
++      $data_table = $this->getDataTable();
++      $query = $this->database->select($data_table, 'c1');
++      $query->innerJoin($data_table, 'c2',
++        $query->joinCondition()
++          ->compare('c2.entity_id', 'c1.entity_id')
++          ->compare('c2.entity_type', 'c1.entity_type')
++          ->compare('c2.field_name', 'c1.field_name')
++      );
++      $query->addExpressionCountAll('count');
++      $query->condition('c2.cid', $comment->id());
++      if (!$this->currentUser->hasPermission('administer comments')) {
++        $query->condition('c1.status', CommentInterface::PUBLISHED);
++      }
+ 
+-    $query->condition('c1.default_langcode', 1);
+-    $query->condition('c2.default_langcode', 1);
++      if ($comment_mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
++        // For rendering flat comments, cid is used for ordering comments due to
++        // unpredictable behavior with timestamp, so we make the same assumption
++        // here.
++        $query->condition('c1.cid', $comment->id(), '<');
++      }
++      else {
++        // For threaded comments, the c.thread column is used for ordering. We can
++        // use the sorting code for comparison, but must remove the trailing
++        // slash.
++        $query->where('SUBSTRING([c1].[thread], 1, (LENGTH([c1].[thread]) - 1)) < SUBSTRING([c2].[thread], 1, (LENGTH([c2].[thread]) - 1))');
++      }
+ 
+-    $ordinal = $query->execute()->fetchField();
++      $query->condition('c1.default_langcode', 1);
++      $query->condition('c2.default_langcode', 1);
++
++      $ordinal = $query->execute()->fetchField();
++    }
+ 
+     return ($divisor > 1) ? floor($ordinal / $divisor) : $ordinal;
+   }
+@@ -147,58 +261,111 @@ public function getDisplayOrdinal(CommentInterface $comment, $comment_mode, $div
+   public function getNewCommentPageNumber($total_comments, $new_comments, FieldableEntityInterface $entity, $field_name) {
+     $field = $entity->getFieldDefinition($field_name);
+     $comments_per_page = $field->getSetting('per_page');
+-    $data_table = $this->getDataTable();
+ 
+-    if ($total_comments <= $comments_per_page) {
+-      // Only one page of comments.
+-      $count = 0;
+-    }
+-    elseif ($field->getSetting('default_mode') == CommentManagerInterface::COMMENT_MODE_FLAT) {
+-      // Flat comments.
+-      $count = $total_comments - $new_comments;
++    if ($this->database->driver() == 'mongodb') {
++      $base_table = $this->getBaseTable();
++
++      if ($total_comments <= $comments_per_page) {
++        // Only one page of comments.
++        $count = 0;
++      }
++      elseif ($field->getSetting('default_mode') == CommentManagerInterface::COMMENT_MODE_FLAT) {
++        // Flat comments.
++        $count = $total_comments - $new_comments;
++      }
++      else {
++        // Threaded comments.
++
++        // 1. Find all the threads with a new comment.
++        $unread_threads = $this->database->select($base_table, 'comment')
++          ->fields('comment', ['thread'])
++          ->condition('comment_translations.entity_id', (int) $entity->id())
++          ->condition('comment_translations.entity_type', $entity->getEntityTypeId())
++          ->condition('comment_translations.field_name', $field_name)
++          ->condition('comment_translations.status', (bool) CommentInterface::PUBLISHED)
++          ->condition('comment_translations.default_langcode', TRUE)
++          ->orderBy('comment_translations.created', 'DESC')
++          ->orderBy('comment_translations.cid', 'DESC')
++          ->range(0, $new_comments)
++          ->execute()
++          ->fetchCol();
++
++        // 2. Find the first thread.
++        foreach ($unread_threads as &$unread_thread) {
++          $unread_thread = substr($unread_thread, 0, -1);
++          $unread_thread = ltrim($unread_thread, '0');
++        }
++        natsort($unread_threads);
++
++        $first_thread = reset($unread_threads);
++
++        // 3. Find the number of the first comment of the first unread thread.
++        $threads_query = $this->database->select($base_table, 'comment')
++          ->fields('comment', ['cid'])
++          ->condition('comment_translations.entity_id', (int) $entity->id())
++          ->condition('comment_translations.entity_type', $entity->getEntityTypeId())
++          ->condition('comment_translations.field_name', $field_name)
++          ->condition('comment_translations.status', (bool) CommentInterface::PUBLISHED);
++        $threads_query->addSubstringField('thread_without_slash', 'thread', 1, -2);
++        $threads_query->condition('thread_without_slash', $first_thread, '<');
++        $cids = $threads_query->execute()->fetchAll();
++        $count = count($cids);
++      }
+     }
+     else {
+-      // Threaded comments.
+-
+-      // 1. Find all the threads with a new comment.
+-      $unread_threads_query = $this->database->select($data_table, 'comment')
+-        ->fields('comment', ['thread'])
+-        ->condition('entity_id', $entity->id())
+-        ->condition('entity_type', $entity->getEntityTypeId())
+-        ->condition('field_name', $field_name)
+-        ->condition('status', CommentInterface::PUBLISHED)
+-        ->condition('default_langcode', 1)
+-        ->orderBy('created', 'DESC')
+-        ->orderBy('cid', 'DESC')
+-        ->range(0, $new_comments);
+-
+-      // 2. Find the first thread.
+-      $first_thread_query = $this->database->select($unread_threads_query, 'thread');
+-      $first_thread_query->addExpression('SUBSTRING([thread], 1, (LENGTH([thread]) - 1))', 'torder');
+-      $first_thread = $first_thread_query
+-        ->fields('thread', ['thread'])
+-        ->orderBy('torder')
+-        ->range(0, 1)
+-        ->execute()
+-        ->fetchField();
++      $data_table = $this->getDataTable();
+ 
+-      // Remove the final '/'.
+-      $first_thread = substr($first_thread, 0, -1);
+-
+-      // Find the number of the first comment of the first unread thread.
+-      $count = $this->database->query('SELECT COUNT(*) FROM {' . $data_table . '} WHERE [entity_id] = :entity_id
+-        AND [entity_type] = :entity_type
+-        AND [field_name] = :field_name
+-        AND [status] = :status
+-        AND SUBSTRING([thread], 1, (LENGTH([thread]) - 1)) < :thread
+-        AND [default_langcode] = 1', [
+-          ':status' => CommentInterface::PUBLISHED,
+-          ':entity_id' => $entity->id(),
+-          ':field_name' => $field_name,
+-          ':entity_type' => $entity->getEntityTypeId(),
+-          ':thread' => $first_thread,
+-        ]
+-      )->fetchField();
++      if ($total_comments <= $comments_per_page) {
++        // Only one page of comments.
++        $count = 0;
++      }
++      elseif ($field->getSetting('default_mode') == CommentManagerInterface::COMMENT_MODE_FLAT) {
++        // Flat comments.
++        $count = $total_comments - $new_comments;
++      }
++      else {
++        // Threaded comments.
++
++        // 1. Find all the threads with a new comment.
++        $unread_threads_query = $this->database->select($data_table, 'comment')
++          ->fields('comment', ['thread'])
++          ->condition('entity_id', $entity->id())
++          ->condition('entity_type', $entity->getEntityTypeId())
++          ->condition('field_name', $field_name)
++          ->condition('status', CommentInterface::PUBLISHED)
++          ->condition('default_langcode', 1)
++          ->orderBy('created', 'DESC')
++          ->orderBy('cid', 'DESC')
++          ->range(0, $new_comments);
++
++        // 2. Find the first thread.
++        $first_thread_query = $this->database->select($unread_threads_query, 'thread');
++        $first_thread_query->addExpression('SUBSTRING([thread], 1, (LENGTH([thread]) - 1))', 'torder');
++        $first_thread = $first_thread_query
++          ->fields('thread', ['thread'])
++          ->orderBy('torder')
++          ->range(0, 1)
++          ->execute()
++          ->fetchField();
++
++        // Remove the final '/'.
++        $first_thread = substr($first_thread, 0, -1);
++
++        // Find the number of the first comment of the first unread thread.
++        $count = $this->database->query('SELECT COUNT(*) FROM {' . $data_table . '} WHERE [entity_id] = :entity_id
++          AND [entity_type] = :entity_type
++          AND [field_name] = :field_name
++          AND [status] = :status
++          AND SUBSTRING([thread], 1, (LENGTH([thread]) - 1)) < :thread
++          AND [default_langcode] = 1', [
++            ':status' => CommentInterface::PUBLISHED,
++            ':entity_id' => $entity->id(),
++            ':field_name' => $field_name,
++            ':entity_type' => $entity->getEntityTypeId(),
++            ':thread' => $first_thread,
++          ]
++        )->fetchField();
++      }
+     }
+ 
+     return $comments_per_page > 0 ? (int) ($count / $comments_per_page) : 0;
+@@ -208,12 +375,26 @@ public function getNewCommentPageNumber($total_comments, $new_comments, Fieldabl
+    * {@inheritdoc}
+    */
+   public function getChildCids(array $comments) {
+-    return $this->database->select($this->getDataTable(), 'c')
+-      ->fields('c', ['cid'])
+-      ->condition('pid', array_keys($comments), 'IN')
+-      ->condition('default_langcode', 1)
+-      ->execute()
+-      ->fetchCol();
++    if ($this->database->driver() == 'mongodb') {
++      $cids = [];
++      foreach (array_keys($comments) as $cid) {
++        $cids[] = (int) $cid;
++      }
++      return $this->database->select($this->getBaseTable(), 'c')
++        ->fields('c', ['cid'])
++        ->condition('comment_translations.pid', $cids, 'IN')
++        ->condition('comment_translations.default_langcode', TRUE)
++        ->execute()
++        ->fetchCol();
++    }
++    else {
++      return $this->database->select($this->getDataTable(), 'c')
++        ->fields('c', ['cid'])
++        ->condition('pid', array_keys($comments), 'IN')
++        ->condition('default_langcode', 1)
++        ->execute()
++        ->fetchCol();
++    }
+   }
+ 
+   /**
+@@ -274,30 +455,51 @@ public function getChildCids(array $comments) {
+    * to consider the trailing "/" so we use a substring only.
+    */
+   public function loadThread(EntityInterface $entity, $field_name, $mode, $comments_per_page = 0, $pager_id = 0) {
+-    $data_table = $this->getDataTable();
+-    $query = $this->database->select($data_table, 'c');
+-    $query->addField('c', 'cid');
+-    $query
+-      ->condition('c.entity_id', $entity->id())
+-      ->condition('c.entity_type', $entity->getEntityTypeId())
+-      ->condition('c.field_name', $field_name)
+-      ->condition('c.default_langcode', 1)
+-      ->addTag('entity_access')
+-      ->addTag('comment_filter')
+-      ->addMetaData('base_table', 'comment')
+-      ->addMetaData('entity', $entity)
+-      ->addMetaData('field_name', $field_name);
+-
+-    if ($comments_per_page) {
+-      $query = $query->extend(PagerSelectExtender::class)
+-        ->limit($comments_per_page);
+-      if ($pager_id) {
+-        $query->element($pager_id);
++    if ($this->database->driver() == 'mongodb') {
++      $query = $this->database->select($this->getBaseTable(), 'c');
++      $query->addField('c', 'cid');
++      $query
++        ->condition('comment_translations.entity_id', (int) $entity->id())
++        ->condition('comment_translations.entity_type', $entity->getEntityTypeId())
++        ->condition('comment_translations.field_name', $field_name)
++        ->condition('comment_translations.default_langcode', TRUE)
++        ->addTag('entity_access')
++        ->addTag('comment_filter')
++        ->addMetaData('base_table', 'comment')
++        ->addMetaData('entity', $entity)
++        ->addMetaData('field_name', $field_name);
++
++      if ($comments_per_page) {
++        $query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender')
++          ->limit($comments_per_page);
++        if ($pager_id) {
++          $query->element($pager_id);
++        }
++
++        // @todo Start using $query->setCountQuery($count_query);
++        // $query->setCountQueryMethod($this, 'countQueryThread', [$entity, $field_name, $comments_per_page]);
+       }
+ 
+-      $count_query = $this->database->select($data_table, 'c');
+-      $count_query->addExpression('COUNT(*)');
+-      $count_query
++      if (!$this->currentUser->hasPermission('administer comments')) {
++        $query->condition('comment_translations.status', (bool) CommentInterface::PUBLISHED);
++      }
++      if ($mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
++        $query->orderBy('cid', 'ASC');
++      }
++      else {
++        // See comment above. Analysis reveals that this doesn't cost too
++        // much. It scales much much better than having the whole comment
++        // structure.
++        $query->addSubstringField('thread_order', 'comment_translations.thread', 1, -2);
++        $query->orderBy('thread_order', 'ASC');
++      }
++      $cids = $query->execute()->fetchCol();
++    }
++    else {
++      $data_table = $this->getDataTable();
++      $query = $this->database->select($data_table, 'c');
++      $query->addField('c', 'cid');
++      $query
+         ->condition('c.entity_id', $entity->id())
+         ->condition('c.entity_type', $entity->getEntityTypeId())
+         ->condition('c.field_name', $field_name)
+@@ -307,26 +509,47 @@ public function loadThread(EntityInterface $entity, $field_name, $mode, $comment
+         ->addMetaData('base_table', 'comment')
+         ->addMetaData('entity', $entity)
+         ->addMetaData('field_name', $field_name);
+-      $query->setCountQuery($count_query);
+-    }
+ 
+-    if (!$this->currentUser->hasPermission('administer comments')) {
+-      $query->condition('c.status', CommentInterface::PUBLISHED);
+       if ($comments_per_page) {
+-        $count_query->condition('c.status', CommentInterface::PUBLISHED);
++        $query = $query->extend(PagerSelectExtender::class)
++          ->limit($comments_per_page);
++        if ($pager_id) {
++          $query->element($pager_id);
++        }
++
++        $count_query = $this->database->select($data_table, 'c');
++        $count_query->addExpression('COUNT(*)');
++        $count_query
++          ->condition('c.entity_id', $entity->id())
++          ->condition('c.entity_type', $entity->getEntityTypeId())
++          ->condition('c.field_name', $field_name)
++          ->condition('c.default_langcode', 1)
++          ->addTag('entity_access')
++          ->addTag('comment_filter')
++          ->addMetaData('base_table', 'comment')
++          ->addMetaData('entity', $entity)
++          ->addMetaData('field_name', $field_name);
++        $query->setCountQuery($count_query);
++      }
++
++      if (!$this->currentUser->hasPermission('administer comments')) {
++        $query->condition('c.status', CommentInterface::PUBLISHED);
++        if ($comments_per_page) {
++          $count_query->condition('c.status', CommentInterface::PUBLISHED);
++        }
++      }
++      if ($mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
++        $query->orderBy('c.cid', 'ASC');
++      }
++      else {
++        // See comment above. Analysis reveals that this doesn't cost too much. It
++        // scales much better than having the whole comment structure.
++        $query->addExpression('SUBSTRING([c].[thread], 1, (LENGTH([c].[thread]) - 1))', 'torder');
++        $query->orderBy('torder', 'ASC');
+       }
+-    }
+-    if ($mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
+-      $query->orderBy('c.cid', 'ASC');
+-    }
+-    else {
+-      // See comment above. Analysis reveals that this doesn't cost too much. It
+-      // scales much better than having the whole comment structure.
+-      $query->addExpression('SUBSTRING([c].[thread], 1, (LENGTH([c].[thread]) - 1))', 'torder');
+-      $query->orderBy('torder', 'ASC');
+-    }
+ 
+-    $cids = $query->execute()->fetchCol();
++      $cids = $query->execute()->fetchCol();
++    }
+ 
+     $comments = [];
+     if ($cids) {
+@@ -340,12 +563,22 @@ public function loadThread(EntityInterface $entity, $field_name, $mode, $comment
+    * {@inheritdoc}
+    */
+   public function getUnapprovedCount() {
+-    return $this->database->select($this->getDataTable(), 'c')
+-      ->condition('status', CommentInterface::NOT_PUBLISHED, '=')
+-      ->condition('default_langcode', 1)
+-      ->countQuery()
+-      ->execute()
+-      ->fetchField();
++    if ($this->database->driver() == 'mongodb') {
++      return $this->database->select($this->getBaseTable(), 'c')
++        ->condition('comment_translations.status', (bool) CommentInterface::NOT_PUBLISHED)
++        ->condition('comment_translations.default_langcode', TRUE)
++        ->countQuery()
++        ->execute()
++        ->fetchField();
++    }
++    else {
++      return $this->database->select($this->getDataTable(), 'c')
++        ->condition('status', CommentInterface::NOT_PUBLISHED, '=')
++        ->condition('default_langcode', 1)
++        ->countQuery()
++        ->execute()
++        ->fetchField();
++    }
+   }
+ 
+ }
+diff --git a/core/modules/comment/src/CommentViewsData.php b/core/modules/comment/src/CommentViewsData.php
+index 944827366376f987c3612857763e0a71e6e57d5c..8c7dd64b2283208b2d46c5de13266197169145a8 100644
+--- a/core/modules/comment/src/CommentViewsData.php
++++ b/core/modules/comment/src/CommentViewsData.php
+@@ -16,28 +16,35 @@ class CommentViewsData extends EntityViewsData {
+   public function getViewsData() {
+     $data = parent::getViewsData();
+ 
+-    $data['comment_field_data']['table']['base']['help'] = $this->t('Comments are responses to content.');
+-    $data['comment_field_data']['table']['base']['access query tag'] = 'comment_access';
++    if ($this->connection->driver() == 'mongodb') {
++      $data_table = 'comment';
++    }
++    else {
++      $data_table = 'comment_field_data';
++    }
++
++    $data[$data_table]['table']['base']['help'] = $this->t('Comments are responses to content.');
++    $data[$data_table]['table']['base']['access query tag'] = 'comment_access';
+ 
+-    $data['comment_field_data']['table']['wizard_id'] = 'comment';
++    $data[$data_table]['table']['wizard_id'] = 'comment';
+ 
+-    $data['comment_field_data']['subject']['title'] = $this->t('Title');
+-    $data['comment_field_data']['subject']['help'] = $this->t('The title of the comment.');
+-    $data['comment_field_data']['subject']['field']['default_formatter'] = 'comment_permalink';
++    $data[$data_table]['subject']['title'] = $this->t('Title');
++    $data[$data_table]['subject']['help'] = $this->t('The title of the comment.');
++    $data[$data_table]['subject']['field']['default_formatter'] = 'comment_permalink';
+ 
+-    $data['comment_field_data']['name']['title'] = $this->t('Author');
+-    $data['comment_field_data']['name']['help'] = $this->t("The name of the comment's author. Can be rendered as a link to the author's homepage.");
+-    $data['comment_field_data']['name']['field']['default_formatter'] = 'comment_username';
++    $data[$data_table]['name']['title'] = $this->t('Author');
++    $data[$data_table]['name']['help'] = $this->t("The name of the comment's author. Can be rendered as a link to the author's homepage.");
++    $data[$data_table]['name']['field']['default_formatter'] = 'comment_username';
+ 
+-    $data['comment_field_data']['homepage']['title'] = $this->t("Author's website");
+-    $data['comment_field_data']['homepage']['help'] = $this->t("The website address of the comment's author. Can be rendered as a link. Will be empty if the author is a registered user.");
++    $data[$data_table]['homepage']['title'] = $this->t("Author's website");
++    $data[$data_table]['homepage']['help'] = $this->t("The website address of the comment's author. Can be rendered as a link. Will be empty if the author is a registered user.");
+ 
+-    $data['comment_field_data']['mail']['help'] = $this->t('Email of user that posted the comment. Will be empty if the author is a registered user.');
++    $data[$data_table]['mail']['help'] = $this->t('Email of user that posted the comment. Will be empty if the author is a registered user.');
+ 
+-    $data['comment_field_data']['created']['title'] = $this->t('Post date');
+-    $data['comment_field_data']['created']['help'] = $this->t('Date and time of when the comment was created.');
++    $data[$data_table]['created']['title'] = $this->t('Post date');
++    $data[$data_table]['created']['help'] = $this->t('Date and time of when the comment was created.');
+ 
+-    $data['comment_field_data']['created_fulldata'] = [
++    $data[$data_table]['created_fulldata'] = [
+       'title' => $this->t('Created date'),
+       'help' => $this->t('Date in the form of CCYYMMDD.'),
+       'argument' => [
+@@ -46,7 +53,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['created_year_month'] = [
++    $data[$data_table]['created_year_month'] = [
+       'title' => $this->t('Created year + month'),
+       'help' => $this->t('Date in the form of YYYYMM.'),
+       'argument' => [
+@@ -55,7 +62,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['created_year'] = [
++    $data[$data_table]['created_year'] = [
+       'title' => $this->t('Created year'),
+       'help' => $this->t('Date in the form of YYYY.'),
+       'argument' => [
+@@ -64,7 +71,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['created_month'] = [
++    $data[$data_table]['created_month'] = [
+       'title' => $this->t('Created month'),
+       'help' => $this->t('Date in the form of MM (01 - 12).'),
+       'argument' => [
+@@ -73,7 +80,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['created_day'] = [
++    $data[$data_table]['created_day'] = [
+       'title' => $this->t('Created day'),
+       'help' => $this->t('Date in the form of DD (01 - 31).'),
+       'argument' => [
+@@ -82,7 +89,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['created_week'] = [
++    $data[$data_table]['created_week'] = [
+       'title' => $this->t('Created week'),
+       'help' => $this->t('Date in the form of WW (01 - 53).'),
+       'argument' => [
+@@ -91,10 +98,10 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['changed']['title'] = $this->t('Updated date');
+-    $data['comment_field_data']['changed']['help'] = $this->t('Date and time of when the comment was last updated.');
++    $data[$data_table]['changed']['title'] = $this->t('Updated date');
++    $data[$data_table]['changed']['help'] = $this->t('Date and time of when the comment was last updated.');
+ 
+-    $data['comment_field_data']['changed_fulldata'] = [
++    $data[$data_table]['changed_fulldata'] = [
+       'title' => $this->t('Changed date'),
+       'help' => $this->t('Date in the form of CCYYMMDD.'),
+       'argument' => [
+@@ -103,7 +110,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['changed_year_month'] = [
++    $data[$data_table]['changed_year_month'] = [
+       'title' => $this->t('Changed year + month'),
+       'help' => $this->t('Date in the form of YYYYMM.'),
+       'argument' => [
+@@ -112,7 +119,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['changed_year'] = [
++    $data[$data_table]['changed_year'] = [
+       'title' => $this->t('Changed year'),
+       'help' => $this->t('Date in the form of YYYY.'),
+       'argument' => [
+@@ -121,7 +128,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['changed_month'] = [
++    $data[$data_table]['changed_month'] = [
+       'title' => $this->t('Changed month'),
+       'help' => $this->t('Date in the form of MM (01 - 12).'),
+       'argument' => [
+@@ -130,7 +137,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['changed_day'] = [
++    $data[$data_table]['changed_day'] = [
+       'title' => $this->t('Changed day'),
+       'help' => $this->t('Date in the form of DD (01 - 31).'),
+       'argument' => [
+@@ -139,7 +146,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['changed_week'] = [
++    $data[$data_table]['changed_week'] = [
+       'title' => $this->t('Changed week'),
+       'help' => $this->t('Date in the form of WW (01 - 53).'),
+       'argument' => [
+@@ -148,10 +155,10 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['status']['title'] = $this->t('Approved status');
+-    $data['comment_field_data']['status']['help'] = $this->t('Whether the comment is approved (or still in the moderation queue).');
+-    $data['comment_field_data']['status']['filter']['label'] = $this->t('Approved comment status');
+-    $data['comment_field_data']['status']['filter']['type'] = 'yes-no';
++    $data[$data_table]['status']['title'] = $this->t('Approved status');
++    $data[$data_table]['status']['help'] = $this->t('Whether the comment is approved (or still in the moderation queue).');
++    $data[$data_table]['status']['filter']['label'] = $this->t('Approved comment status');
++    $data[$data_table]['status']['filter']['type'] = 'yes-no';
+ 
+     $data['comment']['approve_comment'] = [
+       'field' => [
+@@ -169,8 +176,8 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['entity_id']['field']['id'] = 'commented_entity';
+-    unset($data['comment_field_data']['entity_id']['relationship']);
++    $data[$data_table]['entity_id']['field']['id'] = 'commented_entity';
++    unset($data[$data_table]['entity_id']['relationship']);
+ 
+     $data['comment']['comment_bulk_form'] = [
+       'title' => $this->t('Comment operations bulk form'),
+@@ -180,18 +187,18 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['comment_field_data']['thread']['field'] = [
++    $data[$data_table]['thread']['field'] = [
+       'title' => $this->t('Depth'),
+       'help' => $this->t('Display the depth of the comment if it is threaded.'),
+       'id' => 'comment_depth',
+     ];
+-    $data['comment_field_data']['thread']['sort'] = [
++    $data[$data_table]['thread']['sort'] = [
+       'title' => $this->t('Thread'),
+       'help' => $this->t('Sort by the threaded order. This will keep child comments together with their parents.'),
+       'id' => 'comment_thread',
+     ];
+-    unset($data['comment_field_data']['thread']['filter']);
+-    unset($data['comment_field_data']['thread']['argument']);
++    unset($data[$data_table]['thread']['filter']);
++    unset($data[$data_table]['thread']['argument']);
+ 
+     $entities_types = \Drupal::entityTypeManager()->getDefinitions();
+ 
+@@ -201,20 +208,34 @@ public function getViewsData() {
+         continue;
+       }
+       if (\Drupal::service('comment.manager')->getFields($type)) {
+-        $data['comment_field_data'][$type] = [
++        if ($this->connection->driver() == 'mongodb') {
++          $base = $entity_type->getBaseTable();
++          $relationship_field = 'comment_translations.entity_id';
++          $left_field = 'comment_translations.entity_type';
++        }
++        else {
++          $base = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
++          $relationship_field = 'entity_id';
++          $left_field = 'entity_type';
++        }
++
++        $data[$data_table][$type] = [
+           'relationship' => [
+             'title' => $entity_type->getLabel(),
+             'help' => $this->t('The @entity_type to which the comment is a reply to.', ['@entity_type' => $entity_type->getLabel()]),
+-            'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
++            'base' => $base,
+             'base field' => $entity_type->getKey('id'),
+-            'relationship field' => 'entity_id',
++            'relationship field' => $relationship_field,
+             'id' => 'standard',
+             'label' => $entity_type->getLabel(),
+             'extra' => [
+               [
+-                'field' => 'entity_type',
++                // The left table in this join is comment and
++                // the field entity_type is from that table therefore it Should
++                // be "left_field" and not "field".
++                'left_field' => $left_field,
+                 'value' => $type,
+-                'table' => 'comment_field_data',
++                'table' => $data_table,
+               ],
+             ],
+           ],
+@@ -222,16 +243,16 @@ public function getViewsData() {
+       }
+     }
+ 
+-    $data['comment_field_data']['uid']['title'] = $this->t('Author uid');
+-    $data['comment_field_data']['uid']['help'] = $this->t('If you need more fields than the uid add the comment: author relationship');
+-    $data['comment_field_data']['uid']['relationship']['title'] = $this->t('Author');
+-    $data['comment_field_data']['uid']['relationship']['help'] = $this->t("The User ID of the comment's author.");
+-    $data['comment_field_data']['uid']['relationship']['label'] = $this->t('author');
++    $data[$data_table]['uid']['title'] = $this->t('Author uid');
++    $data[$data_table]['uid']['help'] = $this->t('If you need more fields than the uid add the comment: author relationship');
++    $data[$data_table]['uid']['relationship']['title'] = $this->t('Author');
++    $data[$data_table]['uid']['relationship']['help'] = $this->t("The User ID of the comment's author.");
++    $data[$data_table]['uid']['relationship']['label'] = $this->t('author');
+ 
+-    $data['comment_field_data']['pid']['title'] = $this->t('Parent CID');
+-    $data['comment_field_data']['pid']['relationship']['title'] = $this->t('Parent comment');
+-    $data['comment_field_data']['pid']['relationship']['help'] = $this->t('The parent comment');
+-    $data['comment_field_data']['pid']['relationship']['label'] = $this->t('parent');
++    $data[$data_table]['pid']['title'] = $this->t('Parent CID');
++    $data[$data_table]['pid']['relationship']['title'] = $this->t('Parent comment');
++    $data[$data_table]['pid']['relationship']['help'] = $this->t('The parent comment');
++    $data[$data_table]['pid']['relationship']['label'] = $this->t('parent');
+ 
+     // Define the base group of this table. Fields that don't have a group defined
+     // will go into this field by default.
+@@ -242,6 +263,13 @@ public function getViewsData() {
+       if ($type == 'comment' || !$entity_type->entityClassImplements(ContentEntityInterface::class) || !$entity_type->getBaseTable()) {
+         continue;
+       }
++      if ($this->connection->driver() == 'mongodb') {
++        $entity_type_table = $entity_type->getBaseTable();
++      }
++      else {
++        $entity_type_table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
++      }
++
+       // This relationship does not use the 'field id' column, if the entity has
+       // multiple comment-fields, then this might introduce duplicates, in which
+       // case the site-builder should enable aggregation and SUM the comment_count
+@@ -249,7 +277,7 @@ public function getViewsData() {
+       // {comment_entity_statistics} for each field as multiple joins between
+       // the same two tables is not supported.
+       if (\Drupal::service('comment.manager')->getFields($type)) {
+-        $data['comment_entity_statistics']['table']['join'][$entity_type->getDataTable() ?: $entity_type->getBaseTable()] = [
++        $data['comment_entity_statistics']['table']['join'][$entity_type_table] = [
+           'type' => 'LEFT',
+           'left_field' => $entity_type->getKey('id'),
+           'field' => 'entity_id',
+diff --git a/core/modules/comment/src/Hook/CommentHooks.php b/core/modules/comment/src/Hook/CommentHooks.php
+index e104fe978c6c7e10609e02b1b79874fe058977bc..2455dac87e5da35eb53bedb1a82bf7c839d72716 100644
+--- a/core/modules/comment/src/Hook/CommentHooks.php
++++ b/core/modules/comment/src/Hook/CommentHooks.php
+@@ -430,7 +430,7 @@ public function nodeSearchResult(EntityInterface $node) {
+   public function userCancel($edit, UserInterface $account, $method) {
+     switch ($method) {
+       case 'user_cancel_block_unpublish':
+-        $comments = \Drupal::entityTypeManager()->getStorage('comment')->loadByProperties(['uid' => $account->id()]);
++        $comments = \Drupal::entityTypeManager()->getStorage('comment')->loadByProperties(['uid' => (int) $account->id()]);
+         foreach ($comments as $comment) {
+           $comment->setUnpublished();
+           $comment->save();
+@@ -439,7 +439,7 @@ public function userCancel($edit, UserInterface $account, $method) {
+ 
+       case 'user_cancel_reassign':
+         /** @var \Drupal\comment\CommentInterface[] $comments */
+-        $comments = \Drupal::entityTypeManager()->getStorage('comment')->loadByProperties(['uid' => $account->id()]);
++        $comments = \Drupal::entityTypeManager()->getStorage('comment')->loadByProperties(['uid' => (int) $account->id()]);
+         foreach ($comments as $comment) {
+           $langcodes = array_keys($comment->getTranslationLanguages());
+           // For efficiency manually save the original comment before applying any
+@@ -462,7 +462,7 @@ public function userCancel($edit, UserInterface $account, $method) {
+   #[Hook('user_predelete')]
+   public function userPredelete($account) {
+     $entity_query = \Drupal::entityQuery('comment')->accessCheck(FALSE);
+-    $entity_query->condition('uid', $account->id());
++    $entity_query->condition('uid', (int) $account->id());
+     $cids = $entity_query->execute();
+     $comment_storage = \Drupal::entityTypeManager()->getStorage('comment');
+     $comments = $comment_storage->loadMultiple($cids);
+diff --git a/core/modules/comment/src/Hook/CommentViewsHooks.php b/core/modules/comment/src/Hook/CommentViewsHooks.php
+index 9ca89e72322ef8344110c27e630e0bc3842f4125..f1216f4031d9536bea5d8a754c0f179ff9fb09bd 100644
+--- a/core/modules/comment/src/Hook/CommentViewsHooks.php
++++ b/core/modules/comment/src/Hook/CommentViewsHooks.php
+@@ -25,13 +25,22 @@ public function viewsDataAlter(&$data): void {
+         'no group by' => TRUE,
+       ],
+     ];
++
++    // Get the database driver.
++    $driver = \Drupal::database()->driver();
++
+     // Provides an integration for each entity type except comment.
+     foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
+       if ($entity_type_id == 'comment' || !$entity_type->entityClassImplements(ContentEntityInterface::class) || !$entity_type->getBaseTable()) {
+         continue;
+       }
+       $fields = \Drupal::service('comment.manager')->getFields($entity_type_id);
+-      $base_table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
++      if ($entity_type->getDataTable() && ($driver != 'mongodb')) {
++        $base_table = $entity_type->getDataTable();
++      }
++      else {
++        $base_table = $entity_type->getBaseTable();
++      }
+       $args = ['@entity_type' => $entity_type_id];
+       if ($fields) {
+         $data[$base_table]['comments_link'] = [
+@@ -42,7 +51,7 @@ public function viewsDataAlter(&$data): void {
+           ],
+         ];
+         // Multilingual properties are stored in data table.
+-        if (!($table = $entity_type->getDataTable())) {
++        if (!($table = $entity_type->getDataTable()) || ($driver == 'mongodb')) {
+           $table = $entity_type->getBaseTable();
+         }
+         $data[$table]['uid_touch'] = [
+@@ -75,19 +84,19 @@ public function viewsDataAlter(&$data): void {
+             'relationship' => [
+               'group' => t('Comment'),
+               'label' => t('Comments'),
+-              'base' => 'comment_field_data',
++              'base' => $base_table,
+               'base field' => 'entity_id',
+               'relationship field' => $entity_type->getKey('id'),
+               'id' => 'standard',
+               'extra' => [
+-                        [
+-                          'field' => 'entity_type',
+-                          'value' => $entity_type_id,
+-                        ],
+-                        [
+-                          'field' => 'field_name',
+-                          'value' => $field_name,
+-                        ],
++                [
++                  'field' => 'entity_type',
++                  'value' => $entity_type_id,
++                ],
++                [
++                  'field' => 'field_name',
++                  'value' => $field_name,
++                ],
+               ],
+             ],
+           ];
+diff --git a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php
+index 9cbf62a7f59a8eb333ab6c67cfe0e2cc1f818c26..01c03dae3c7d90070b2c5b9616222cc59f5de2ed 100644
+--- a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php
++++ b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php
+@@ -31,7 +31,7 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
+     // core requires us to also know about the concept of 'published' and
+     // 'unpublished'.
+     if (!$this->currentUser->hasPermission('administer comments')) {
+-      $query->condition('status', CommentInterface::PUBLISHED);
++      $query->condition('status', (bool) CommentInterface::PUBLISHED);
+     }
+     return $query;
+   }
+@@ -75,7 +75,7 @@ public function validateReferenceableEntities(array $ids) {
+       $query = $this->buildEntityQuery();
+       // Mirror the conditions checked in buildEntityQuery().
+       if (!$this->currentUser->hasPermission('administer comments')) {
+-        $query->condition('status', 1);
++        $query->condition('status', TRUE);
+       }
+       $result = $query
+         ->condition($entity_type->getKey('id'), $ids, 'IN')
+@@ -90,13 +90,20 @@ public function validateReferenceableEntities(array $ids) {
+    */
+   public function entityQueryAlter(SelectInterface $query) {
+     parent::entityQueryAlter($query);
+-
+-    $tables = $query->getTables();
+-    $data_table = 'comment_field_data';
+-    if (!isset($tables['comment_field_data']['alias'])) {
+-      // If no conditions join against the comment data table, it should be
+-      // joined manually to allow node access processing.
+-      $query->innerJoin($data_table, NULL, "[base_table].[cid] = [$data_table].[cid] AND [$data_table].[default_langcode] = 1");
++    $driver = \Drupal::database()->driver();
++
++    if ($driver != 'mongodb') {
++      $tables = $query->getTables();
++      $data_table = 'comment_field_data';
++      if (!isset($tables['comment_field_data']['alias'])) {
++        // If no conditions join against the comment data table, it should be
++        // joined manually to allow node access processing.
++        $query->innerJoin($data_table, NULL,
++          $query->joinCondition()
++            ->compare('base_table.cid', "$data_table.cid")
++            ->condition("$data_table.default_langcode", TRUE)
++        );
++      }
+     }
+ 
+     // Historically, comments were always linked to 'node' entities, but that is
+@@ -121,7 +128,21 @@ public function entityQueryAlter(SelectInterface $query) {
+ 
+         // The Comment module doesn't implement per-comment access, so it
+         // checks instead that the user has access to the host entity.
+-        $entity_alias = $query->innerJoin($host_entity_field_data_table, 'n', "[%alias].[$id_key] = [$data_table].[entity_id] AND [$data_table].[entity_type] = '$host_entity_type_id'");
++        if ($driver == 'mongodb') {
++          $entity_alias = $query->innerJoin($host_entity_type->getBaseTable(), 'n',
++            $query->joinCondition()
++              ->compare("%alias.$id_key", 'comment_translations.entity_id')
++              ->condition("%alias.comment_translations.entity_type", $host_entity_type_id)
++          );
++        }
++        else {
++          $entity_alias = $query->innerJoin($host_entity_field_data_table, 'n',
++            $query->joinCondition()
++              ->compare("%alias.$id_key", "$data_table.entity_id")
++              ->condition("$data_table.entity_type", $host_entity_type_id)
++          );
++        }
++
+         // Pass the query to the entity access control.
+         $this->reAlterQuery($query, $host_entity_type_id . '_access', $entity_alias);
+ 
+@@ -131,7 +152,13 @@ public function entityQueryAlter(SelectInterface $query) {
+           // insufficient for nodes.
+           // @see \Drupal\node\Plugin\EntityReferenceSelection\NodeSelection::buildEntityQuery()
+           if (!$this->currentUser->hasPermission('bypass node access') && !$this->moduleHandler->hasImplementations('node_grants')) {
+-            $query->condition($entity_alias . '.status', 1);
++            if ($driver == 'mongodb') {
++              $query->addFilterUnwindPath($entity_alias . '.node_current_revision');
++              $query->condition($entity_alias . '.node_current_revision.status', TRUE);
++            }
++            else {
++              $query->condition($entity_alias . '.status', 1);
++            }
+           }
+         }
+       }
+diff --git a/core/modules/comment/src/Plugin/migrate/source/d6/Comment.php b/core/modules/comment/src/Plugin/migrate/source/d6/Comment.php
+index 6b1f4651cc4b22b305e7826354a7f3a4d9b603f7..381387f26bd1789ecad1f57fc60280ea01c6a97c 100644
+--- a/core/modules/comment/src/Plugin/migrate/source/d6/Comment.php
++++ b/core/modules/comment/src/Plugin/migrate/source/d6/Comment.php
+@@ -31,7 +31,7 @@ public function query() {
+         'hostname', 'timestamp', 'status', 'thread', 'name', 'mail', 'homepage',
+         'format',
+       ]);
+-    $query->innerJoin('node', 'n', '[c].[nid] = [n].[nid]');
++    $query->innerJoin('node', 'n', $query->joinCondition()->compare('c.nid', 'n.nid'));
+     $query->fields('n', ['type', 'language']);
+     $query->orderBy('c.timestamp');
+     return $query;
+diff --git a/core/modules/comment/src/Plugin/migrate/source/d7/CommentEntityTranslation.php b/core/modules/comment/src/Plugin/migrate/source/d7/CommentEntityTranslation.php
+index e14560fe0ce467a316fb07cbc4dd2c4021fba356..d5151dc275023527ec841d562ac055d83a30b237 100644
+--- a/core/modules/comment/src/Plugin/migrate/source/d7/CommentEntityTranslation.php
++++ b/core/modules/comment/src/Plugin/migrate/source/d7/CommentEntityTranslation.php
+@@ -33,8 +33,8 @@ public function query() {
+       ->condition('et.entity_type', 'comment')
+       ->condition('et.source', '', '<>');
+ 
+-    $query->innerJoin('comment', 'c', '[c].[cid] = [et].[entity_id]');
+-    $query->innerJoin('node', 'n', '[n].[nid] = [c].[nid]');
++    $query->innerJoin('comment', 'c', $query->joinCondition()->compare('c.cid', 'et.entity_id'));
++    $query->innerJoin('node', 'n', $query->joinCondition()->compare('n.nid', 'c.nid'));
+ 
+     $query->addField('n', 'type', 'node_type');
+ 
+diff --git a/core/modules/comment/src/Plugin/migrate/source/d7/Comment.php b/core/modules/comment/src/Plugin/migrate/source/d7/Comment.php
+index b06a3d504ba882f440fb76b72aebe2736229296f..e036053ca10f9b31f742ff0898d5f34defffbde3 100644
+--- a/core/modules/comment/src/Plugin/migrate/source/d7/Comment.php
++++ b/core/modules/comment/src/Plugin/migrate/source/d7/Comment.php
+@@ -27,7 +27,7 @@ class Comment extends FieldableEntity {
+    */
+   public function query() {
+     $query = $this->select('comment', 'c')->fields('c');
+-    $query->innerJoin('node', 'n', '[c].[nid] = [n].[nid]');
++    $query->innerJoin('node', 'n', $query->joinCondition()->compare('c.nid', 'n.nid'));
+     $query->addField('n', 'type', 'node_type');
+     $query->orderBy('c.created');
+     return $query;
+diff --git a/core/modules/comment/src/Plugin/views/wizard/Comment.php b/core/modules/comment/src/Plugin/views/wizard/Comment.php
+index 2dbd119beea97bc745f9cd24870e325bda72bc78..8e64c7b06667e0444a700c8231be4e7531142e2f 100644
+--- a/core/modules/comment/src/Plugin/views/wizard/Comment.php
++++ b/core/modules/comment/src/Plugin/views/wizard/Comment.php
+@@ -2,9 +2,13 @@
+ 
+ namespace Drupal\comment\Plugin\views\wizard;
+ 
++use Drupal\Core\Database\Connection;
++use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
++use Drupal\Core\Menu\MenuParentFormSelectorInterface;
+ use Drupal\Core\StringTranslation\TranslatableMarkup;
+ use Drupal\views\Attribute\ViewsWizard;
+ use Drupal\views\Plugin\views\wizard\WizardPluginBase;
++use Symfony\Component\DependencyInjection\ContainerInterface;
+ 
+ /**
+  * @todo replace numbers with constants.
+@@ -42,6 +46,32 @@ class Comment extends WizardPluginBase {
+     ],
+   ];
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
++    return new static(
++      $configuration,
++      $plugin_id,
++      $plugin_definition,
++      $container->get('entity_type.bundle.info'),
++      $container->get('menu.parent_form_selector'),
++      $container->get('database')
++    );
++  }
++
++  /**
++   * Constructs a WizardPluginBase object.
++   */
++  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, MenuParentFormSelectorInterface $parent_form_selector, Connection $connection) {
++    parent::__construct($configuration, $plugin_id, $plugin_definition, $bundle_info_service, $parent_form_selector, $connection);
++
++    if ($connection->driver() == 'mongodb') {
++      $this->base_table = 'comment';
++      $this->filters['status_node']['table'] = 'node';
++    }
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -64,9 +94,19 @@ protected function defaultDisplayOptions() {
+ 
+     // Add a relationship to nodes.
+     $display_options['relationships']['node']['id'] = 'node';
+-    $display_options['relationships']['node']['table'] = 'comment_field_data';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['relationships']['node']['table'] = 'comment';
++    }
++    else {
++      $display_options['relationships']['node']['table'] = 'comment_field_data';
++    }
+     $display_options['relationships']['node']['field'] = 'node';
+-    $display_options['relationships']['node']['entity_type'] = 'comment_field_data';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['relationships']['node']['entity_type'] = 'comment';
++    }
++    else {
++      $display_options['relationships']['node']['entity_type'] = 'comment_field_data';
++    }
+     $display_options['relationships']['node']['required'] = 1;
+     $display_options['relationships']['node']['plugin_id'] = 'standard';
+ 
+@@ -75,7 +115,12 @@ protected function defaultDisplayOptions() {
+ 
+     /* Field: Comment: Title */
+     $display_options['fields']['subject']['id'] = 'subject';
+-    $display_options['fields']['subject']['table'] = 'comment_field_data';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['fields']['subject']['table'] = 'comment';
++    }
++    else {
++      $display_options['fields']['subject']['table'] = 'comment_field_data';
++    }
+     $display_options['fields']['subject']['field'] = 'subject';
+     $display_options['fields']['subject']['entity_type'] = 'comment';
+     $display_options['fields']['subject']['entity_field'] = 'subject';
+diff --git a/core/modules/config_translation/src/Plugin/migrate/source/d6/ProfileFieldTranslation.php b/core/modules/config_translation/src/Plugin/migrate/source/d6/ProfileFieldTranslation.php
+index 2c120e2c1aaac19104f66f2b2a2c8f10d3124d7f..17aef147b180d6d5265388b082169db383a5fe3a 100644
+--- a/core/modules/config_translation/src/Plugin/migrate/source/d6/ProfileFieldTranslation.php
++++ b/core/modules/config_translation/src/Plugin/migrate/source/d6/ProfileFieldTranslation.php
+@@ -28,8 +28,8 @@ public function query() {
+     $query = parent::query();
+     $query->fields('i18n', ['property'])
+       ->fields('lt', ['lid', 'translation', 'language']);
+-    $query->leftJoin('i18n_strings', 'i18n', '[i18n].[objectid] = [pf].[name]');
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->leftJoin('i18n_strings', 'i18n', $query->joinCondition()->compare('i18n.objectid', 'pf.name'));
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+     return $query;
+   }
+ 
+diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php
+index 85ef099318566ce8d4e057aac393a10c88dbfa36..a2df544f50562a151fbb935603b95d92ce11ee10 100644
+--- a/core/modules/content_moderation/src/Entity/ContentModerationState.php
++++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
+@@ -10,6 +10,7 @@
+ use Drupal\Core\Entity\EntityInterface;
+ use Drupal\Core\Entity\EntityTypeInterface;
+ use Drupal\Core\Field\BaseFieldDefinition;
++use Drupal\Core\Language\LanguageInterface;
+ use Drupal\Core\TypedData\TranslatableInterface;
+ use Drupal\user\EntityOwnerTrait;
+ use Drupal\views\EntityViewsData;
+@@ -144,12 +145,39 @@ public static function loadFromModeratedEntity(EntityInterface $entity) {
+       // triggered elsewhere. In this case we have to match on the revision ID
+       // (instead of the loaded revision ID).
+       $revision_id = $entity->getLoadedRevisionId() ?: $entity->getRevisionId();
++
++      if ((\Drupal::database()->driver() === 'mongodb') && $entity->getLoadedRevisionId() && $entity->getRevisionId() && ($entity->getLoadedRevisionId() != $entity->getRevisionId())) {
++        // Get the langcodes for the entity.
++        $entity_langcodes = array_keys($entity->getTranslationLanguages());
++        // Load the revision for the loaded revision id.
++        $loaded_revision = $storage->loadRevision($entity->getLoadedRevisionId());
++        if ($loaded_revision && !empty($entity_langcodes)) {
++          $loaded_revision_langcodes = array_keys($loaded_revision->getTranslationLanguages());
++
++          // When the entity langcode is unspecified and the loaded revision
++          // has only a single langcode, then do not switch to the entity
++          // revision ID.
++          $switch_the_revision_id = TRUE;
++          if (($entity_langcodes === [LanguageInterface::LANGCODE_NOT_SPECIFIED]) && (count($loaded_revision_langcodes) === 1)) {
++            $switch_the_revision_id = FALSE;
++          }
++
++          // When there langcodes missing from the revision from the loaded
++          // revision ID compared to the ones in the entity, should we use the
++          // entity revision ID instead of the loaded revision ID.
++          $missing_langcodes = array_diff($loaded_revision_langcodes, $entity_langcodes);
++          if (!empty($loaded_revision_langcodes) && !empty($missing_langcodes) && $switch_the_revision_id) {
++            $revision_id = $entity->getRevisionId();
++          }
++        }
++      }
++
+       $ids = $storage->getQuery()
+         ->accessCheck(FALSE)
+         ->condition('content_entity_type_id', $entity->getEntityTypeId())
+-        ->condition('content_entity_id', $entity->id())
++        ->condition('content_entity_id', (int) $entity->id())
+         ->condition('workflow', $moderation_info->getWorkflowForEntity($entity)->id())
+-        ->condition('content_entity_revision_id', $revision_id)
++        ->condition('content_entity_revision_id', (int) $revision_id)
+         ->allRevisions()
+         ->execute();
+ 
+diff --git a/core/modules/content_moderation/src/ModerationInformation.php b/core/modules/content_moderation/src/ModerationInformation.php
+index 78d0dc6014acfca79622a8ad9349302c6b582b28..8aaafc9ad85fd0a3bbb4566f23e7d594a04b59fe 100644
+--- a/core/modules/content_moderation/src/ModerationInformation.php
++++ b/core/modules/content_moderation/src/ModerationInformation.php
+@@ -91,7 +91,7 @@ public function getDefaultRevisionId($entity_type_id, $entity_id) {
+     if ($storage = $this->entityTypeManager->getStorage($entity_type_id)) {
+       $result = $storage->getQuery()
+         ->currentRevision()
+-        ->condition($this->entityTypeManager->getDefinition($entity_type_id)->getKey('id'), $entity_id)
++        ->condition($this->entityTypeManager->getDefinition($entity_type_id)->getKey('id'), (int) $entity_id)
+         // No access check is performed here since this is an API function and
+         // should return the same ID regardless of the current user.
+         ->accessCheck(FALSE)
+diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
+index 41174b60490bbf0b8f325c13bd93dac8ad340d59..d8863d8e2d2f14189f50c7ebc153101e7e720e77 100644
+--- a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
++++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
+@@ -77,10 +77,10 @@ protected function loadContentModerationStateRevision(ContentEntityInterface $en
+     $revisions = $content_moderation_storage->getQuery()
+       ->accessCheck(FALSE)
+       ->condition('content_entity_type_id', $entity->getEntityTypeId())
+-      ->condition('content_entity_id', $entity->id())
++      ->condition('content_entity_id', (int) $entity->id())
+       // Ensure the correct revision is loaded in scenarios where a revision is
+       // being reverted.
+-      ->condition('content_entity_revision_id', $entity->isNewRevision() ? $entity->getLoadedRevisionId() : $entity->getRevisionId())
++      ->condition('content_entity_revision_id', $entity->isNewRevision() ? (int) $entity->getLoadedRevisionId() : (int) $entity->getRevisionId())
+       ->condition('workflow', $moderation_info->getWorkflowForEntity($entity)->id())
+       ->condition('langcode', $entity->language()->getId())
+       ->allRevisions()
+diff --git a/core/modules/content_moderation/src/ViewsData.php b/core/modules/content_moderation/src/ViewsData.php
+index 3fe9b7fa5f2838b4dc4a8906239a02690a098090..dc1e9187849d096f6b563399a90975f477d5beaa 100644
+--- a/core/modules/content_moderation/src/ViewsData.php
++++ b/core/modules/content_moderation/src/ViewsData.php
+@@ -55,8 +55,15 @@ public function getViewsData() {
+       return $this->moderationInformation->isModeratedEntityType($type);
+     });
+ 
++    $driver = \Drupal::database()->driver();
++
+     foreach ($entity_types_with_moderation as $entity_type) {
+-      $table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
++      if ($driver == 'mongodb') {
++        $table = $entity_type->getBaseTable();
++      }
++      else {
++        $table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
++      }
+ 
+       $data[$table]['moderation_state'] = [
+         'title' => $this->t('Moderation state'),
+@@ -69,17 +76,19 @@ public function getViewsData() {
+         'sort' => ['id' => 'moderation_state_sort'],
+       ];
+ 
+-      $revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
+-      $data[$revision_table]['moderation_state'] = [
+-        'title' => $this->t('Moderation state'),
+-        'field' => [
+-          'id' => 'moderation_state_field',
+-          'default_formatter' => 'content_moderation_state',
+-          'field_name' => 'moderation_state',
+-        ],
+-        'filter' => ['id' => 'moderation_state_filter', 'allow empty' => TRUE],
+-        'sort' => ['id' => 'moderation_state_sort'],
+-      ];
++      if ($driver != 'mongodb') {
++        $revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
++        $data[$revision_table]['moderation_state'] = [
++          'title' => $this->t('Moderation state'),
++          'field' => [
++            'id' => 'moderation_state_field',
++            'default_formatter' => 'content_moderation_state',
++            'field_name' => 'moderation_state',
++          ],
++          'filter' => ['id' => 'moderation_state_filter', 'allow empty' => TRUE],
++          'sort' => ['id' => 'moderation_state_sort'],
++        ];
++      }
+     }
+ 
+     return $data;
+diff --git a/core/modules/content_translation/src/Plugin/migrate/source/I18nQueryTrait.php b/core/modules/content_translation/src/Plugin/migrate/source/I18nQueryTrait.php
+index 29a64920b721d8bacec7bc8ee8393158d4a82e07..62c70cf3dbe04702d6a87667820f58cd6cc91e1b 100644
+--- a/core/modules/content_translation/src/Plugin/migrate/source/I18nQueryTrait.php
++++ b/core/modules/content_translation/src/Plugin/migrate/source/I18nQueryTrait.php
+@@ -73,7 +73,7 @@ protected function getPropertyNotInRowTranslation(Row $row, $property_not_in_row
+       ->fields('i18n', ['lid'])
+       ->condition('i18n.property', $property_not_in_row)
+       ->condition('i18n.objectid', $object_id);
+-    $query->leftJoin('locales_target', 'lt', '[i18n].[lid] = [lt].[lid]');
++    $query->leftJoin('locales_target', 'lt', $query->joinCondition()->compare('i18n.lid', 'lt.lid'));
+     $query->condition('lt.language', $language);
+     $query->addField('lt', 'translation');
+     $results = $query->execute()->fetchAssoc();
+diff --git a/core/modules/dblog/dblog.admin.inc b/core/modules/dblog/dblog.admin.inc
+index b5eae06ca8ddf9aeb2c58c96e19ce69d96fd3cc5..280ea3f50de3b7f245e0c956b5d0f016a812a155 100644
+--- a/core/modules/dblog/dblog.admin.inc
++++ b/core/modules/dblog/dblog.admin.inc
+@@ -27,14 +27,14 @@ function dblog_filters() {
+   if (!empty($types)) {
+     $filters['type'] = [
+       'title' => t('Type'),
+-      'where' => "w.type = ?",
++      'field' => "w.type",
+       'options' => $types,
+     ];
+   }
+ 
+   $filters['severity'] = [
+     'title' => t('Severity'),
+-    'where' => 'w.severity = ?',
++    'field' => 'w.severity',
+     'options' => RfcLogLevel::getLevels(),
+   ];
+ 
+diff --git a/core/modules/dblog/dblog.install b/core/modules/dblog/dblog.install
+index 78d780ed1e0025c3f9e9c67ecdee40023b6d5b96..9165b3535dedd80f1e0bc1a647ed891df58681ba 100644
+--- a/core/modules/dblog/dblog.install
++++ b/core/modules/dblog/dblog.install
+@@ -90,6 +90,10 @@ function dblog_schema(): array {
+     ],
+   ];
+ 
++  if (\Drupal::database()->driver() == 'mongodb') {
++    $schema['watchdog']['fields']['timestamp']['type'] = 'date';
++  }
++
+   return $schema;
+ }
+ 
+diff --git a/core/modules/dblog/dblog.module b/core/modules/dblog/dblog.module
+index f2f0eed1772a390282861b974b987915220ca2c7..fef5c01239de6b4799f0a1f836576be5d9b01546 100644
+--- a/core/modules/dblog/dblog.module
++++ b/core/modules/dblog/dblog.module
+@@ -11,6 +11,20 @@
+  *   List of uniquely defined database log message types.
+  */
+ function _dblog_get_message_types() {
+-  return \Drupal::database()->query('SELECT DISTINCT([type]) FROM {watchdog} ORDER BY [type]')
+-    ->fetchAllKeyed(0, 0);
++  $connection = \Drupal::database();
++  if ($connection->driver() == 'mongodb') {
++    $types = $connection->select('watchdog')
++      ->fields('watchdog', ['type'])
++      ->execute()
++      ->fetchCol();
++
++    $types = array_unique($types);
++    sort($types);
++
++    return array_combine($types, $types);
++  }
++  else {
++    return $connection->query('SELECT DISTINCT([type]) FROM {watchdog} ORDER BY [type]')
++      ->fetchAllKeyed(0, 0);
++  }
+ }
+diff --git a/core/modules/dblog/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php
+index 505996b40b262c38b4163418991d2e0d458ae060..0c2c78f83211c0d5e49825fe81255159d4b655b5 100644
+--- a/core/modules/dblog/src/Controller/DbLogController.php
++++ b/core/modules/dblog/src/Controller/DbLogController.php
+@@ -10,6 +10,7 @@
+ use Drupal\Core\Controller\ControllerBase;
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\Query\PagerSelectExtender;
++use Drupal\Core\Database\Query\SelectInterface;
+ use Drupal\Core\Database\Query\TableSortExtender;
+ use Drupal\Core\Datetime\DateFormatterInterface;
+ use Drupal\Core\Extension\ModuleHandlerInterface;
+@@ -104,7 +105,6 @@ public static function getLogLevelClassMap() {
+    */
+   public function overview(Request $request) {
+ 
+-    $filter = $this->buildFilterQuery($request);
+     $rows = [];
+ 
+     $classes = static::getLogLevelClassMap();
+@@ -152,11 +152,10 @@ public function overview(Request $request) {
+       'variables',
+       'link',
+     ]);
+-    $query->leftJoin('users_field_data', 'ufd', '[w].[uid] = [ufd].[uid]');
++    $query->leftJoin('users', 'ufd', $query->joinCondition()->compare('w.uid', 'ufd.uid'));
++
++    $this->addFilterToQuery($request, $query);
+ 
+-    if (!empty($filter['where'])) {
+-      $query->where($filter['where'], $filter['args']);
+-    }
+     $result = $query
+       ->limit(50)
+       ->orderByHeader($header)
+@@ -229,8 +228,8 @@ public function overview(Request $request) {
+   public function eventDetails($event_id) {
+     $query = $this->database->select('watchdog', 'w')
+       ->fields('w')
+-      ->condition('w.wid', $event_id);
+-    $query->leftJoin('users', 'u', '[u].[uid] = [w].[uid]');
++      ->condition('w.wid', (int) $event_id);
++    $query->leftJoin('users', 'u', $query->joinCondition()->compare('u.uid', 'w.uid'));
+     $query->addField('u', 'uid', 'uid');
+     $dblog = $query->execute()->fetchObject();
+ 
+@@ -304,14 +303,16 @@ public function eventDetails($event_id) {
+   /**
+    * Builds a query for database log administration filters based on session.
+    *
++   * This method retrieves the session-based filters from the request and applies
++   * them to the provided query object. If no filters are present, the query is
++   * left unchanged.
++   *
+    * @param \Symfony\Component\HttpFoundation\Request $request
+    *   The request.
+-   *
+-   * @return array|null
+-   *   An associative array with keys 'where' and 'args' or NULL if there were
+-   *   no filters set.
++   * @param \Drupal\Core\Database\Query\SelectInterface $query
++   *   The database query.
+    */
+-  protected function buildFilterQuery(Request $request) {
++  protected function addFilterToQuery(Request $request, SelectInterface &$query): void {
+     $session_filters = $request->getSession()->get('dblog_overview_filter', []);
+     if (empty($session_filters)) {
+       return;
+@@ -321,24 +322,29 @@ protected function buildFilterQuery(Request $request) {
+ 
+     $filters = dblog_filters();
+ 
+-    // Build query.
+-    $where = $args = [];
++    // Build the condition.
++    $condition_and = $query->getConnection()->condition('AND');
++    $condition_and_used = FALSE;
+     foreach ($session_filters as $key => $filter) {
+-      $filter_where = [];
++      $condition_or = $query->getConnection()->condition('OR');
++      $condition_or_used = FALSE;
+       foreach ($filter as $value) {
+-        $filter_where[] = $filters[$key]['where'];
+-        $args[] = $value;
++        if ($key == 'severity') {
++          $value = (int) $value;
++        }
++        if (in_array($value, array_keys($filters[$key]['options']))) {
++          $condition_or->condition($filters[$key]['field'], $value);
++          $condition_or_used = TRUE;
++        }
+       }
+-      if (!empty($filter_where)) {
+-        $where[] = '(' . implode(' OR ', $filter_where) . ')';
++      if ($condition_or_used) {
++        $condition_and->condition($condition_or);
++        $condition_and_used = TRUE;
+       }
+     }
+-    $where = !empty($where) ? implode(' AND ', $where) : '';
+-
+-    return [
+-      'where' => $where,
+-      'args' => $args,
+-    ];
++    if ($condition_and_used) {
++      $query->condition($condition_and);
++    }
+   }
+ 
+   /**
+@@ -425,13 +431,13 @@ public function topLogMessages($type) {
+     ];
+ 
+     $count_query = $this->database->select('watchdog');
+-    $count_query->addExpression('COUNT(DISTINCT([message]))');
++    $count_query->addExpressionCountDistinct('message');
+     $count_query->condition('type', $type);
+ 
+     $query = $this->database->select('watchdog', 'w')
+       ->extend(PagerSelectExtender::class)
+       ->extend(TableSortExtender::class);
+-    $query->addExpression('COUNT([wid])', 'count');
++    $query->addExpressionCount('wid', 'count');
+     $query = $query
+       ->fields('w', ['message', 'variables'])
+       ->condition('w.type', $type)
+diff --git a/core/modules/dblog/src/Plugin/rest/resource/DbLogResource.php b/core/modules/dblog/src/Plugin/rest/resource/DbLogResource.php
+index d9de3987af980fdcd32118813db2a7b7d0f70b41..b79168ce93ca5191469506c5087918652d5d5056 100644
+--- a/core/modules/dblog/src/Plugin/rest/resource/DbLogResource.php
++++ b/core/modules/dblog/src/Plugin/rest/resource/DbLogResource.php
+@@ -7,6 +7,8 @@
+ use Drupal\rest\Attribute\RestResource;
+ use Drupal\rest\Plugin\ResourceBase;
+ use Drupal\rest\ResourceResponse;
++use MongoDB\BSON\Binary;
++use MongoDB\BSON\UTCDateTime;
+ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+ 
+@@ -42,10 +44,19 @@ public function get($id = NULL) {
+     if ($id) {
+       $record = Database::getConnection()->select('watchdog', 'w')
+         ->fields('w')
+-        ->condition('wid', $id)
++        ->condition('wid', (int) $id)
+         ->execute()
+         ->fetchAssoc();
+       if (!empty($record)) {
++        if (isset($record['timestamp']) && ($record['timestamp'] instanceof UTCDateTime)) {
++          $record['timestamp'] = (int) $record['timestamp']->__toString();
++          $record['timestamp'] = $record['timestamp'] / 1000;
++          $record['timestamp'] = (string) $record['timestamp'];
++        }
++        if (isset($record['variables']) && ($record['variables'] instanceof Binary)) {
++          $record['variables'] = $record['variables']->getData();
++        }
++
+         return new ResourceResponse($record);
+       }
+ 
+diff --git a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstanceOptionTranslation.php b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstanceOptionTranslation.php
+index e1aefa69dfc2e5ff9a66b3cdf45fb3272196c7d7..378143a6366ed7d172b5b627ad9e05925928c394 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstanceOptionTranslation.php
++++ b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstanceOptionTranslation.php
+@@ -24,7 +24,7 @@ class FieldInstanceOptionTranslation extends FieldOptionTranslation {
+    */
+   public function query() {
+     $query = parent::query();
+-    $query->join('content_node_field_instance', 'cnfi', '[cnfi].[field_name] = [cnf].[field_name]');
++    $query->join('content_node_field_instance', 'cnfi', $query->joinCondition()->compare('cnfi.field_name', 'cnf.field_name'));
+     $query->addField('cnfi', 'type_name');
+     return $query;
+   }
+diff --git a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php
+index 4d2fd561f15cbad51f62ed8cb3b07a8a030735b2..b10f079992dd70a5544de27ce63caeb43dc24d2c 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php
++++ b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php
+@@ -67,7 +67,7 @@ public function query() {
+         'type',
+         'module',
+       ]);
+-    $query->join('content_node_field', 'cnf', '[cnfi].[field_name] = [cnf].[field_name]');
++    $query->join('content_node_field', 'cnf', $query->joinCondition()->compare('cnfi.field_name', 'cnf.field_name'));
+     $query->orderBy('cnfi.weight');
+ 
+     return $query;
+diff --git a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php
+index 53b8b9452362bffd23c74c5501c29d1715844d61..03ebe7ef58a95902d047cdd1feba6b3da72ba9a5 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php
++++ b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php
+@@ -74,7 +74,7 @@ public function query() {
+         'type',
+         'module',
+       ]);
+-    $query->join('content_node_field', 'cnf', '[cnfi].[field_name] = [cnf].[field_name]');
++    $query->join('content_node_field', 'cnf', $query->joinCondition()->compare('cnfi.field_name', 'cnf.field_name'));
+     $query->orderBy('cnfi.weight');
+ 
+     return $query;
+diff --git a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstance.php b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstance.php
+index 76d5ab30b4ac39dde74b59f9195cea6b3ea8b0f4..8269055164477d0721d60f8787def3a82babcd7d 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstance.php
++++ b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstance.php
+@@ -46,7 +46,7 @@ public function query() {
+     if (isset($this->configuration['node_type'])) {
+       $query->condition('cnfi.type_name', $this->configuration['node_type']);
+     }
+-    $query->join('content_node_field', 'cnf', '[cnf].[field_name] = [cnfi].[field_name]');
++    $query->join('content_node_field', 'cnf', $query->joinCondition()->compare('cnf.field_name', 'cnfi.field_name'));
+     $query->fields('cnf');
+     $query->orderBy('cnfi.field_name');
+     $query->orderBy('cnfi.type_name');
+diff --git a/core/modules/field/src/Plugin/migrate/source/d6/FieldLabelDescriptionTranslation.php b/core/modules/field/src/Plugin/migrate/source/d6/FieldLabelDescriptionTranslation.php
+index f39a50c30715b9065df7a6a916cafb9ea4bae99c..7c5956775fef602dab5ca6c4e09b9b40d178f254 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d6/FieldLabelDescriptionTranslation.php
++++ b/core/modules/field/src/Plugin/migrate/source/d6/FieldLabelDescriptionTranslation.php
+@@ -34,7 +34,7 @@ public function query() {
+       ->condition('property', 'widget_label')
+       ->condition('property', 'widget_description');
+     $query->condition($condition);
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+ 
+     return $query;
+   }
+diff --git a/core/modules/field/src/Plugin/migrate/source/d6/FieldOptionTranslation.php b/core/modules/field/src/Plugin/migrate/source/d6/FieldOptionTranslation.php
+index 0be145cf1da61ebc9b0e78ec57ddf46a534206ed..4a0bc71e16d48b10cfd2acdcdd237e1c4037103c 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d6/FieldOptionTranslation.php
++++ b/core/modules/field/src/Plugin/migrate/source/d6/FieldOptionTranslation.php
+@@ -34,8 +34,8 @@ public function query() {
+       ])
+       ->condition('i18n.type', 'field')
+       ->condition('property', 'option\_%', 'LIKE');
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
+-    $query->leftJoin('content_node_field', 'cnf', '[cnf].[field_name] = [i18n].[objectid]');
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
++    $query->leftJoin('content_node_field', 'cnf', $query->joinCondition()->compare('cnf.field_name', 'i18n.objectid'));
+     $query->addField('cnf', 'field_name');
+     $query->addField('cnf', 'global_settings');
+     // Minimize changes to the d6_field_option_translation.yml, which is copied
+diff --git a/core/modules/field/src/Plugin/migrate/source/d6/Field.php b/core/modules/field/src/Plugin/migrate/source/d6/Field.php
+index d451269a824795c260055033f3b62e848d59c00d..4962dcd04006986ee91d45ef6437aba6b35da009 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d6/Field.php
++++ b/core/modules/field/src/Plugin/migrate/source/d6/Field.php
+@@ -41,7 +41,7 @@ public function query() {
+       ])
+       ->distinct();
+     // Only import fields which are actually being used.
+-    $query->innerJoin('content_node_field_instance', 'cnfi', '[cnfi].[field_name] = [cnf].[field_name]');
++    $query->innerJoin('content_node_field_instance', 'cnfi', $query->joinCondition()->compare('cnfi.field_name', 'cnf.field_name'));
+ 
+     return $query;
+   }
+diff --git a/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php b/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
+index 562e3f2cad979418e265ffef951051d7c9f67906..0d49920594ac23ec291e6ddce3328498b64eafe9 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
++++ b/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
+@@ -66,7 +66,7 @@ public function query() {
+       ->condition('fc.storage_active', 1)
+       ->condition('fc.deleted', 0)
+       ->condition('fci.deleted', 0);
+-    $query->join('field_config', 'fc', '[fci].[field_id] = [fc].[id]');
++    $query->join('field_config', 'fc', $query->joinCondition()->compare('fci.field_id', 'fc.id'));
+ 
+     // Optionally filter by entity type and bundle.
+     if (isset($this->configuration['entity_type'])) {
+diff --git a/core/modules/field/src/Plugin/migrate/source/d7/FieldLabelDescriptionTranslation.php b/core/modules/field/src/Plugin/migrate/source/d7/FieldLabelDescriptionTranslation.php
+index e0968506f38ff518ed32aa8ade07c3dd6949f077..e7a3a9859c1a3babbb31c61d5ae01eeea03919c3 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d7/FieldLabelDescriptionTranslation.php
++++ b/core/modules/field/src/Plugin/migrate/source/d7/FieldLabelDescriptionTranslation.php
+@@ -50,9 +50,13 @@ public function query() {
+       ->condition('textgroup', 'field')
+       ->condition('objectid', '#allowed_values', '!=');
+     $query->condition($condition);
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+ 
+-    $query->leftJoin('field_config_instance', 'fci', '[fci].[bundle] = [i18n].[objectid] AND [fci].[field_name] = [i18n].[type]');
++    $query->leftJoin('field_config_instance', 'fci',
++      $query->joinCondition()
++        ->compare('fci.bundle', 'i18n.objectid')
++        ->compare('fci.field_name', 'i18n.type')
++    );
+     return $query;
+   }
+ 
+diff --git a/core/modules/field/src/Plugin/migrate/source/d7/FieldOptionTranslation.php b/core/modules/field/src/Plugin/migrate/source/d7/FieldOptionTranslation.php
+index 5a3968178e95b58dec28029b595720e438970cfe..406e35bd58db3bc18d35c0e1567a9a1ab9306b15 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d7/FieldOptionTranslation.php
++++ b/core/modules/field/src/Plugin/migrate/source/d7/FieldOptionTranslation.php
+@@ -24,8 +24,8 @@ class FieldOptionTranslation extends Field {
+    */
+   public function query() {
+     $query = parent::query();
+-    $query->leftJoin('i18n_string', 'i18n', '[i18n].[type] = [fc].[field_name]');
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->leftJoin('i18n_string', 'i18n', $query->joinCondition()->compare('i18n.type', 'fc.field_name'));
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+     $query->condition('i18n.textgroup', 'field')
+       ->condition('objectid', '#allowed_values');
+     // Add all i18n and locales_target fields.
+diff --git a/core/modules/field/src/Plugin/migrate/source/d7/Field.php b/core/modules/field/src/Plugin/migrate/source/d7/Field.php
+index 3d123115348d53c08619eb345913254afda4546c..9355122aad823df11b3800e418321f46f2719e99 100644
+--- a/core/modules/field/src/Plugin/migrate/source/d7/Field.php
++++ b/core/modules/field/src/Plugin/migrate/source/d7/Field.php
+@@ -36,7 +36,7 @@ public function query() {
+       ->condition('fc.storage_active', 1)
+       ->condition('fc.deleted', 0)
+       ->condition('fci.deleted', 0);
+-    $query->join('field_config_instance', 'fci', '[fc].[id] = [fci].[field_id]');
++    $query->join('field_config_instance', 'fci', $query->joinCondition()->compare('fc.id', 'fci.field_id'));
+ 
+     // The Title module fields are not migrated.
+     if ($this->moduleExists('title')) {
+diff --git a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
+index eab628e0a101a111a0a4c9e6b61ab7bb0ad5b410..473d99bc87f1a8509fab318fb8ff384a9d3058aa 100644
+--- a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
++++ b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
+@@ -228,7 +228,7 @@ public function validateCardinality(array &$element, FormStateInterface $form_st
+       // need to be incremented.
+       $entities_with_higher_delta = \Drupal::entityQuery($this->entity->getTargetEntityTypeId())
+         ->accessCheck(FALSE)
+-        ->condition($this->entity->getName() . '.%delta', $cardinality_number)
++        ->condition($this->entity->getName() . '.%delta', (int) $cardinality_number)
+         ->count()
+         ->execute();
+       if ($entities_with_higher_delta) {
+diff --git a/core/modules/file/src/FileStorage.php b/core/modules/file/src/FileStorage.php
+index dedf0851ace65771a6187e01ad0414327318086a..c15e00cae385c46c005fee87602120fbdd0d4139 100644
+--- a/core/modules/file/src/FileStorage.php
++++ b/core/modules/file/src/FileStorage.php
+@@ -14,12 +14,27 @@ class FileStorage extends SqlContentEntityStorage implements FileStorageInterfac
+    */
+   public function spaceUsed($uid = NULL, $status = FileInterface::STATUS_PERMANENT) {
+     $query = $this->database->select($this->entityType->getBaseTable(), 'f')
+-      ->condition('f.status', $status);
+-    $query->addExpression('SUM([f].[filesize])', 'filesize');
++      ->condition('f.status', (bool) $status);
+     if (isset($uid)) {
+-      $query->condition('f.uid', $uid);
++      $query->condition('f.uid', (int) $uid);
++    }
++
++    if ($this->database->driver() == 'mongodb') {
++      $files = $query->execute()->fetchAll();
++
++      $size = 0;
++      foreach ($files as $file) {
++        if (isset($file->filesize)) {
++          $size += $file->filesize;
++        }
++      }
++
++      return $size;
++    }
++    else {
++      $query->addExpressionSum('f.filesize', 'filesize');
++      return $query->execute()->fetchField();
+     }
+-    return $query->execute()->fetchField();
+   }
+ 
+ }
+diff --git a/core/modules/file/src/FileUsage/DatabaseFileUsageBackend.php b/core/modules/file/src/FileUsage/DatabaseFileUsageBackend.php
+index 8192cd7110a97b595010cd57b8316a0c28d21490..fe22695544c369110953e499d5fc928ab1ce96c8 100644
+--- a/core/modules/file/src/FileUsage/DatabaseFileUsageBackend.php
++++ b/core/modules/file/src/FileUsage/DatabaseFileUsageBackend.php
+@@ -48,7 +48,7 @@ public function __construct(ConfigFactoryInterface $config_factory, Connection $
+   public function add(FileInterface $file, $module, $type, $id, $count = 1) {
+     $this->connection->merge($this->tableName)
+       ->keys([
+-        'fid' => $file->id(),
++        'fid' => (int) $file->id(),
+         'module' => $module,
+         'type' => $type,
+         'id' => $id,
+@@ -67,7 +67,7 @@ public function delete(FileInterface $file, $module, $type = NULL, $id = NULL, $
+     // Delete rows that have an exact or less value to prevent empty rows.
+     $query = $this->connection->delete($this->tableName)
+       ->condition('module', $module)
+-      ->condition('fid', $file->id());
++      ->condition('fid', (int) $file->id());
+     if ($type && $id) {
+       $query
+         ->condition('type', $type)
+@@ -101,7 +101,7 @@ public function delete(FileInterface $file, $module, $type = NULL, $id = NULL, $
+   public function listUsage(FileInterface $file) {
+     $result = $this->connection->select($this->tableName, 'f')
+       ->fields('f', ['module', 'type', 'id', 'count'])
+-      ->condition('fid', $file->id())
++      ->condition('fid', (int) $file->id())
+       ->condition('count', 0, '>')
+       ->execute();
+     $references = [];
+diff --git a/core/modules/file/src/FileViewsData.php b/core/modules/file/src/FileViewsData.php
+index a5f1934c2ac4b07d1d222175224892dc9300ed66..87f542dfab34aecec772a84544d5d0f0ff4c7786 100644
+--- a/core/modules/file/src/FileViewsData.php
++++ b/core/modules/file/src/FileViewsData.php
+@@ -15,6 +15,19 @@ class FileViewsData extends EntityViewsData {
+   public function getViewsData() {
+     $data = parent::getViewsData();
+ 
++    if ($this->connection->driver() == 'mongodb') {
++      $node_table = 'node';
++      $users_table = 'users';
++      $term_table = 'taxonomy_term_data';
++      $comment_table = 'comment';
++    }
++    else {
++      $node_table = 'node_field_data';
++      $users_table = 'users_field_data';
++      $term_table = 'taxonomy_term_field_data';
++      $comment_table = 'comment_field_data';
++    }
++
+     // @todo There is no corresponding information in entity metadata.
+     $data['file_managed']['table']['base']['help'] = $this->t('Files maintained by Drupal and various modules.');
+     $data['file_managed']['table']['base']['defaults']['field'] = 'filename';
+@@ -78,7 +91,7 @@ public function getViewsData() {
+       ],
+       // Link ourselves to the {node_field_data} table
+       // so we can provide node->file relationships.
+-      'node_field_data' => [
++      $node_table => [
+         'join_id' => 'casted_int_field_join',
+         'cast' => 'right',
+         'field' => 'id',
+@@ -87,7 +100,7 @@ public function getViewsData() {
+       ],
+       // Link ourselves to the {users_field_data} table
+       // so we can provide user->file relationships.
+-      'users_field_data' => [
++      $users_table => [
+         'join_id' => 'casted_int_field_join',
+         'cast' => 'right',
+         'field' => 'id',
+@@ -126,7 +139,7 @@ public function getViewsData() {
+       'help' => $this->t('Content that is associated with this file, usually because this file is in a field on the content.'),
+       // Only provide this field/relationship/etc.,
+       // when the 'file_managed' base table is present.
+-      'skip base' => ['node_field_data', 'node_field_revision', 'users_field_data', 'comment_field_data', 'taxonomy_term_field_data'],
++      'skip base' => [$node_table, 'node_field_revision', $users_table, $comment_table, $term_table],
+       'real field' => 'id',
+       'relationship' => [
+         'id' => 'standard',
+@@ -134,7 +147,7 @@ public function getViewsData() {
+         'cast' => 'left',
+         'title' => $this->t('Content'),
+         'label' => $this->t('Content'),
+-        'base' => 'node_field_data',
++        'base' => $node_table,
+         'base field' => 'nid',
+         'relationship field' => 'id',
+         'extra' => [['table' => 'file_usage', 'field' => 'type', 'operator' => '=', 'value' => 'node']],
+@@ -145,7 +158,7 @@ public function getViewsData() {
+       'help' => $this->t('A file that is associated with this node, usually because it is in a field on the node.'),
+       // Only provide this field/relationship/etc.,
+       // when the 'node' base table is present.
+-      'skip base' => ['file_managed', 'users_field_data', 'comment_field_data', 'taxonomy_term_field_data'],
++      'skip base' => ['file_managed', $users_table, $comment_table, $term_table],
+       'real field' => 'fid',
+       'relationship' => [
+         'id' => 'standard',
+@@ -163,7 +176,7 @@ public function getViewsData() {
+       'help' => $this->t('A user that is associated with this file, usually because this file is in a field on the user.'),
+       // Only provide this field/relationship/etc.,
+       // when the 'file_managed' base table is present.
+-      'skip base' => ['node_field_data', 'node_field_revision', 'users_field_data', 'comment_field_data', 'taxonomy_term_field_data'],
++      'skip base' => [$node_table, 'node_field_revision', $users_table, $comment_table, $term_table],
+       'real field' => 'id',
+       'relationship' => [
+         'id' => 'standard',
+@@ -182,7 +195,7 @@ public function getViewsData() {
+       'help' => $this->t('A file that is associated with this user, usually because it is in a field on the user.'),
+       // Only provide this field/relationship/etc.,
+       // when the 'users' base table is present.
+-      'skip base' => ['file_managed', 'node_field_data', 'node_field_revision', 'comment_field_data', 'taxonomy_term_field_data'],
++      'skip base' => ['file_managed', $node_table, 'node_field_revision', $comment_table, $term_table],
+       'real field' => 'fid',
+       'relationship' => [
+         'id' => 'standard',
+@@ -202,7 +215,7 @@ public function getViewsData() {
+       'help' => $this->t('A comment that is associated with this file, usually because this file is in a field on the comment.'),
+       // Only provide this field/relationship/etc.,
+       // when the 'file_managed' base table is present.
+-      'skip base' => ['node_field_data', 'node_field_revision', 'users_field_data', 'comment_field_data', 'taxonomy_term_field_data'],
++      'skip base' => [$node_table, 'node_field_revision', $users_table, $comment_table, $term_table],
+       'real field' => 'id',
+       'relationship' => [
+         'id' => 'standard',
+@@ -210,7 +223,7 @@ public function getViewsData() {
+         'cast' => 'left',
+         'title' => $this->t('Comment'),
+         'label' => $this->t('Comment'),
+-        'base' => 'comment_field_data',
++        'base' => $comment_table,
+         'base field' => 'cid',
+         'relationship field' => 'id',
+         'extra' => [['table' => 'file_usage', 'field' => 'type', 'operator' => '=', 'value' => 'comment']],
+@@ -221,7 +234,7 @@ public function getViewsData() {
+       'help' => $this->t('A file that is associated with this comment, usually because it is in a field on the comment.'),
+       // Only provide this field/relationship/etc.,
+       // when the 'comment' base table is present.
+-      'skip base' => ['file_managed', 'node_field_data', 'node_field_revision', 'users_field_data', 'taxonomy_term_field_data'],
++      'skip base' => ['file_managed', $node_table, 'node_field_revision', $users_table, $term_table],
+       'real field' => 'fid',
+       'relationship' => [
+         'id' => 'standard',
+@@ -239,7 +252,7 @@ public function getViewsData() {
+       'help' => $this->t('A taxonomy term that is associated with this file, usually because this file is in a field on the taxonomy term.'),
+       // Only provide this field/relationship/etc.,
+       // when the 'file_managed' base table is present.
+-      'skip base' => ['node_field_data', 'node_field_revision', 'users_field_data', 'comment_field_data', 'taxonomy_term_field_data'],
++      'skip base' => [$node_table, 'node_field_revision', $users_table, $comment_table, $term_table],
+       'real field' => 'id',
+       'relationship' => [
+         'id' => 'standard',
+@@ -258,7 +271,7 @@ public function getViewsData() {
+       'help' => $this->t('A file that is associated with this taxonomy term, usually because it is in a field on the taxonomy term.'),
+       // Only provide this field/relationship/etc.,
+       // when the 'taxonomy_term_data' base table is present.
+-      'skip base' => ['file_managed', 'node_field_data', 'node_field_revision', 'users_field_data', 'comment_field_data'],
++      'skip base' => ['file_managed', $node_table, 'node_field_revision', $users_table, $comment_table],
+       'real field' => 'fid',
+       'relationship' => [
+         'id' => 'standard',
+diff --git a/core/modules/file/src/Hook/FileHooks.php b/core/modules/file/src/Hook/FileHooks.php
+index c3cb60ca529dcdc984c777cac4e14e02aba5e387..7b67196c65318f844d17d3b493b54da1083d014f 100644
+--- a/core/modules/file/src/Hook/FileHooks.php
++++ b/core/modules/file/src/Hook/FileHooks.php
+@@ -3,6 +3,7 @@
+ namespace Drupal\file\Hook;
+ 
+ use Drupal\Core\Form\FormStateInterface;
++use Drupal\Core\Database\Database;
+ use Drupal\Core\Datetime\Entity\DateFormat;
+ use Drupal\Core\StringTranslation\ByteSizeMarkup;
+ use Drupal\Core\Render\BubbleableMetadata;
+@@ -12,6 +13,7 @@
+ use Drupal\Core\Url;
+ use Drupal\Core\Routing\RouteMatchInterface;
+ use Drupal\Core\Hook\Attribute\Hook;
++use MongoDB\BSON\UTCDateTime;
+ 
+ /**
+  * Hook implementations for file.
+@@ -171,7 +173,12 @@ public function cron(): void {
+     // Only delete temporary files if older than $age. Note that automatic cleanup
+     // is disabled if $age set to 0.
+     if ($age) {
+-      $fids = \Drupal::entityQuery('file')->accessCheck(FALSE)->condition('status', FileInterface::STATUS_PERMANENT, '<>')->condition('changed', \Drupal::time()->getRequestTime() - $age, '<')->range(0, 100)->execute();
++      $timestamp = \Drupal::time()->getRequestTime() - $age;
++      if (Database::getConnection()->driver() == 'mongodb') {
++        $timestamp = new UTCDateTime($timestamp * 1000);
++      }
++
++      $fids = \Drupal::entityQuery('file')->accessCheck(FALSE)->condition('status', FileInterface::STATUS_PERMANENT, '<>')->condition('changed', $timestamp, '<')->range(0, 100)->execute();
+       $files = $file_storage->loadMultiple($fids);
+       foreach ($files as $file) {
+         $references = \Drupal::service('file.usage')->listUsage($file);
+diff --git a/core/modules/file/src/Hook/FileViewsHooks.php b/core/modules/file/src/Hook/FileViewsHooks.php
+index 2bc80cdb01f3c34c0fb6e9c117cff51bb63dd9f5..cf117e3885c7d09f4063d5a7cf07f79a042457f8 100644
+--- a/core/modules/file/src/Hook/FileViewsHooks.php
++++ b/core/modules/file/src/Hook/FileViewsHooks.php
+@@ -51,6 +51,15 @@ public function fieldViewsDataViewsDataAlter(array &$data, FieldStorageConfigInt
+     /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+     $table_mapping = $entity_type_manager->getStorage($entity_type_id)->getTableMapping();
+     [$label] = views_entity_field_label($entity_type_id, $field_name);
++
++    // MongoDB always uses the entity base table as the base.
++    if ((\Drupal::database()->driver() !== 'mongodb') && $entity_type->getDataTable()) {
++      $base = $entity_type->getDataTable();
++    }
++    else {
++      $base = $entity_type->getBaseTable();
++    }
++
+     $data['file_managed'][$pseudo_field_name]['relationship'] = [
+       'title' => t('@entity using @field', [
+         '@entity' => $entity_type->getLabel(),
+@@ -65,7 +74,7 @@ public function fieldViewsDataViewsDataAlter(array &$data, FieldStorageConfigInt
+         '@field' => $label,
+       ]),
+       'id' => 'entity_reverse',
+-      'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
++      'base' => $base,
+       'entity_type' => $entity_type_id,
+       'base field' => $entity_type->getKey('id'),
+       'field_name' => $field_name,
+@@ -79,6 +88,11 @@ public function fieldViewsDataViewsDataAlter(array &$data, FieldStorageConfigInt
+         ],
+       ],
+     ];
++
++    // Only set the field table when the database is not MongoDB.
++    if (\Drupal::database()->driver() == 'mongodb') {
++      unset($data['file_managed'][$pseudo_field_name]['relationship']['field table']);
++    }
+   }
+ 
+ }
+diff --git a/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php b/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php
+index 29359dbb9f742a118c55f36162854c5922d525c9..00a9795e65b3c9a962477fd09682b1f35d75c832 100644
+--- a/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php
++++ b/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php
+@@ -30,8 +30,8 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
+     //   become "permanent" after the containing entity gets validated and
+     //   saved.)
+     $query->condition($query->orConditionGroup()
+-      ->condition('status', FileInterface::STATUS_PERMANENT)
+-      ->condition('uid', $this->currentUser->id()));
++      ->condition('status', (bool) FileInterface::STATUS_PERMANENT)
++      ->condition('uid', (int) $this->currentUser->id()));
+     return $query;
+   }
+ 
+diff --git a/core/modules/help/src/Plugin/Search/HelpSearch.php b/core/modules/help/src/Plugin/Search/HelpSearch.php
+index c3122aa42a8e16fadce15673f4b1e7c72db38a40..3e0ba8ff41410658bdf3cb04acd3d778842faa4a 100644
+--- a/core/modules/help/src/Plugin/Search/HelpSearch.php
++++ b/core/modules/help/src/Plugin/Search/HelpSearch.php
+@@ -221,7 +221,11 @@ protected function findResults() {
+       ->condition('i.langcode', $this->languageManager->getCurrentLanguage()->getId())
+       ->extend(SearchQuery::class)
+       ->extend(PagerSelectExtender::class);
+-    $query->innerJoin('help_search_items', 'hsi', '[i].[sid] = [hsi].[sid] AND [i].[type] = :type', [':type' => $this->getType()]);
++    $query->innerJoin('help_search_items', 'hsi',
++      $query->joinCondition()
++        ->compare('i.sid', 'hsi.sid')
++        ->condition('i.type', $this->getType())
++    );
+     if ($denied_permissions) {
+       $query->condition('hsi.permission', $denied_permissions, 'NOT IN');
+     }
+@@ -317,7 +321,11 @@ public function updateIndex() {
+ 
+     $query = $this->database->select('help_search_items', 'hsi');
+     $query->fields('hsi', ['sid', 'section_plugin_id', 'topic_id']);
+-    $query->leftJoin('search_dataset', 'sd', '[sd].[sid] = [hsi].[sid] AND [sd].[type] = :type', [':type' => $this->getType()]);
++    $query->leftJoin('search_dataset', 'sd',
++      $query->joinCondition()
++        ->compare('sd.sid', 'hsi.sid')
++        ->condition('sd.type', $this->getType())
++    );
+     $query->where('[sd].[sid] IS NULL');
+     $query->groupBy('hsi.sid')
+       ->groupBy('hsi.section_plugin_id')
+@@ -330,7 +338,11 @@ public function updateIndex() {
+     if (count($items) < $limit) {
+       $query = $this->database->select('help_search_items', 'hsi');
+       $query->fields('hsi', ['sid', 'section_plugin_id', 'topic_id']);
+-      $query->leftJoin('search_dataset', 'sd', '[sd].[sid] = [hsi].[sid] AND [sd].[type] = :type', [':type' => $this->getType()]);
++      $query->leftJoin('search_dataset', 'sd',
++        $query->joinCondition()
++          ->compare('sd.sid', 'hsi.sid')
++          ->condition('sd.type', $this->getType())
++      );
+       $query->condition('sd.reindex', 0, '<>');
+       $query->groupBy('hsi.sid')
+         ->groupBy('hsi.section_plugin_id')
+@@ -415,7 +427,7 @@ public function updateTopicList() {
+ 
+           // Permission has changed, update record.
+           $this->database->update('help_search_items')
+-            ->condition('sid', $old_item->sid)
++            ->condition('sid', (int) $old_item->sid)
+             ->fields(['permission' => $permission])
+             ->execute();
+           unset($sids_to_remove[$old_item->sid]);
+@@ -444,8 +456,12 @@ public function updateTopicList() {
+    */
+   public function updateIndexState() {
+     $query = $this->database->select('help_search_items', 'hsi');
+-    $query->addExpression('COUNT(DISTINCT([hsi].[sid]))');
+-    $query->leftJoin('search_dataset', 'sd', '[hsi].[sid] = [sd].[sid] AND [sd].[type] = :type', [':type' => $this->getType()]);
++    $query->addExpressionCountDistinct('hsi.sid');
++    $query->leftJoin('search_dataset', 'sd',
++      $query->joinCondition()
++        ->compare('hsi.sid', 'sd.sid')
++        ->condition('sd.type', $this->getType())
++    );
+     $query->isNull('sd.sid');
+     $never_indexed = $query->execute()->fetchField();
+     $this->state->set('help_search_unindexed_count', $never_indexed);
+@@ -470,8 +486,12 @@ public function indexStatus() {
+       ->fetchField();
+ 
+     $query = $this->database->select('help_search_items', 'hsi');
+-    $query->addExpression('COUNT(DISTINCT([hsi].[sid]))');
+-    $query->leftJoin('search_dataset', 'sd', '[hsi].[sid] = [sd].[sid] AND [sd].[type] = :type', [':type' => $this->getType()]);
++    $query->addExpressionCountDistinct('hsi.sid');
++    $query->leftJoin('search_dataset', 'sd',
++      $query->joinCondition()
++        ->compare('hsi.sid', 'sd.sid')
++        ->condition('sd.type', $this->getType())
++    );
+     $condition = $this->database->condition('OR');
+     $condition->condition('sd.reindex', 0, '<>')
+       ->isNull('sd.sid');
+@@ -496,6 +516,9 @@ protected function removeItemsFromIndex($sids) {
+     // Remove items from our table in batches of 100, to avoid problems
+     // with having too many placeholders in database queries.
+     foreach (array_chunk($sids, 100) as $this_list) {
++      foreach ($this_list as &$item) {
++        $item = (int) $item;
++      }
+       $this->database->delete('help_search_items')
+         ->condition('sid', $this_list, 'IN')
+         ->execute();
+diff --git a/core/modules/history/history.install b/core/modules/history/history.install
+index 574c41a8dfa23f3fa5999b1cf3d0226756106d90..5d708ce788ee90e95dc6a301926d4ff1d9809fae 100644
+--- a/core/modules/history/history.install
++++ b/core/modules/history/history.install
+@@ -5,6 +5,8 @@
+  * Installation functions for History module.
+  */
+ 
++use Drupal\Core\Database\Database;
++
+ /**
+  * Implements hook_schema().
+  */
+@@ -39,6 +41,10 @@ function history_schema(): array {
+     ],
+   ];
+ 
++  if (Database::getConnection()->driver() == 'mongodb') {
++    $schema['history']['fields']['timestamp']['type'] = 'date';
++  }
++
+   return $schema;
+ }
+ 
+diff --git a/core/modules/history/history.module b/core/modules/history/history.module
+index 1d53c536054607004a55757ee3fdc92abec86704..451dcfdc2d3cc11319048d94edabba78daca1163 100644
+--- a/core/modules/history/history.module
++++ b/core/modules/history/history.module
+@@ -52,7 +52,7 @@ function history_read_multiple($nids) {
+     }
+     else {
+       // Initialize value if current user has not viewed the node.
+-      $nodes_to_read[$nid] = 0;
++      $nodes_to_read[(int) $nid] = 0;
+     }
+   }
+ 
+@@ -62,7 +62,7 @@ function history_read_multiple($nids) {
+ 
+   $result = \Drupal::database()->select('history', 'h')
+     ->fields('h', ['nid', 'timestamp'])
+-    ->condition('uid', \Drupal::currentUser()->id())
++    ->condition('uid', (int) \Drupal::currentUser()->id())
+     ->condition('nid', array_keys($nodes_to_read), 'IN')
+     ->execute();
+   foreach ($result as $row) {
+@@ -92,8 +92,8 @@ function history_write($nid, $account = NULL) {
+     $request_time = \Drupal::time()->getRequestTime();
+     \Drupal::database()->merge('history')
+       ->keys([
+-        'uid' => $account->id(),
+-        'nid' => $nid,
++        'uid' => (int) $account->id(),
++        'nid' => (int) $nid,
+       ])
+       ->fields(['timestamp' => $request_time])
+       ->execute();
+diff --git a/core/modules/history/src/Hook/HistoryHooks.php b/core/modules/history/src/Hook/HistoryHooks.php
+index 2b95304dfd2d7a86105f0e67c89344e165b7ba7e..0a3c1e615b0b00cd3e3ccc00f9156c3652c1b9bf 100644
+--- a/core/modules/history/src/Hook/HistoryHooks.php
++++ b/core/modules/history/src/Hook/HistoryHooks.php
+@@ -66,7 +66,7 @@ public function nodeViewAlter(array &$build, EntityInterface $node, EntityViewDi
+    */
+   #[Hook('node_delete')]
+   public function nodeDelete(EntityInterface $node) {
+-    \Drupal::database()->delete('history')->condition('nid', $node->id())->execute();
++    \Drupal::database()->delete('history')->condition('nid', (int) $node->id())->execute();
+   }
+ 
+   /**
+@@ -76,7 +76,7 @@ public function nodeDelete(EntityInterface $node) {
+   public function userCancel($edit, UserInterface $account, $method) {
+     switch ($method) {
+       case 'user_cancel_reassign':
+-        \Drupal::database()->delete('history')->condition('uid', $account->id())->execute();
++        \Drupal::database()->delete('history')->condition('uid', (int) $account->id())->execute();
+         break;
+     }
+   }
+@@ -86,7 +86,7 @@ public function userCancel($edit, UserInterface $account, $method) {
+    */
+   #[Hook('user_delete')]
+   public function userDelete($account) {
+-    \Drupal::database()->delete('history')->condition('uid', $account->id())->execute();
++    \Drupal::database()->delete('history')->condition('uid', (int) $account->id())->execute();
+   }
+ 
+ }
+diff --git a/core/modules/history/src/Hook/HistoryViewsHooks.php b/core/modules/history/src/Hook/HistoryViewsHooks.php
+index 465988fd5fe32889aef2eb1294f5b0c6e8a846bb..aa2c4c61a2184264ff5b1c273bd94e5802369957 100644
+--- a/core/modules/history/src/Hook/HistoryViewsHooks.php
++++ b/core/modules/history/src/Hook/HistoryViewsHooks.php
+@@ -23,19 +23,28 @@ public function viewsData(): array {
+     // alias it so that we can later add the real table for other purposes if we
+     // need it.
+     $data['history']['table']['group'] = t('Content');
++
++    // Which table to use as the base table for the entity type "node".
++    if (\Drupal::database()->driver() == 'mongodb') {
++      $node_table = 'node';
++    }
++    else {
++      $node_table = 'node_field_data';
++    }
++
+     // Explain how this table joins to others.
+     $data['history']['table']['join'] = [
+-          // Directly links to node table.
+-      'node_field_data' => [
++      // Directly links to node table.
++      $node_table => [
+         'table' => 'history',
+         'left_field' => 'nid',
+         'field' => 'nid',
+         'extra' => [
+-                  [
+-                    'field' => 'uid',
+-                    'value' => '***CURRENT_USER***',
+-                    'numeric' => TRUE,
+-                  ],
++          [
++            'field' => 'uid',
++            'value' => '***CURRENT_USER***',
++            'numeric' => TRUE,
++          ],
+         ],
+       ],
+     ];
+diff --git a/core/modules/layout_builder/src/InlineBlockEntityOperations.php b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
+index 25717b33e6edd2cd573c0db9e2f8e137e224ed5e..ccd7b56e9f490adb17a411c82a072588d6c104a0 100644
+--- a/core/modules/layout_builder/src/InlineBlockEntityOperations.php
++++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
+@@ -206,6 +206,9 @@ public function removeUnused($limit = 100) {
+    */
+   protected function getBlockIdsForRevisionIds(array $revision_ids) {
+     if ($revision_ids) {
++      foreach ($revision_ids as &$revision_id) {
++        $revision_id = (int) $revision_id;
++      }
+       $query = $this->blockContentStorage->getQuery()->accessCheck(FALSE);
+       $query->condition('revision_id', $revision_ids, 'IN');
+       $block_ids = $query->execute();
+diff --git a/core/modules/layout_builder/src/InlineBlockUsage.php b/core/modules/layout_builder/src/InlineBlockUsage.php
+index ab94d4c535bb8f46cc12ae1e9a24382ab45159cb..aaca3f2ce6679502450f76eb781ac9f453203d57 100644
+--- a/core/modules/layout_builder/src/InlineBlockUsage.php
++++ b/core/modules/layout_builder/src/InlineBlockUsage.php
+@@ -33,7 +33,7 @@ public function __construct(Connection $database) {
+   public function addUsage($block_content_id, EntityInterface $entity) {
+     $this->database->merge('inline_block_usage')
+       ->keys([
+-        'block_content_id' => $block_content_id,
++        'block_content_id' => (int) $block_content_id,
+         'layout_entity_id' => $entity->id(),
+         'layout_entity_type' => $entity->getEntityTypeId(),
+       ])->execute();
+@@ -69,6 +69,9 @@ public function removeByLayoutEntity(EntityInterface $entity) {
+    */
+   public function deleteUsage(array $block_content_ids) {
+     if (!empty($block_content_ids)) {
++      foreach ($block_content_ids as &$block_content_id) {
++        $block_content_id = (int) $block_content_id;
++      }
+       $query = $this->database->delete('inline_block_usage')->condition('block_content_id', $block_content_ids, 'IN');
+       $query->execute();
+     }
+@@ -79,7 +82,7 @@ public function deleteUsage(array $block_content_ids) {
+    */
+   public function getUsage($block_content_id) {
+     $query = $this->database->select('inline_block_usage');
+-    $query->condition('block_content_id', $block_content_id);
++    $query->condition('block_content_id', (int) $block_content_id);
+     $query->fields('inline_block_usage', ['layout_entity_id', 'layout_entity_type']);
+     $query->range(0, 1);
+     return $query->execute()->fetchObject();
+diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
+index e338223e5c1059bf8cbbfc61d484c03ab17eb0c2..87f64d608d28cc0cb60643ba265b139aab06008b 100644
+--- a/core/modules/locale/locale.bulk.inc
++++ b/core/modules/locale/locale.bulk.inc
+@@ -57,7 +57,7 @@ function locale_translate_batch_import_files(array $options, $force = FALSE) {
+   if (!$force) {
+     $result = \Drupal::database()->select('locale_file', 'lf')
+       ->fields('lf', ['langcode', 'uri', 'timestamp'])
+-      ->condition('langcode', $langcodes)
++      ->condition('langcode', $langcodes, 'IN')
+       ->execute()
+       ->fetchAllAssoc('uri');
+     foreach ($result as $uri => $info) {
+diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
+index cf034cc6f36fe48f3eb6c9b313c5a1754b907034..3ba836ad9eb8c359e71b25ef466595b6f6e3e735 100644
+--- a/core/modules/locale/locale.install
++++ b/core/modules/locale/locale.install
+@@ -5,6 +5,7 @@
+  * Install, update, and uninstall functions for the Locale module.
+  */
+ 
++use Drupal\Core\Database\Database;
+ use Drupal\Core\File\Exception\FileException;
+ use Drupal\Core\File\FileSystemInterface;
+ use Drupal\Core\Link;
+@@ -249,6 +250,15 @@ function locale_schema(): array {
+     ],
+     'primary key' => ['project', 'langcode'],
+   ];
++
++  if (Database::getConnection()->driver() == 'mongodb') {
++    $schema['locales_target']['fields']['customized']['type'] = 'bool';
++    $schema['locales_target']['fields']['customized']['default'] = FALSE;
++
++    $schema['locale_file']['fields']['timestamp']['type'] = 'date';
++    $schema['locale_file']['fields']['last_checked']['type'] = 'date';
++  }
++
+   return $schema;
+ }
+ 
+diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc
+index b0c577c14850bf257fa8f5c04fc15320ed2167ee..1ace50d9b1e1612b0ba6037ebca5abb954ede0a1 100644
+--- a/core/modules/locale/locale.translation.inc
++++ b/core/modules/locale/locale.translation.inc
+@@ -5,6 +5,7 @@
+  */
+ 
+ use Drupal\Core\StreamWrapper\StreamWrapperManager;
++use MongoDB\BSON\UTCDateTime;
+ 
+ /**
+  * Comparison result of source files timestamps.
+@@ -332,11 +333,14 @@ function locale_cron_fill_queue() {
+   // Determine which project+language should be updated.
+   $request_time = \Drupal::time()->getRequestTime();
+   $last = $request_time - $config->get('translation.update_interval_days') * 3600 * 24;
++  $connection = \Drupal::database();
++  if ($connection->driver() == 'mongodb') {
++    $last = new UTCDateTime($last * 1000);
++  }
+   $projects = \Drupal::service('locale.project')->getAll();
+   $projects = array_filter($projects, function ($project) {
+     return $project['status'] == 1;
+   });
+-  $connection = \Drupal::database();
+   $files = $connection->select('locale_file', 'f')
+     ->condition('f.project', array_keys($projects), 'IN')
+     ->condition('f.last_checked', $last, '<')
+diff --git a/core/modules/locale/src/StringDatabaseStorage.php b/core/modules/locale/src/StringDatabaseStorage.php
+index 74e94fca5d160ee85be61e572144ec962f51f38b..f4f63161d92673e55e2760ded54f6f0eed000a51 100644
+--- a/core/modules/locale/src/StringDatabaseStorage.php
++++ b/core/modules/locale/src/StringDatabaseStorage.php
+@@ -377,14 +377,16 @@ protected function dbStringSelect(array $conditions, array $options = []) {
+     if ($join) {
+       if (isset($conditions['language'])) {
+         // If we've got a language condition, we use it for the join.
+-        $query->$join('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", [
+-          ':langcode' => $conditions['language'],
+-        ]);
++        $query->$join('locales_target', 't',
++          $query->joinCondition()
++            ->compare('t.lid', 's.lid')
++            ->condition('t.language', $conditions['language'])
++        );
+         unset($conditions['language']);
+       }
+       else {
+         // Since we don't have a language, join with locale id only.
+-        $query->$join('locales_target', 't', "t.lid = s.lid");
++        $query->$join('locales_target', 't', $query->joinCondition()->compare('t.lid', 's.lid'));
+       }
+       if (!empty($options['translation'])) {
+         // We cannot just add all fields because 'lid' may get null values.
+diff --git a/core/modules/media/src/MediaAccessControlHandler.php b/core/modules/media/src/MediaAccessControlHandler.php
+index 4197d07968571573497e45ebf03b0e35d15dc2eb..0c0edf8547c6460330729bf8daede18bd111980c 100644
+--- a/core/modules/media/src/MediaAccessControlHandler.php
++++ b/core/modules/media/src/MediaAccessControlHandler.php
+@@ -57,7 +57,7 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
+     }
+ 
+     $type = $entity->bundle();
+-    $is_owner = ($account->id() && $account->id() === $entity->getOwnerId());
++    $is_owner = ($account->id() && $account->id() == $entity->getOwnerId());
+     switch ($operation) {
+       case 'view':
+         if ($entity->isPublished()) {
+@@ -127,7 +127,7 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
+           $entity_access = $entity->access('view', $account, TRUE);
+           if (!$entity->isDefaultRevision()) {
+             $media_storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+-            $entity_access->andIf($this->access($media_storage->load($entity->id()), 'view', $account, TRUE));
++            $entity_access->andIf($this->access($media_storage->load((int) $entity->id()), 'view', $account, TRUE));
+           }
+ 
+           return AccessResult::allowed()->cachePerPermissions()->andIf($entity_access);
+diff --git a/core/modules/media/src/MediaViewsData.php b/core/modules/media/src/MediaViewsData.php
+index fdfada27281974108b01845326c58ee79fe53357..7d0c21a71a070996b75c6e8f737daf9f930afd42 100644
+--- a/core/modules/media/src/MediaViewsData.php
++++ b/core/modules/media/src/MediaViewsData.php
+@@ -15,16 +15,25 @@ class MediaViewsData extends EntityViewsData {
+   public function getViewsData() {
+     $data = parent::getViewsData();
+ 
+-    $data['media_field_data']['table']['wizard_id'] = 'media';
+-    $data['media_field_revision']['table']['wizard_id'] = 'media_revision';
+-
+-    $data['media_field_data']['user_name']['filter'] = $data['media_field_data']['uid']['filter'];
+-    $data['media_field_data']['user_name']['filter']['title'] = $this->t('Authored by');
+-    $data['media_field_data']['user_name']['filter']['help'] = $this->t('The username of the content author.');
+-    $data['media_field_data']['user_name']['filter']['id'] = 'user_name';
+-    $data['media_field_data']['user_name']['filter']['real field'] = 'uid';
+-
+-    $data['media_field_data']['status_extra'] = [
++    if ($this->connection->driver() == 'mongodb') {
++      $data_table = 'media';
++      $revision_table = 'media';
++    }
++    else {
++      $data_table = 'media_field_data';
++      $revision_table = 'media_field_revision';
++    }
++
++    $data[$data_table]['table']['wizard_id'] = 'media';
++    $data[$revision_table]['table']['wizard_id'] = 'media_revision';
++
++    $data[$data_table]['user_name']['filter'] = $data[$data_table]['uid']['filter'];
++    $data[$data_table]['user_name']['filter']['title'] = $this->t('Authored by');
++    $data[$data_table]['user_name']['filter']['help'] = $this->t('The username of the content author.');
++    $data[$data_table]['user_name']['filter']['id'] = 'user_name';
++    $data[$data_table]['user_name']['filter']['real field'] = 'uid';
++
++    $data[$data_table]['status_extra'] = [
+       'title' => $this->t('Published status or admin user'),
+       'help' => $this->t('Filters out unpublished media if the current user cannot view it.'),
+       'filter' => [
+diff --git a/core/modules/media/src/Plugin/EntityReferenceSelection/MediaSelection.php b/core/modules/media/src/Plugin/EntityReferenceSelection/MediaSelection.php
+index 01b517c321c6e4ea74d43e15b3ab9863b46e6d81..3f98d839ca58154fb3bcd7efbc0101cce41815c1 100644
+--- a/core/modules/media/src/Plugin/EntityReferenceSelection/MediaSelection.php
++++ b/core/modules/media/src/Plugin/EntityReferenceSelection/MediaSelection.php
+@@ -27,7 +27,7 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
+     // Ensure that users with insufficient permission cannot see unpublished
+     // entities.
+     if (!$this->currentUser->hasPermission('administer media')) {
+-      $query->condition('status', 1);
++      $query->condition('status', TRUE);
+     }
+     return $query;
+   }
+diff --git a/core/modules/media/src/Plugin/views/wizard/Media.php b/core/modules/media/src/Plugin/views/wizard/Media.php
+index 322f04fc2885d2a59577bdfce798dc55a8ffdf96..1606a49d50f9bec66303991f2e06c44a80c2f768 100644
+--- a/core/modules/media/src/Plugin/views/wizard/Media.php
++++ b/core/modules/media/src/Plugin/views/wizard/Media.php
+@@ -2,9 +2,13 @@
+ 
+ namespace Drupal\media\Plugin\views\wizard;
+ 
++use Drupal\Core\Database\Connection;
++use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
++use Drupal\Core\Menu\MenuParentFormSelectorInterface;
+ use Drupal\Core\StringTranslation\TranslatableMarkup;
+ use Drupal\views\Attribute\ViewsWizard;
+ use Drupal\views\Plugin\views\wizard\WizardPluginBase;
++use Symfony\Component\DependencyInjection\ContainerInterface;
+ 
+ /**
+  * Provides Views creation wizard for Media.
+@@ -23,6 +27,32 @@ class Media extends WizardPluginBase {
+    */
+   protected $createdColumn = 'media_field_data-created';
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
++    return new static(
++      $configuration,
++      $plugin_id,
++      $plugin_definition,
++      $container->get('entity_type.bundle.info'),
++      $container->get('menu.parent_form_selector'),
++      $container->get('database')
++    );
++  }
++
++  /**
++   * Constructs a WizardPluginBase object.
++   */
++  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, MenuParentFormSelectorInterface $parent_form_selector, Connection $connection) {
++    parent::__construct($configuration, $plugin_id, $plugin_definition, $bundle_info_service, $parent_form_selector, $connection);
++
++    if ($connection->driver() == 'mongodb') {
++      $this->base_table = 'media';
++      $this->createdColumn = 'media-created';
++    }
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -48,7 +78,12 @@ protected function defaultDisplayOptions() {
+     // Add the name field, so that the display has content if the user switches
+     // to a row style that uses fields.
+     $display_options['fields']['name']['id'] = 'name';
+-    $display_options['fields']['name']['table'] = 'media_field_data';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['fields']['name']['table'] = 'media';
++    }
++    else {
++      $display_options['fields']['name']['table'] = 'media_field_data';
++    }
+     $display_options['fields']['name']['field'] = 'name';
+     $display_options['fields']['name']['entity_type'] = 'media';
+     $display_options['fields']['name']['entity_field'] = 'media';
+diff --git a/core/modules/media/src/Plugin/views/wizard/MediaRevision.php b/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
+index f7a2aafee23ee8e86aadaa7a5bfe98d3b2d431e6..dbedd54c78067ac80e10c6bc0299155bcf4b82fe 100644
+--- a/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
++++ b/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
+@@ -2,9 +2,13 @@
+ 
+ namespace Drupal\media\Plugin\views\wizard;
+ 
++use Drupal\Core\Database\Connection;
++use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
++use Drupal\Core\Menu\MenuParentFormSelectorInterface;
+ use Drupal\Core\StringTranslation\TranslatableMarkup;
+ use Drupal\views\Attribute\ViewsWizard;
+ use Drupal\views\Plugin\views\wizard\WizardPluginBase;
++use Symfony\Component\DependencyInjection\ContainerInterface;
+ 
+ /**
+  * Provides Views creation wizard for Media revisions.
+@@ -23,6 +27,32 @@ class MediaRevision extends WizardPluginBase {
+    */
+   protected $createdColumn = 'media_field_revision-created';
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
++    return new static(
++      $configuration,
++      $plugin_id,
++      $plugin_definition,
++      $container->get('entity_type.bundle.info'),
++      $container->get('menu.parent_form_selector'),
++      $container->get('database')
++    );
++  }
++
++  /**
++   * Constructs a WizardPluginBase object.
++   */
++  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, MenuParentFormSelectorInterface $parent_form_selector, Connection $connection) {
++    parent::__construct($configuration, $plugin_id, $plugin_definition, $bundle_info_service, $parent_form_selector, $connection);
++
++    if ($connection->driver() == 'mongodb') {
++      $this->base_table = 'media';
++      $this->createdColumn = 'media-created';
++    }
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -38,7 +68,12 @@ protected function defaultDisplayOptions() {
+ 
+     // Add the changed field.
+     $display_options['fields']['changed']['id'] = 'changed';
+-    $display_options['fields']['changed']['table'] = 'media_field_revision';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['fields']['changed']['table'] = 'media';
++    }
++    else {
++      $display_options['fields']['changed']['table'] = 'media_field_revision';
++    }
+     $display_options['fields']['changed']['field'] = 'changed';
+     $display_options['fields']['changed']['entity_type'] = 'media';
+     $display_options['fields']['changed']['entity_field'] = 'changed';
+@@ -60,7 +95,12 @@ protected function defaultDisplayOptions() {
+ 
+     // Add the name field.
+     $display_options['fields']['name']['id'] = 'name';
+-    $display_options['fields']['name']['table'] = 'media_field_revision';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['fields']['name']['table'] = 'media';
++    }
++    else {
++      $display_options['fields']['name']['table'] = 'media_field_revision';
++    }
+     $display_options['fields']['name']['field'] = 'name';
+     $display_options['fields']['name']['entity_type'] = 'media';
+     $display_options['fields']['name']['entity_field'] = 'name';
+diff --git a/core/modules/menu_link_content/src/MenuLinkContentStorage.php b/core/modules/menu_link_content/src/MenuLinkContentStorage.php
+index cce5c93c392d4f640d72774cad55ee74e2e99024..fb1e58637ca91caa676bf51455fda4ebbefd7e79 100644
+--- a/core/modules/menu_link_content/src/MenuLinkContentStorage.php
++++ b/core/modules/menu_link_content/src/MenuLinkContentStorage.php
+@@ -20,25 +20,40 @@ public function getMenuLinkIdsWithPendingRevisions() {
+     $langcode_field = $table_mapping->getColumnNames($this->entityType->getKey('langcode'))['value'];
+     $revision_default_field = $table_mapping->getColumnNames($this->entityType->getRevisionMetadataKey('revision_default'))['value'];
+ 
+-    $query = $this->database->select($this->getRevisionDataTable(), 'mlfr');
+-    $query->fields('mlfr', [$id_field]);
+-    $query->addExpression("MAX([mlfr].[$revision_field])", $revision_field);
+-
+-    $query->join($this->getRevisionTable(), 'mlr', "[mlfr].[$revision_field] = [mlr].[$revision_field] AND [mlr].[$revision_default_field] = 0");
+-
+-    $inner_select = $this->database->select($this->getRevisionDataTable(), 't');
+-    $inner_select->condition("t.$rta_field", '1');
+-    $inner_select->fields('t', [$id_field, $langcode_field]);
+-    $inner_select->addExpression("MAX([t].[$revision_field])", $revision_field);
+-    $inner_select
+-      ->groupBy("t.$id_field")
+-      ->groupBy("t.$langcode_field");
+-
+-    $query->join($inner_select, 'mr', "[mlfr].[$revision_field] = [mr].[$revision_field] AND [mlfr].[$langcode_field] = [mr].[$langcode_field]");
+-
+-    $query->groupBy("mlfr.$id_field");
+-
+-    return $query->execute()->fetchAllKeyed(1, 0);
++    if ($this->database->driver() == 'mongodb') {
++      // @todo Fix this query for MongoDB.
++      // See: https://git.drupalcode.org/project/drupal/-/commit/fbdccdc952c53fce12a81ac6640514c52e5fc3af
++      return [];
++    }
++    else {
++      $query = $this->database->select($this->getRevisionDataTable(), 'mlfr');
++      $query->fields('mlfr', [$id_field]);
++      $query->addExpressionMax("mlfr.$revision_field", $revision_field);
++
++      $query->join($this->getRevisionTable(), 'mlr',
++        $query->joinCondition()
++          ->compare("mlfr.$revision_field", "mlr.$revision_field")
++          ->condition("mlr.$revision_default_field", 0)
++      );
++
++      $inner_select = $this->database->select($this->getRevisionDataTable(), 't');
++      $inner_select->condition("t.$rta_field", '1');
++      $inner_select->fields('t', [$id_field, $langcode_field]);
++      $inner_select->addExpressionMax("t.$revision_field", $revision_field);
++      $inner_select
++        ->groupBy("t.$id_field")
++        ->groupBy("t.$langcode_field");
++
++      $query->join($inner_select, 'mr',
++        $query->joinCondition()
++          ->compare("mlfr.$revision_field", "mr.$revision_field")
++          ->compare("mlfr.$langcode_field", "mr.$langcode_field")
++      );
++
++      $query->groupBy("mlfr.$id_field");
++
++      return $query->execute()->fetchAllKeyed(1, 0);
++    }
+   }
+ 
+ }
+diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php b/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php
+index 3003310e6d8329305aff694c6d6b900a4dec1870..a7c34a32b2f9f379d14d45fec65fd9819aad7afd 100644
+--- a/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php
++++ b/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php
+@@ -42,12 +42,12 @@ public function query() {
+ 
+     // Add in the property, which is either title or description. Cast the mlid
+     // to text so PostgreSQL can make the join.
+-    $query->leftJoin(static::I18N_STRING_TABLE, 'i18n', 'CAST([ml].[mlid] AS CHAR(255)) = [i18n].[objectid]');
++    $query->leftJoin(static::I18N_STRING_TABLE, 'i18n', $query->joinCondition()->where('CAST([ml].[mlid] AS CHAR(255)) = [i18n].[objectid]'));
+     $query->addField('i18n', 'lid');
+     $query->addField('i18n', 'property');
+ 
+     // Add in the translation for the property.
+-    $query->innerJoin('locales_target', 'lt', '[i18n].[lid] = [lt].[lid]');
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('i18n.lid', 'lt.lid'));
+     $query->addField('lt', 'language');
+     $query->addField('lt', 'translation');
+     return $query;
+diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php b/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php
+index 85a2a4f61505369d178acd447f0cba2da8c1fc15..8cf330a046ce3c5601a6e3a2951fceaeb28e8cac 100644
+--- a/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php
++++ b/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php
+@@ -28,13 +28,13 @@ public function query() {
+ 
+     // Add in the property, which is either title or description. Cast the mlid
+     // to text so PostgreSQL can make the join.
+-    $query->leftJoin('i18n_string', 'i18n', 'CAST([ml].[mlid] AS CHAR(255)) = [i18n].[objectid]');
++    $query->leftJoin('i18n_string', 'i18n', $query->joinCondition()->where('CAST([ml].[mlid] AS CHAR(255)) = [i18n].[objectid]'));
+     $query->fields('i18n', ['lid', 'objectid', 'property', 'textgroup'])
+       ->condition('i18n.textgroup', 'menu')
+       ->condition('i18n.type', 'item');
+ 
+     // Add in the translation for the property.
+-    $query->innerJoin('locales_target', 'lt', '[i18n].[lid] = [lt].[lid]');
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('i18n.lid', 'lt.lid'));
+     $query->addField('lt', 'language', 'lt_language');
+     $query->fields('lt', ['translation']);
+     $query->isNotNull('lt.language');
+diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php b/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
+index 9af552b56e2ffff0b90eaa3cdc235d1e2f49f9c8..ea515007def2af623a002c33885259e4c95ad87f 100644
+--- a/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
++++ b/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
+@@ -67,7 +67,7 @@ public function query() {
+     if (isset($this->configuration['menu_name'])) {
+       $query->condition('ml.menu_name', (array) $this->configuration['menu_name'], 'IN');
+     }
+-    $query->leftJoin('menu_links', 'pl', '[ml].[plid] = [pl].[mlid]');
++    $query->leftJoin('menu_links', 'pl', $query->joinCondition()->compare('ml.plid', 'pl.mlid'));
+     $query->addField('pl', 'link_path', 'parent_link_path');
+     $query->orderBy('ml.depth');
+     $query->orderby('ml.mlid');
+diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
+index 2a639219a00eccc1309cb8b06592ff7a55673282..ea8bf908567f90e7f6a718d73f74cdd918da8492 100644
+--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
++++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
+@@ -55,7 +55,7 @@ protected function getFields($entity_type, $bundle = NULL) {
+ 
+       // Join the 'field_config' table and add the 'translatable' setting to the
+       // query.
+-      $query->leftJoin('field_config', 'fc', '[fci].[field_id] = [fc].[id]');
++      $query->leftJoin('field_config', 'fc', $query->joinCondition()->compare('fci.field_id', 'fc.id'));
+       $query->addField('fc', 'translatable');
+ 
+       $this->fieldInfo[$cid] = $query->execute()->fetchAllAssoc('field_name');
+diff --git a/core/modules/migrate/src/Controller/MigrateMessageController.php b/core/modules/migrate/src/Controller/MigrateMessageController.php
+index 385b89f343b8cfa8ee8f94c15a8c8a68480b18e3..77fc28f36eb2b6db2c65921b9cc22b0878441791 100644
+--- a/core/modules/migrate/src/Controller/MigrateMessageController.php
++++ b/core/modules/migrate/src/Controller/MigrateMessageController.php
+@@ -6,6 +6,7 @@
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\DatabaseConnectionRefusedException;
+ use Drupal\Core\Database\DatabaseNotFoundException;
++use Drupal\Core\Database\Query\SelectInterface;
+ use Drupal\Core\Form\FormBuilderInterface;
+ use Drupal\Core\StringTranslation\TranslatableMarkup;
+ use Drupal\Core\Url;
+@@ -186,13 +187,10 @@ public function details(string $migration_id, Request $request): array {
+       ->extend('\Drupal\Core\Database\Query\PagerSelectExtender')
+       ->extend('\Drupal\Core\Database\Query\TableSortExtender');
+     // Not all messages have a matching row in the map table.
+-    $query->leftJoin($map_table, 'map', 'msg.source_ids_hash = map.source_ids_hash');
++    $query->leftJoin($map_table, 'map', $query->joinCondition()->compare('msg.source_ids_hash', 'map.source_ids_hash'));
+     $query->fields('msg');
+     $query->fields('map');
+-    $filter = $this->buildFilterQuery($request);
+-    if (!empty($filter['where'])) {
+-      $query->where($filter['where'], $filter['args']);
+-    }
++    $this->addFilterQuery($request, $query);
+     $result = $query
+       ->limit(50)
+       ->orderByHeader($header)
+@@ -238,54 +236,57 @@ public function details(string $migration_id, Request $request): array {
+   }
+ 
+   /**
+-   * Builds a query for migrate message administration.
++   * Adds the condition to the query for migrate message administration.
+    *
+    * @param \Symfony\Component\HttpFoundation\Request $request
+    *   The request.
+-   *
+-   * @return array|null
+-   *   An associative array with keys 'where' and 'args' or NULL if there were
+-   *   no filters set.
++   * @param \Drupal\Core\Database\Query\SelectInterface $query
++   *   The database query.
+    */
+-  protected function buildFilterQuery(Request $request): ?array {
++  protected function addFilterQuery(Request $request, SelectInterface &$query): void {
+     $session_filters = $request->getSession()->get('migration_messages_overview_filter', []);
+     if (empty($session_filters)) {
+-      return NULL;
++      return;
+     }
+ 
+-    // Build query.
+-    $where = $args = [];
++    // Build conditions.
++    $condition_ors = [];
+     foreach ($session_filters as $filter) {
+-      $filter_where = [];
++      $condition = $query->orConditionGroup();
++      $filter_added = FALSE;
+ 
+       switch ($filter['type']) {
+         case 'array':
+           foreach ($filter['value'] as $value) {
+-            $filter_where[] = $filter['where'];
+-            $args[] = $value;
++            if ($filter['where'] == 'msg.level') {
++              $value = (int) $value;
++            }
++            $condition->condition($filter['where'], $value);
++            $filter_added = TRUE;
+           }
+           break;
+ 
+         case 'string':
+-          $filter_where[] = $filter['where'];
+-          $args[] = '%' . $filter['value'] . '%';
++          $condition->condition($filter['where'], '%' . $filter['value'] . '%', 'LIKE');
++          $filter_added = TRUE;
+           break;
+ 
+         default:
+-          $filter_where[] = $filter['where'];
+-          $args[] = $filter['value'];
++          if ($filter['where'] == 'msg.level') {
++            $filter['value'] = (int) $filter['value'];
++          }
++          $condition->condition($filter['where'], $filter['value']);
++          $filter_added = TRUE;
+       }
+ 
+-      if (!empty($filter_where)) {
+-        $where[] = '(' . implode(' OR ', $filter_where) . ')';
++      if ($filter_added) {
++        $condition_ors[] = $condition;
+       }
+     }
+-    $where = !empty($where) ? implode(' AND ', $where) : '';
+ 
+-    return [
+-      'where' => $where,
+-      'args' => $args,
+-    ];
++    foreach ($condition_ors as $condition_or) {
++      $query->condition($condition_or);
++    }
+   }
+ 
+   /**
+diff --git a/core/modules/migrate/src/Form/MessageForm.php b/core/modules/migrate/src/Form/MessageForm.php
+index 75522351418d19d88dd99ee46811dabb9ade4c72..4ef16782fc9e7017375ad79adcc26382d30860ad 100644
+--- a/core/modules/migrate/src/Form/MessageForm.php
++++ b/core/modules/migrate/src/Form/MessageForm.php
+@@ -82,12 +82,12 @@ public function buildForm(array $form, FormStateInterface $form_state) {
+   public function submitForm(array &$form, FormStateInterface $form_state) {
+     $filters['message'] = [
+       'title' => $this->t('message'),
+-      'where' => 'msg.message LIKE ?',
++      'where' => 'msg.message',
+       'type' => 'string',
+     ];
+     $filters['severity'] = [
+       'title' => $this->t('Severity'),
+-      'where' => 'msg.level = ?',
++      'where' => 'msg.level',
+       'type' => 'array',
+     ];
+     $session_filters = $this->getRequest()->getSession()->get('migration_messages_overview_filter', []);
+diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
+index 1df1b2f96c7d13e4438f162db238c9bad83a3015..31a3cfa5f8f83ce2ef1bc7bf5c3dd240b5f52b93 100644
+--- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
++++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
+@@ -737,7 +737,7 @@ public function saveMessage(array $source_id_values, $message, $level = Migratio
+    */
+   public function getMessages(array $source_id_values = [], $level = NULL) {
+     $query = $this->getDatabase()->select($this->messageTableName(), 'msg');
+-    $condition = sprintf('[msg].[%s] = [map].[%s]', $this::SOURCE_IDS_HASH, $this::SOURCE_IDS_HASH);
++    $condition = $query->joinCondition()->compare('msg.' . $this::SOURCE_IDS_HASH, 'map.' . $this::SOURCE_IDS_HASH);
+     $query->addJoin('LEFT', $this->mapTableName(), 'map', $condition);
+     // Explicitly define the fields we want. The order will be preserved: source
+     // IDs, destination IDs (if possible), and then the rest.
+diff --git a/core/modules/migrate/src/Plugin/migrate/source/DummyQueryTrait.php b/core/modules/migrate/src/Plugin/migrate/source/DummyQueryTrait.php
+index 93509af71ce6cb86d55d1dc10166c5caabcda100..5fcced4ef29b1ed820fc03a4d33628d921b2e750 100644
+--- a/core/modules/migrate/src/Plugin/migrate/source/DummyQueryTrait.php
++++ b/core/modules/migrate/src/Plugin/migrate/source/DummyQueryTrait.php
+@@ -20,7 +20,7 @@ public function query() {
+     // anyway.
+     $query = $this->select(uniqid(), 's')
+       ->range(0, 1);
+-    $query->addExpression('1');
++    $query->addExpressionConstant('1');
+     return $query;
+   }
+ 
+diff --git a/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
+index 090e93837135aa658873526539337e8a262dfcb2..66720373fde6fcf41945f259ecd68f1912de6013 100644
+--- a/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
++++ b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
+@@ -279,14 +279,12 @@ protected function initializeIterator() {
+         // Build the join to the map table. Because the source key could have
+         // multiple fields, we need to build things up.
+         $count = 1;
+-        $map_join = '';
+-        $delimiter = '';
++        $map_join = $this->query->joinCondition();
+         foreach ($this->getIds() as $field_name => $field_schema) {
+           if (isset($field_schema['alias'])) {
+             $field_name = $field_schema['alias'] . '.' . $this->query->escapeField($field_name);
+           }
+-          $map_join .= "$delimiter$field_name = map.sourceid" . $count++;
+-          $delimiter = ' AND ';
++          $map_join->compare($field_name, "map.sourceid" . $count++);
+         }
+ 
+         $alias = $this->query->leftJoin($this->migration->getIdMap()
+diff --git a/core/modules/node/node.install b/core/modules/node/node.install
+index ddde78929ad540dea0e81f3bbe38a3a629aabff0..9958ea5d3a9083e0b6a5c0f79bef0bf629dc7abb 100644
+--- a/core/modules/node/node.install
++++ b/core/modules/node/node.install
+@@ -114,6 +114,28 @@ function node_schema(): array {
+     ],
+   ];
+ 
++  if (Database::getConnection()->driver() == 'mongodb') {
++    $schema['node_access']['fields']['fallback']['type'] = 'bool';
++    $schema['node_access']['fields']['fallback']['default'] = TRUE;
++    unset($schema['node_access']['fields']['fallback']['unsigned']);
++    unset($schema['node_access']['fields']['fallback']['size']);
++
++    $schema['node_access']['fields']['grant_view']['type'] = 'bool';
++    $schema['node_access']['fields']['grant_view']['default'] = FALSE;
++    unset($schema['node_access']['fields']['grant_view']['unsigned']);
++    unset($schema['node_access']['fields']['grant_view']['size']);
++
++    $schema['node_access']['fields']['grant_update']['type'] = 'bool';
++    $schema['node_access']['fields']['grant_update']['default'] = FALSE;
++    unset($schema['node_access']['fields']['grant_update']['unsigned']);
++    unset($schema['node_access']['fields']['grant_update']['size']);
++
++    $schema['node_access']['fields']['grant_delete']['type'] = 'bool';
++    $schema['node_access']['fields']['grant_delete']['default'] = FALSE;
++    unset($schema['node_access']['fields']['grant_delete']['unsigned']);
++    unset($schema['node_access']['fields']['grant_delete']['size']);
++  }
++
+   return $schema;
+ }
+ 
+diff --git a/core/modules/node/node.module b/core/modules/node/node.module
+index 917aa8e47f050e9ce60efc430e93d29e3cbbed93..bb88b5370ca2596df265d4d41e1901373fa7c09f 100644
+--- a/core/modules/node/node.module
++++ b/core/modules/node/node.module
+@@ -609,7 +609,7 @@ function _node_access_rebuild_batch_operation(&$context) {
+   // Process the next 20 nodes.
+   $limit = 20;
+   $nids = \Drupal::entityQuery('node')
+-    ->condition('nid', $context['sandbox']['current_node'], '>')
++    ->condition('nid', (int) $context['sandbox']['current_node'], '>')
+     ->sort('nid', 'ASC')
+     // Disable access checking since all nodes must be processed even if the
+     // user does not have access. And unless the current user has the bypass
+diff --git a/core/modules/node/src/Hook/NodeHooks1.php b/core/modules/node/src/Hook/NodeHooks1.php
+index 663d16fd7cd54b1af93432bab65e9151ed82215c..3571cf1372665d4f28d56ec17a6387f8960176d9 100644
+--- a/core/modules/node/src/Hook/NodeHooks1.php
++++ b/core/modules/node/src/Hook/NodeHooks1.php
+@@ -16,6 +16,7 @@
+ use Drupal\Core\Url;
+ use Drupal\Core\Routing\RouteMatchInterface;
+ use Drupal\Core\Hook\Attribute\Hook;
++use MongoDB\BSON\UTCDateTime;
+ 
+ /**
+  * Hook implementations for node.
+@@ -231,6 +232,17 @@ public function ranking() {
+     // Add relevance based on updated date, but only if it the scale values have
+     // been calculated in node_cron().
+     if ($node_min_max = \Drupal::state()->get('node.min_max_update_time')) {
++      if (isset($node_min_max['min_created']) && ($node_min_max['min_created'] instanceof UTCDateTime)) {
++        $node_min_max['min_created'] = (int) $node_min_max['min_created']->__toString();
++        $node_min_max['min_created'] = $node_min_max['min_created'] / 1000;
++        $node_min_max['min_created'] = (string) $node_min_max['min_created'];
++      }
++      if (isset($node_min_max['max_created']) && ($node_min_max['max_created'] instanceof UTCDateTime)) {
++        $node_min_max['max_created'] = (int) $node_min_max['max_created']->__toString();
++        $node_min_max['max_created'] = $node_min_max['max_created'] / 1000;
++        $node_min_max['max_created'] = (string) $node_min_max['max_created'];
++      }
++
+       $ranking['recent'] = [
+         'title' => t('Recently created'),
+             // Exponential decay with half life of 14% of the age range of nodes.
+@@ -251,7 +263,7 @@ public function ranking() {
+   public function userPredelete($account) {
+     // Delete nodes (current revisions).
+     // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
+-    $nids = \Drupal::entityQuery('node')->condition('uid', $account->id())->accessCheck(FALSE)->execute();
++    $nids = \Drupal::entityQuery('node')->condition('uid', (int) $account->id())->accessCheck(FALSE)->execute();
+     // Delete old revisions.
+     $storage_controller = \Drupal::entityTypeManager()->getStorage('node');
+     $nodes = $storage_controller->loadMultiple($nids);
+diff --git a/core/modules/node/src/Hook/NodeHooks.php b/core/modules/node/src/Hook/NodeHooks.php
+index 38d3fb4e69f156b586c6998aacc7eddc548c0e0b..b0538896345621c4b0cfd42bd32e32ef124f53eb 100644
+--- a/core/modules/node/src/Hook/NodeHooks.php
++++ b/core/modules/node/src/Hook/NodeHooks.php
+@@ -47,7 +47,7 @@ public function userCancelBlockUnpublish($edit, UserInterface $account, $method)
+     if ($method === 'user_cancel_block_unpublish') {
+       $nids = $this->nodeStorage->getQuery()
+         ->accessCheck(FALSE)
+-        ->condition('uid', $account->id())
++        ->condition('uid', (int) $account->id())
+         ->execute();
+       $this->moduleHandler->invoke('node', 'mass_update', [$nids, ['status' => 0], NULL, TRUE]);
+     }
+diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php
+index eea6cc10012719a9522f2b6b76965c5cafde5743..cbaeffa5c3cd315ed981b93d533cffb1287d9917 100644
+--- a/core/modules/node/src/NodeGrantDatabaseStorage.php
++++ b/core/modules/node/src/NodeGrantDatabaseStorage.php
+@@ -83,18 +83,25 @@ public function access(NodeInterface $node, $operation, AccountInterface $accoun
+ 
+     // Check the database for potential access grants.
+     $query = $this->database->select('node_access');
+-    $query->addExpression('1');
+-    // Only interested for granting in the current operation.
+-    $query->condition('grant_' . $operation, 1, '>=');
++    if ($this->database->driver() == 'mongodb') {
++      $query->fields('node_access', ['nid', 'langcode', 'gid', 'realm']);
++      // Only interested for granting in the current operation.
++      $query->condition('grant_' . $operation, TRUE);
++    }
++    else {
++      $query->addExpressionConstant('1');
++      // Only interested for granting in the current operation.
++      $query->condition('grant_' . $operation, TRUE, '>=');
++    }
+     // Check for grants for this node and the correct langcode. New translations
+     // do not yet have a langcode and must check the fallback node record.
+     $nids = $query->andConditionGroup()
+-      ->condition('nid', $node->id());
++      ->condition('nid', (int) $node->id());
+     if (!$node->isNewTranslation()) {
+       $nids->condition('langcode', $node->language()->getId());
+     }
+     else {
+-      $nids->condition('fallback', 1);
++      $nids->condition('fallback', TRUE);
+     }
+     // If the node is published, also take the default grant into account. The
+     // default is saved with a node ID of 0.
+@@ -127,7 +134,15 @@ public function access(NodeInterface $node, $operation, AccountInterface $accoun
+       return $access_result;
+     };
+ 
+-    if ($query->execute()->fetchField()) {
++    if ($this->database->driver() == 'mongodb') {
++      $count = $query->execute()->fetchAll();
++      $query_result = count($count);
++    }
++    else {
++      $query_result = $query->execute()->fetchField();
++    }
++
++    if ($query_result) {
+       return $set_cacheability(AccessResult::allowed());
+     }
+     else {
+@@ -140,17 +155,32 @@ public function access(NodeInterface $node, $operation, AccountInterface $accoun
+    */
+   public function checkAll(AccountInterface $account) {
+     $query = $this->database->select('node_access');
+-    $query->addExpression('COUNT(*)');
+-    $query
+-      ->condition('nid', 0)
+-      ->condition('grant_view', 1, '>=');
++    if ($this->database->driver() == 'mongodb') {
++      $query->fields('node_access', ['nid', 'langcode', 'gid', 'realm']);
++      $query
++        ->condition('nid', 0)
++        ->condition('grant_view', TRUE);
++    }
++    else {
++      $query->addExpressionCountAll();
++      $query
++        ->condition('nid', 0)
++        ->condition('grant_view', TRUE, '>=');
++    }
+ 
+     $grants = $this->buildGrantsQueryCondition(node_access_grants('view', $account));
+ 
+     if (count($grants) > 0) {
+       $query->condition($grants);
+     }
+-    return $query->execute()->fetchField();
++
++    if ($this->database->driver() == 'mongodb') {
++      $count = $query->execute()->fetchAll();
++      return count($count);
++    }
++    else {
++      return $query->execute()->fetchField();
++    }
+   }
+ 
+   /**
+@@ -174,46 +204,71 @@ public function alterQuery($query, array $tables, $operation, AccountInterface $
+     foreach ($tables as $table_alias => $tableinfo) {
+       $table = $tableinfo['table'];
+       if (!($table instanceof SelectInterface) && $table == $base_table) {
+-        // Set the subquery.
+-        $subquery = $this->database->select('node_access', 'na')
+-          ->fields('na', ['nid']);
++        if ($this->database->driver() == 'mongodb') {
++          // Attach conditions to the sub-query for nodes.
++          if ($grants_exist) {
++            $query->condition($grant_conditions);
++          }
++
++          $query->condition('grant_' . $operation, TRUE);
++
++          if ($is_multilingual) {
++            // If no specific langcode to check for is given, use the grant entry
++            // which is set as a fallback.
++            // If a specific langcode is given, use the grant entry for it.
++            if ($langcode === FALSE) {
++              $query->condition('fallback', TRUE);
++            }
++            else {
++              $query->condition('langcode', $langcode);
++            }
++          }
+ 
+-        // Attach conditions to the sub-query for nodes.
+-        if ($grants_exist) {
+-          $subquery->condition($grant_conditions);
++          $query->addJoin('INNER', 'node_access', 'na', $query->joinCondition()->compare('na.nid', "$base_table.nid"));
++          $query->unwindJoinAndAddFields('na', ['nid', 'langcode', 'fallback', 'gid', 'realm', 'grant_' . $operation]);
+         }
+-        $subquery->condition('na.grant_' . $operation, 1, '>=');
+-
+-        // Add langcode-based filtering if this is a multilingual site.
+-        if ($is_multilingual) {
+-          // If no specific langcode to check for is given, use the grant entry
+-          // which is set as a fallback.
+-          // If a specific langcode is given, use the grant entry for it.
+-          if ($langcode === FALSE) {
+-            $subquery->condition('na.fallback', 1, '=');
++        else {
++          // Set the subquery.
++          $subquery = $this->database->select('node_access', 'na')
++            ->fields('na', ['nid']);
++
++          // Attach conditions to the sub-query for nodes.
++          if ($grants_exist) {
++            $subquery->condition($grant_conditions);
+           }
+-          else {
+-            $subquery->condition('na.langcode', $langcode, '=');
++          $subquery->condition('na.grant_' . $operation, TRUE, '>=');
++
++          // Add langcode-based filtering if this is a multilingual site.
++          if ($is_multilingual) {
++            // If no specific langcode to check for is given, use the grant entry
++            // which is set as a fallback.
++            // If a specific langcode is given, use the grant entry for it.
++            if ($langcode === FALSE) {
++              $subquery->condition('na.fallback', TRUE);
++            }
++            else {
++              $subquery->condition('na.langcode', $langcode);
++            }
+           }
+-        }
+ 
+-        $field = 'nid';
+-        // Now handle entities.
+-        $subquery->where("[$table_alias].[$field] = [na].[nid]");
++          $field = 'nid';
++          // Now handle entities.
++          $subquery->where("[$table_alias].[$field] = [na].[nid]");
+ 
+-        if (empty($tableinfo['join type'])) {
+-          $query->exists($subquery);
+-        }
+-        else {
+-          // If this is a join, add the node access check to the join condition.
+-          // This requires using $query->getTables() to alter the table
+-          // information.
+-          $join_cond = $query
+-            ->andConditionGroup()
+-            ->exists($subquery);
+-          $join_cond->where($tableinfo['condition'], $query->getTables()[$table_alias]['arguments']);
+-          $query->getTables()[$table_alias]['arguments'] = [];
+-          $query->getTables()[$table_alias]['condition'] = $join_cond;
++          if (empty($tableinfo['join type'])) {
++            $query->exists($subquery);
++          }
++          else {
++            // If this is a join, add the node access check to the join condition.
++            // This requires using $query->getTables() to alter the table
++            // information.
++            $join_cond = $query
++              ->andConditionGroup()
++              ->exists($subquery);
++            $join_cond->where($tableinfo['condition'], $query->getTables()[$table_alias]['arguments']);
++            $query->getTables()[$table_alias]['arguments'] = [];
++            $query->getTables()[$table_alias]['condition'] = $join_cond;
++          }
+         }
+       }
+     }
+@@ -224,7 +279,7 @@ public function alterQuery($query, array $tables, $operation, AccountInterface $
+    */
+   public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE) {
+     if ($delete) {
+-      $query = $this->database->delete('node_access')->condition('nid', $node->id());
++      $query = $this->database->delete('node_access')->condition('nid', (int) $node->id());
+       if ($realm) {
+         $query->condition('realm', [$realm, 'all'], 'IN');
+       }
+@@ -293,13 +348,25 @@ public function writeDefault() {
+    * {@inheritdoc}
+    */
+   public function count() {
+-    return $this->database->query('SELECT COUNT(*) FROM {node_access}')->fetchField();
++    if ($this->database->driver() == 'mongodb') {
++      $prefixed_table = $this->database->getPrefix() . 'node_access';
++
++      return (string) $this->database->getConnection()->selectCollection($prefixed_table)->count([], ['session' => $this->database->getMongodbSession()]);
++    }
++    else {
++      return $this->database->query('SELECT COUNT(*) FROM {node_access}')->fetchField();
++    }
+   }
+ 
+   /**
+    * {@inheritdoc}
+    */
+   public function deleteNodeRecords(array $nids) {
++    // Make sure that all $nids have an integer value.
++    foreach ($nids as &$nid) {
++      $nid = (int) $nid;
++    }
++
+     $this->database->delete('node_access')
+       ->condition('nid', $nids, 'IN')
+       ->execute();
+@@ -320,6 +387,9 @@ protected function buildGrantsQueryCondition(array $node_access_grants) {
+     $grants = $this->database->condition('OR');
+     foreach ($node_access_grants as $realm => $gids) {
+       if (!empty($gids)) {
++        foreach ($gids as &$gid) {
++          $gid = (int) $gid;
++        }
+         $and = $this->database->condition('AND');
+         $grants->condition($and
+           ->condition('gid', $gids, 'IN')
+diff --git a/core/modules/node/src/NodeViewsData.php b/core/modules/node/src/NodeViewsData.php
+index 2f3a837277506eb538ed02c87b127a55cfab2bb7..a04bae8a59ac9f59f0af440942e151ec2cf027ae 100644
+--- a/core/modules/node/src/NodeViewsData.php
++++ b/core/modules/node/src/NodeViewsData.php
+@@ -15,28 +15,37 @@ class NodeViewsData extends EntityViewsData {
+   public function getViewsData() {
+     $data = parent::getViewsData();
+ 
+-    $data['node_field_data']['table']['base']['weight'] = -10;
+-    $data['node_field_data']['table']['base']['access query tag'] = 'node_access';
+-    $data['node_field_data']['table']['wizard_id'] = 'node';
++    if ($this->connection->driver() == 'mongodb') {
++      $data_table = 'node';
++      $revision_table = 'node';
++    }
++    else {
++      $data_table = 'node_field_data';
++      $revision_table = 'node_field_revision';
++    }
++
++    $data[$data_table]['table']['base']['weight'] = -10;
++    $data[$data_table]['table']['base']['access query tag'] = 'node_access';
++    $data[$data_table]['table']['wizard_id'] = 'node';
+ 
+-    $data['node_field_data']['nid']['argument'] = [
++    $data[$data_table]['nid']['argument'] = [
+       'id' => 'node_nid',
+       'name field' => 'title',
+       'numeric' => TRUE,
+       'validate type' => 'nid',
+     ];
+ 
+-    $data['node_field_data']['title']['field']['default_formatter_settings'] = ['link_to_entity' => TRUE];
+-    $data['node_field_data']['title']['field']['link_to_node default'] = TRUE;
++    $data[$data_table]['title']['field']['default_formatter_settings'] = ['link_to_entity' => TRUE];
++    $data[$data_table]['title']['field']['link_to_node default'] = TRUE;
+ 
+-    $data['node_field_data']['type']['argument']['id'] = 'node_type';
++    $data[$data_table]['type']['argument']['id'] = 'node_type';
+ 
+-    $data['node_field_data']['status']['filter']['label'] = $this->t('Published status');
+-    $data['node_field_data']['status']['filter']['type'] = 'yes-no';
++    $data[$data_table]['status']['filter']['label'] = $this->t('Published status');
++    $data[$data_table]['status']['filter']['type'] = 'yes-no';
+     // Use status = 1 instead of status <> 0 in WHERE statement.
+-    $data['node_field_data']['status']['filter']['use_equal'] = TRUE;
++    $data[$data_table]['status']['filter']['use_equal'] = TRUE;
+ 
+-    $data['node_field_data']['status_extra'] = [
++    $data[$data_table]['status_extra'] = [
+       'title' => $this->t('Published status or admin user'),
+       'help' => $this->t('Filters out unpublished content if the current user cannot view it.'),
+       'filter' => [
+@@ -46,14 +55,14 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['promote']['help'] = $this->t('A boolean indicating whether the node is visible on the front page.');
+-    $data['node_field_data']['promote']['filter']['label'] = $this->t('Promoted to front page status');
+-    $data['node_field_data']['promote']['filter']['type'] = 'yes-no';
++    $data[$data_table]['promote']['help'] = $this->t('A boolean indicating whether the node is visible on the front page.');
++    $data[$data_table]['promote']['filter']['label'] = $this->t('Promoted to front page status');
++    $data[$data_table]['promote']['filter']['type'] = 'yes-no';
+ 
+-    $data['node_field_data']['sticky']['help'] = $this->t('A boolean indicating whether the node should sort to the top of content lists.');
+-    $data['node_field_data']['sticky']['filter']['label'] = $this->t('Sticky status');
+-    $data['node_field_data']['sticky']['filter']['type'] = 'yes-no';
+-    $data['node_field_data']['sticky']['sort']['help'] = $this->t('Whether or not the content is sticky. To list sticky content first, set this to descending.');
++    $data[$data_table]['sticky']['help'] = $this->t('A boolean indicating whether the node should sort to the top of content lists.');
++    $data[$data_table]['sticky']['filter']['label'] = $this->t('Sticky status');
++    $data[$data_table]['sticky']['filter']['type'] = 'yes-no';
++    $data[$data_table]['sticky']['sort']['help'] = $this->t('Whether or not the content is sticky. To list sticky content first, set this to descending.');
+ 
+     $data['node']['node_bulk_form'] = [
+       'title' => $this->t('Node operations bulk form'),
+@@ -67,7 +76,7 @@ public function getViewsData() {
+ 
+     // @todo Add similar support to any date field
+     // @see https://www.drupal.org/node/2337507
+-    $data['node_field_data']['created_fulldate'] = [
++    $data[$data_table]['created_fulldate'] = [
+       'title' => $this->t('Created date'),
+       'help' => $this->t('Date in the form of CCYYMMDD.'),
+       'argument' => [
+@@ -76,7 +85,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['created_year_month'] = [
++    $data[$data_table]['created_year_month'] = [
+       'title' => $this->t('Created year + month'),
+       'help' => $this->t('Date in the form of YYYYMM.'),
+       'argument' => [
+@@ -85,7 +94,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['created_year'] = [
++    $data[$data_table]['created_year'] = [
+       'title' => $this->t('Created year'),
+       'help' => $this->t('Date in the form of YYYY.'),
+       'argument' => [
+@@ -94,7 +103,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['created_month'] = [
++    $data[$data_table]['created_month'] = [
+       'title' => $this->t('Created month'),
+       'help' => $this->t('Date in the form of MM (01 - 12).'),
+       'argument' => [
+@@ -103,7 +112,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['created_day'] = [
++    $data[$data_table]['created_day'] = [
+       'title' => $this->t('Created day'),
+       'help' => $this->t('Date in the form of DD (01 - 31).'),
+       'argument' => [
+@@ -112,7 +121,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['created_week'] = [
++    $data[$data_table]['created_week'] = [
+       'title' => $this->t('Created week'),
+       'help' => $this->t('Date in the form of WW (01 - 53).'),
+       'argument' => [
+@@ -121,7 +130,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['changed_fulldate'] = [
++    $data[$data_table]['changed_fulldate'] = [
+       'title' => $this->t('Updated date'),
+       'help' => $this->t('Date in the form of CCYYMMDD.'),
+       'argument' => [
+@@ -130,7 +139,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['changed_year_month'] = [
++    $data[$data_table]['changed_year_month'] = [
+       'title' => $this->t('Updated year + month'),
+       'help' => $this->t('Date in the form of YYYYMM.'),
+       'argument' => [
+@@ -139,7 +148,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['changed_year'] = [
++    $data[$data_table]['changed_year'] = [
+       'title' => $this->t('Updated year'),
+       'help' => $this->t('Date in the form of YYYY.'),
+       'argument' => [
+@@ -148,7 +157,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['changed_month'] = [
++    $data[$data_table]['changed_month'] = [
+       'title' => $this->t('Updated month'),
+       'help' => $this->t('Date in the form of MM (01 - 12).'),
+       'argument' => [
+@@ -157,7 +166,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['changed_day'] = [
++    $data[$data_table]['changed_day'] = [
+       'title' => $this->t('Updated day'),
+       'help' => $this->t('Date in the form of DD (01 - 31).'),
+       'argument' => [
+@@ -166,7 +175,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['changed_week'] = [
++    $data[$data_table]['changed_week'] = [
+       'title' => $this->t('Updated week'),
+       'help' => $this->t('Date in the form of WW (01 - 53).'),
+       'argument' => [
+@@ -183,47 +192,65 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_data']['uid_revision']['title'] = $this->t('User has a revision');
+-    $data['node_field_data']['uid_revision']['help'] = $this->t('All nodes where a certain user has a revision');
+-    $data['node_field_data']['uid_revision']['real field'] = 'nid';
+-    $data['node_field_data']['uid_revision']['filter']['id'] = 'node_uid_revision';
+-    $data['node_field_data']['uid_revision']['argument']['id'] = 'node_uid_revision';
++    if ($this->connection->driver() == 'mongodb') {
++      // @todo Find out if this is still needed.
++      $data['node']['uid']['help'] = t('The user authoring the content. If you need more fields than the uid add the content: author relationship');
++      $data['node']['uid']['filter']['id'] = 'user_name';
++      $data['node']['uid']['relationship']['title'] = t('Content author');
++      $data['node']['uid']['relationship']['help'] = t('Relate content to the user who created it.');
++      $data['node']['uid']['relationship']['label'] = t('author');
++      $data['node']['uid']['relationship']['base'] = 'users';
++    }
+ 
+-    $data['node_field_revision']['table']['wizard_id'] = 'node_revision';
++    $data[$data_table]['uid_revision']['title'] = $this->t('User has a revision');
++    $data[$data_table]['uid_revision']['help'] = $this->t('All nodes where a certain user has a revision');
++    $data[$data_table]['uid_revision']['real field'] = 'nid';
++    $data[$data_table]['uid_revision']['filter']['id'] = 'node_uid_revision';
++    $data[$data_table]['uid_revision']['argument']['id'] = 'node_uid_revision';
++
++    if ($this->connection->driver() == 'mongodb') {
++      // @todo Find out if this is still needed.
++      $data['node']['revision_uid']['help'] = t('The user who created the revision.');
++      $data['node']['revision_uid']['relationship']['label'] = t('revision user');
++      $data['node']['revision_uid']['filter']['id'] = 'user_name';
++    }
++    else {
++      $data['node_field_revision']['table']['wizard_id'] = 'node_revision';
+ 
+-    // Advertise this table as a possible base table.
+-    $data['node_field_revision']['table']['base']['help'] = $this->t('Content revision is a history of changes to content.');
+-    $data['node_field_revision']['table']['base']['defaults']['title'] = 'title';
++      // Advertise this table as a possible base table.
++      $data['node_field_revision']['table']['base']['help'] = $this->t('Content revision is a history of changes to content.');
++      $data['node_field_revision']['table']['base']['defaults']['title'] = 'title';
+ 
+-    $data['node_field_revision']['nid']['argument'] = [
+-      'id' => 'node_nid',
+-      'numeric' => TRUE,
+-    ];
+-    // @todo the NID field needs different behavior on revision/non-revision
+-    //   tables. It would be neat if this could be encoded in the base field
+-    //   definition.
+-    $data['node_field_revision']['vid'] = [
+-      'argument' => [
+-        'id' => 'node_vid',
++      $data['node_field_revision']['nid']['argument'] = [
++        'id' => 'node_nid',
+         'numeric' => TRUE,
+-      ],
+-    ] + $data['node_field_revision']['vid'];
++      ];
++      // @todo the NID field needs different behavior on revision/non-revision
++      //   tables. It would be neat if this could be encoded in the base field
++      //   definition.
++      $data['node_field_revision']['vid'] = [
++        'argument' => [
++          'id' => 'node_vid',
++          'numeric' => TRUE,
++        ],
++      ] + $data['node_field_revision']['vid'];
+ 
+-    $data['node_field_revision']['langcode']['help'] = $this->t('The language the original content is in.');
++      $data['node_field_revision']['langcode']['help'] = $this->t('The language the original content is in.');
+ 
+-    $data['node_field_revision']['table']['wizard_id'] = 'node_field_revision';
++      $data['node_field_revision']['table']['wizard_id'] = 'node_field_revision';
+ 
+-    $data['node_field_revision']['status']['filter']['label'] = $this->t('Published');
+-    $data['node_field_revision']['status']['filter']['type'] = 'yes-no';
+-    $data['node_field_revision']['status']['filter']['use_equal'] = TRUE;
++      $data['node_field_revision']['status']['filter']['label'] = $this->t('Published');
++      $data['node_field_revision']['status']['filter']['type'] = 'yes-no';
++      $data['node_field_revision']['status']['filter']['use_equal'] = TRUE;
+ 
+-    $data['node_field_revision']['promote']['help'] = $this->t('A boolean indicating whether the node is visible on the front page.');
++      $data['node_field_revision']['promote']['help'] = $this->t('A boolean indicating whether the node is visible on the front page.');
+ 
+-    $data['node_field_revision']['sticky']['help'] = $this->t('A boolean indicating whether the node should sort to the top of content lists.');
++      $data['node_field_revision']['sticky']['help'] = $this->t('A boolean indicating whether the node should sort to the top of content lists.');
+ 
+-    $data['node_field_revision']['langcode']['help'] = $this->t('The language of the content or translation.');
++      $data['node_field_revision']['langcode']['help'] = $this->t('The language of the content or translation.');
++    }
+ 
+-    $data['node_field_revision']['link_to_revision'] = [
++    $data[$revision_table]['link_to_revision'] = [
+       'field' => [
+         'title' => $this->t('Link to revision'),
+         'help' => $this->t('Provide a simple link to the revision.'),
+@@ -232,7 +259,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_revision']['revert_revision'] = [
++    $data[$revision_table]['revert_revision'] = [
+       'field' => [
+         'title' => $this->t('Link to revert revision'),
+         'help' => $this->t('Provide a simple link to revert to the revision.'),
+@@ -241,7 +268,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['node_field_revision']['delete_revision'] = [
++    $data[$revision_table]['delete_revision'] = [
+       'field' => [
+         'title' => $this->t('Link to delete revision'),
+         'help' => $this->t('Provide a simple link to delete the content revision.'),
+@@ -256,7 +283,7 @@ public function getViewsData() {
+ 
+     // For other base tables, explain how we join.
+     $data['node_access']['table']['join'] = [
+-      'node_field_data' => [
++      $data_table => [
+         'left_field' => 'nid',
+         'field' => 'nid',
+       ],
+@@ -289,11 +316,21 @@ public function getViewsData() {
+         // Use a Views table alias to allow other modules to use this table too,
+         // if they use the search index.
+         $data['node_search_index']['table']['join'] = [
+-          'node_field_data' => [
++          $data_table => [
+             'left_field' => 'nid',
+             'field' => 'sid',
+             'table' => 'search_index',
+-            'extra' => "node_search_index.type = 'node_search' AND node_search_index.langcode = node_field_data.langcode",
++            'extra' => [
++              [
++                'field' => 'type',
++                'value' => 'node_search',
++                'operator' => '=',
++              ],
++              [
++                'field' => 'langcode',
++                'field2' => ($this->connection->driver() == 'mongodb' ? 'node_current_revision.langcode' : 'langcode'),
++              ],
++            ],
+           ],
+         ];
+ 
+@@ -305,12 +342,23 @@ public function getViewsData() {
+         ];
+ 
+         $data['node_search_dataset']['table']['join'] = [
+-          'node_field_data' => [
++          $data_table => [
+             'left_field' => 'sid',
+             'left_table' => 'node_search_index',
+             'field' => 'sid',
+             'table' => 'search_dataset',
+-            'extra' => 'node_search_index.type = node_search_dataset.type AND node_search_index.langcode = node_search_dataset.langcode',
++            'extra' => [
++              [
++                'field' => 'type',
++                'field2' => 'type',
++                'operator' => '=',
++              ],
++              [
++                'field' => 'langcode',
++                'field2' => 'langcode',
++                'operator' => '=',
++              ],
++            ],
+             'type' => 'INNER',
+           ],
+         ];
+diff --git a/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php b/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php
+index a89e94f92e3e5b03224145134dc0c9a451756ed3..0af19721efbcd331d257d3b0f7bfd3e7b975dc2f 100644
+--- a/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php
++++ b/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php
+@@ -30,7 +30,7 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
+     // modules in use on the site. As long as one access control module is there,
+     // it is supposed to handle this check.
+     if (!$this->currentUser->hasPermission('bypass node access') && !$this->moduleHandler->hasImplementations('node_grants')) {
+-      $query->condition('status', NodeInterface::PUBLISHED);
++      $query->condition('status', (bool) NodeInterface::PUBLISHED);
+     }
+     return $query;
+   }
+diff --git a/core/modules/node/src/Plugin/Search/NodeSearch.php b/core/modules/node/src/Plugin/Search/NodeSearch.php
+index c7019932bd3f8527c63a086b2739b213df898a4f..9899462b5c240fb041cfa3c7aa107007cb547663 100644
+--- a/core/modules/node/src/Plugin/Search/NodeSearch.php
++++ b/core/modules/node/src/Plugin/Search/NodeSearch.php
+@@ -265,7 +265,11 @@ protected function findResults() {
+       ->select('search_index', 'i')
+       ->extend(SearchQuery::class)
+       ->extend(PagerSelectExtender::class);
+-    $query->join('node_field_data', 'n', '[n].[nid] = [i].[sid] AND [n].[langcode] = [i].[langcode]');
++    $query->join('node_field_data', 'n',
++      $query->joinCondition()
++        ->compare('n.nid', 'i.sid')
++        ->compare('n.langcode', 'i.langcode')
++    );
+     $query->condition('n.status', 1)
+       ->addTag('node_access')
+       ->searchExpression($keys, $this->getPluginId());
+@@ -302,7 +306,7 @@ protected function findResults() {
+         }
+         $query->condition($where);
+         if (!empty($info['join'])) {
+-          $query->join($info['join']['table'], $info['join']['alias'], $info['join']['condition']);
++          $query->join($info['join']['table'], $info['join']['alias'], $query->joinCondition()->where($info['join']['condition']));
+         }
+       }
+     }
+@@ -445,7 +449,7 @@ protected function addNodeRankings(SelectExtender $query) {
+           $node_rank = $this->configuration['rankings'][$rank];
+           // If the table defined in the ranking isn't already joined, then add it.
+           if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
+-            $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
++            $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $query->joinCondition()->where($values['join']['on']));
+           }
+           $arguments = $values['arguments'] ?? [];
+           $query->addScore($values['score'], $arguments, $node_rank);
+@@ -464,9 +468,13 @@ public function updateIndex() {
+ 
+     $query = $this->databaseReplica->select('node', 'n');
+     $query->addField('n', 'nid');
+-    $query->leftJoin('search_dataset', 'sd', '[sd].[sid] = [n].[nid] AND [sd].[type] = :type', [':type' => $this->getPluginId()]);
++    $query->leftJoin('search_dataset', 'sd',
++      $query->joinCondition()
++        ->compare('sd.sid', 'n.nid')
++        ->condition('sd.type', $this->getPluginId())
++    );
+     $query->addExpression('CASE MAX([sd].[reindex]) WHEN NULL THEN 0 ELSE 1 END', 'ex');
+-    $query->addExpression('MAX([sd].[reindex])', 'ex2');
++    $query->addExpressionMax('sd.reindex', 'ex2');
+     $query->condition(
+         $query->orConditionGroup()
+           ->where('[sd].[sid] IS NULL')
+diff --git a/core/modules/node/src/Plugin/views/wizard/Node.php b/core/modules/node/src/Plugin/views/wizard/Node.php
+index b29396fcbba4b1cf91cf751b072cc56bc0c09d43..7c0dd79014b4d9fff534f31d7ded424c6da817db 100644
+--- a/core/modules/node/src/Plugin/views/wizard/Node.php
++++ b/core/modules/node/src/Plugin/views/wizard/Node.php
+@@ -2,6 +2,7 @@
+ 
+ namespace Drupal\node\Plugin\views\wizard;
+ 
++use Drupal\Core\Database\Connection;
+ use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+ use Drupal\Core\Entity\EntityFieldManagerInterface;
+ use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+@@ -65,12 +66,19 @@ class Node extends WizardPluginBase {
+    *   The entity field manager.
+    * @param \Drupal\Core\Menu\MenuParentFormSelectorInterface $parent_form_selector
+    *   The parent form selector service.
++   * @param \Drupal\Core\Database\Connection $connection
++   *   The database connection.
+    */
+-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, EntityDisplayRepositoryInterface $entity_display_repository, EntityFieldManagerInterface $entity_field_manager, MenuParentFormSelectorInterface $parent_form_selector) {
+-    parent::__construct($configuration, $plugin_id, $plugin_definition, $bundle_info_service, $parent_form_selector);
++  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, EntityDisplayRepositoryInterface $entity_display_repository, EntityFieldManagerInterface $entity_field_manager, MenuParentFormSelectorInterface $parent_form_selector, Connection $connection) {
++    parent::__construct($configuration, $plugin_id, $plugin_definition, $bundle_info_service, $parent_form_selector, $connection);
+ 
+     $this->entityDisplayRepository = $entity_display_repository;
+     $this->entityFieldManager = $entity_field_manager;
++
++    if ($connection->driver() == 'mongodb') {
++      $this->base_table = 'node';
++      $this->createdColumn = 'node-created';
++    }
+   }
+ 
+   /**
+@@ -84,7 +92,8 @@ public static function create(ContainerInterface $container, array $configuratio
+       $container->get('entity_type.bundle.info'),
+       $container->get('entity_display.repository'),
+       $container->get('entity_field.manager'),
+-      $container->get('menu.parent_form_selector')
++      $container->get('menu.parent_form_selector'),
++      $container->get('database')
+     );
+   }
+ 
+@@ -97,9 +106,16 @@ public static function create(ContainerInterface $container, array $configuratio
+    */
+   public function getAvailableSorts() {
+     // You can't execute functions in properties, so override the method
+-    return [
+-      'node_field_data-title:ASC' => $this->t('Title'),
+-    ];
++    if ($this->connection->driver() == 'mongodb') {
++      return [
++        'node-title:ASC' => $this->t('Title'),
++      ];
++    }
++    else {
++      return [
++        'node_field_data-title:ASC' => $this->t('Title'),
++      ];
++    }
+   }
+ 
+   /**
+@@ -132,7 +148,12 @@ protected function defaultDisplayOptions() {
+     // to a row style that uses fields.
+     /* Field: Content: Title */
+     $display_options['fields']['title']['id'] = 'title';
+-    $display_options['fields']['title']['table'] = 'node_field_data';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['fields']['title']['table'] = 'node';
++    }
++    else {
++      $display_options['fields']['title']['table'] = 'node_field_data';
++    }
+     $display_options['fields']['title']['field'] = 'title';
+     $display_options['fields']['title']['entity_type'] = 'node';
+     $display_options['fields']['title']['entity_field'] = 'title';
+@@ -229,7 +250,12 @@ protected function display_options_row(&$display_options, $row_plugin, $row_opti
+       case 'titles':
+         $display_options['row']['type'] = 'fields';
+         $display_options['fields']['title']['id'] = 'title';
+-        $display_options['fields']['title']['table'] = 'node_field_data';
++        if ($this->connection->driver() == 'mongodb') {
++          $display_options['fields']['title']['table'] = 'node';
++        }
++        else {
++          $display_options['fields']['title']['table'] = 'node_field_data';
++        }
+         $display_options['fields']['title']['field'] = 'title';
+         $display_options['fields']['title']['settings']['link_to_entity'] = $row_plugin === 'titles_linked';
+         $display_options['fields']['title']['plugin_id'] = 'field';
+diff --git a/core/modules/path_alias/src/AliasRepository.php b/core/modules/path_alias/src/AliasRepository.php
+index 21eb3daef0d65bc2a85a14fd9285cea81c827c29..01fd8390cecd82e48f8241bcb59e403fb0cc4433 100644
+--- a/core/modules/path_alias/src/AliasRepository.php
++++ b/core/modules/path_alias/src/AliasRepository.php
+@@ -99,7 +99,7 @@ public function lookupByAlias($alias, $langcode) {
+    */
+   public function pathHasMatchingAlias($initial_substring) {
+     $query = $this->getBaseQuery();
+-    $query->addExpression(1);
++    $query->addExpressionConstant(1);
+ 
+     return (bool) $query
+       ->condition('base_table.path', $this->connection->escapeLike($initial_substring) . '%', 'LIKE')
+@@ -116,7 +116,7 @@ public function pathHasMatchingAlias($initial_substring) {
+    */
+   protected function getBaseQuery() {
+     $query = $this->connection->select('path_alias', 'base_table');
+-    $query->condition('base_table.status', 1);
++    $query->condition('base_table.status', TRUE);
+ 
+     return $query;
+   }
+diff --git a/core/modules/path_alias/src/PathAliasStorageSchema.php b/core/modules/path_alias/src/PathAliasStorageSchema.php
+index d2bc87de1672251354dd523295ebe34f9e1d5de9..7b6ea485c8e5ff8173e461c5acb9ce13eb5ca258 100644
+--- a/core/modules/path_alias/src/PathAliasStorageSchema.php
++++ b/core/modules/path_alias/src/PathAliasStorageSchema.php
+@@ -22,10 +22,12 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
+       'path_alias__alias_langcode_id_status' => ['alias', 'langcode', 'id', 'status'],
+       'path_alias__path_langcode_id_status' => ['path', 'langcode', 'id', 'status'],
+     ];
+-    $schema[$revision_table]['indexes'] += [
+-      'path_alias_revision__alias_langcode_id_status' => ['alias', 'langcode', 'id', 'status'],
+-      'path_alias_revision__path_langcode_id_status' => ['path', 'langcode', 'id', 'status'],
+-    ];
++    if ($revision_table) {
++      $schema[$revision_table]['indexes'] += [
++        'path_alias_revision__alias_langcode_id_status' => ['alias', 'langcode', 'id', 'status'],
++        'path_alias_revision__path_langcode_id_status' => ['path', 'langcode', 'id', 'status'],
++      ];
++    }
+ 
+     // Unset the path_alias__status index as it is slower than the above
+     // indexes and MySQL 5.7 chooses to use it even though it is suboptimal.
+diff --git a/core/modules/search/src/SearchIndex.php b/core/modules/search/src/SearchIndex.php
+index 1a0bdbdd14772237b7d539d1cd0ac8cbe71aa238..2b8434e0117a6bf12192e407c8c2659dc1f0b62d 100644
+--- a/core/modules/search/src/SearchIndex.php
++++ b/core/modules/search/src/SearchIndex.php
+@@ -204,8 +204,8 @@ public function clear($type = NULL, $sid = NULL, $langcode = NULL) {
+         $query_index->condition('type', $type);
+         $query_dataset->condition('type', $type);
+         if ($sid) {
+-          $query_index->condition('sid', $sid);
+-          $query_dataset->condition('sid', $sid);
++          $query_index->condition('sid', (int) $sid);
++          $query_dataset->condition('sid', (int) $sid);
+           if ($langcode) {
+             $query_index->condition('langcode', $langcode);
+             $query_dataset->condition('langcode', $langcode);
+@@ -242,7 +242,7 @@ public function markForReindex($type = NULL, $sid = NULL, $langcode = NULL) {
+       if ($type) {
+         $query->condition('type', $type);
+         if ($sid) {
+-          $query->condition('sid', $sid);
++          $query->condition('sid', (int) $sid);
+           if ($langcode) {
+             $query->condition('langcode', $langcode);
+           }
+diff --git a/core/modules/search/src/SearchQuery.php b/core/modules/search/src/SearchQuery.php
+index 78c0f9461d84c85da55f8e1824d10ece3566b3df..68c23dfe2889317507b0883d031276afbe9bad18 100644
+--- a/core/modules/search/src/SearchQuery.php
++++ b/core/modules/search/src/SearchQuery.php
+@@ -410,7 +410,7 @@ public function prepareAndNormalize() {
+     $this->condition($or);
+ 
+     // Add keyword normalization information to the query.
+-    $this->join('search_total', 't', '[i].[word] = [t].[word]');
++    $this->join('search_total', 't', $this->joinCondition()->compare('i.word', 't.word'));
+     $this
+       ->condition('i.type', $this->type)
+       ->groupBy('i.type')
+@@ -430,7 +430,12 @@ public function prepareAndNormalize() {
+     // For complex search queries, add the LIKE conditions; if the query is
+     // simple, we do not need them for normalization.
+     if (!$this->simple) {
+-      $normalize_query->join('search_dataset', 'd', '[i].[sid] = [d].[sid] AND [i].[type] = [d].[type] AND [i].[langcode] = [d].[langcode]');
++      $normalize_query->join('search_dataset', 'd',
++        $normalize_query->joinCondition()
++          ->compare('i.sid', 'd.sid')
++          ->compare('i.type', 'd.type')
++          ->compare('i.langcode', 'd.langcode')
++      );
+       if (count($this->conditions)) {
+         $normalize_query->condition($this->conditions);
+       }
+@@ -552,7 +557,12 @@ public function execute() {
+     }
+ 
+     // Add conditions to the query.
+-    $this->join('search_dataset', 'd', '[i].[sid] = [d].[sid] AND [i].[type] = [d].[type] AND [i].[langcode] = [d].[langcode]');
++    $this->join('search_dataset', 'd',
++      $this->joinCondition()
++        ->compare('i.sid', 'd.sid')
++        ->compare('i.type', 'd.type')
++        ->compare('i.langcode', 'd.langcode')
++    );
+     if (count($this->conditions)) {
+       $this->condition($this->conditions);
+     }
+@@ -610,7 +620,11 @@ public function countQuery() {
+     $inner = clone $this->query;
+ 
+     // Add conditions to query.
+-    $inner->join('search_dataset', 'd', '[i].[sid] = [d].[sid] AND [i].[type] = [d].[type]');
++    $inner->join('search_dataset', 'd',
++      $inner->joinCondition()
++        ->compare('i.sid', 'd.sid')
++        ->compare('i.type', 'd.type')
++    );
+     if (count($this->conditions)) {
+       $inner->condition($this->conditions);
+     }
+@@ -626,7 +640,7 @@ public function countQuery() {
+     $count = $this->connection->select($inner->fields('i', ['sid']), NULL);
+ 
+     // Add the COUNT() expression.
+-    $count->addExpression('COUNT(*)');
++    $count->addExpressionCountAll();
+ 
+     return $count;
+   }
+diff --git a/core/modules/shortcut/src/ShortcutSetStorage.php b/core/modules/shortcut/src/ShortcutSetStorage.php
+index c2bccb9387c72c0d3707147453ca96d7cf58f23b..fcb26b4e8de9b9239e29ba2ae0eb5bd49023f644 100644
+--- a/core/modules/shortcut/src/ShortcutSetStorage.php
++++ b/core/modules/shortcut/src/ShortcutSetStorage.php
+@@ -91,7 +91,7 @@ public function deleteAssignedShortcutSets(ShortcutSetInterface $entity) {
+   public function assignUser(ShortcutSetInterface $shortcut_set, $account) {
+     $current_shortcut_set = $this->getDisplayedToUser($account);
+     $this->connection->merge('shortcut_set_users')
+-      ->key('uid', $account->id())
++      ->key('uid', (int) $account->id())
+       ->fields(['set_name' => $shortcut_set->id()])
+       ->execute();
+     if ($current_shortcut_set instanceof ShortcutSetInterface) {
+@@ -105,7 +105,7 @@ public function assignUser(ShortcutSetInterface $shortcut_set, $account) {
+   public function unassignUser($account) {
+     $current_shortcut_set = $this->getDisplayedToUser($account);
+     $deleted = $this->connection->delete('shortcut_set_users')
+-      ->condition('uid', $account->id())
++      ->condition('uid', (int) $account->id())
+       ->execute();
+     if ($current_shortcut_set instanceof ShortcutSetInterface) {
+       Cache::invalidateTags($current_shortcut_set->getCacheTagsToInvalidate());
+@@ -119,7 +119,7 @@ public function unassignUser($account) {
+   public function getAssignedToUser($account) {
+     $query = $this->connection->select('shortcut_set_users', 'ssu');
+     $query->fields('ssu', ['set_name']);
+-    $query->condition('ssu.uid', $account->id());
++    $query->condition('ssu.uid', (int) $account->id());
+     return $query->execute()->fetchField();
+   }
+ 
+diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php
+index bd35a7af852e8afdc2f33f9b27ad52aa75dec706..b2c9bcbdbb6612ed930fe69473f3d4fa5083f958 100644
+--- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php
++++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php
+@@ -424,8 +424,8 @@ public function getFullQualifiedTableName($table) {
+   /**
+    * {@inheritdoc}
+    */
+-  public static function createConnectionOptionsFromUrl($url, $root) {
+-    $database = parent::createConnectionOptionsFromUrl($url, $root);
++  public static function createConnectionOptionsFromUrl($url, $root, $hosts = '') {
++    $database = parent::createConnectionOptionsFromUrl($url, $root, $hosts);
+ 
+     // A SQLite database path with two leading slashes indicates a system path.
+     // Otherwise the path is relative to the Drupal root.
+diff --git a/core/modules/system/src/Plugin/migrate/source/d7/MenuTranslation.php b/core/modules/system/src/Plugin/migrate/source/d7/MenuTranslation.php
+index 9553e7ec1ce3a425b4c56638582036522174b014..863a8bb3ad22f366ea82450ba990d1786137c076 100644
+--- a/core/modules/system/src/Plugin/migrate/source/d7/MenuTranslation.php
++++ b/core/modules/system/src/Plugin/migrate/source/d7/MenuTranslation.php
+@@ -49,8 +49,8 @@ public function query() {
+       ->isNotNull('lt.lid');
+ 
+     $query->addField('m', 'language', 'm_language');
+-    $query->leftJoin('i18n_string', 'i18n', '[i18n].[objectid] = [m].[menu_name]');
+-    $query->leftJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->leftJoin('i18n_string', 'i18n', $query->joinCondition()->compare('i18n.objectid', 'm.menu_name'));
++    $query->leftJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+ 
+     return $query;
+   }
+diff --git a/core/modules/taxonomy/src/Hook/TaxonomyHooks.php b/core/modules/taxonomy/src/Hook/TaxonomyHooks.php
+index 00477f824d9a5bba36b84e40c7c774702ae86b93..596b029028c9fa4146b8af4801daf6537eeb6e8b 100644
+--- a/core/modules/taxonomy/src/Hook/TaxonomyHooks.php
++++ b/core/modules/taxonomy/src/Hook/TaxonomyHooks.php
+@@ -172,7 +172,7 @@ public function nodePredelete(EntityInterface $node) {
+   public function taxonomyTermDelete(Term $term) {
+     if (\Drupal::config('taxonomy.settings')->get('maintain_index_table')) {
+       // Clean up the {taxonomy_index} table when terms are deleted.
+-      \Drupal::database()->delete('taxonomy_index')->condition('tid', $term->id())->execute();
++      \Drupal::database()->delete('taxonomy_index')->condition('tid', (int) $term->id())->execute();
+     }
+   }
+ 
+diff --git a/core/modules/taxonomy/src/Hook/TaxonomyTokensHooks.php b/core/modules/taxonomy/src/Hook/TaxonomyTokensHooks.php
+index 07f5e6b9f9c6349b632a50e74e26da8afb95c41b..7b1fe2b8802c17d3c8d55abb3d8f5a6b43ad1f27 100644
+--- a/core/modules/taxonomy/src/Hook/TaxonomyTokensHooks.php
++++ b/core/modules/taxonomy/src/Hook/TaxonomyTokensHooks.php
+@@ -124,7 +124,7 @@ public function tokens($type, $tokens, array $data, array $options, BubbleableMe
+ 
+           case 'node-count':
+             $query = \Drupal::database()->select('taxonomy_index');
+-            $query->condition('tid', $term->id());
++            $query->condition('tid', (int) $term->id());
+             $query->addTag('term_node_count');
+             $count = $query->countQuery()->execute()->fetchField();
+             $replacements[$original] = $count;
+diff --git a/core/modules/taxonomy/src/Hook/TaxonomyViewsHooks.php b/core/modules/taxonomy/src/Hook/TaxonomyViewsHooks.php
+index bdc7b16a5ef196a0cf879463a583c9d2acd01b1a..7959e1450fcc78593435d6f4de175d96e6fc9fe3 100644
+--- a/core/modules/taxonomy/src/Hook/TaxonomyViewsHooks.php
++++ b/core/modules/taxonomy/src/Hook/TaxonomyViewsHooks.php
+@@ -15,13 +15,22 @@ class TaxonomyViewsHooks {
+    */
+   #[Hook('views_data_alter')]
+   public function viewsDataAlter(&$data): void {
+-    $data['node_field_data']['term_node_tid'] = [
++    if (\Drupal::database()->driver() == 'mongodb') {
++      $node_table = 'node';
++      $taxonomy_term_table = 'taxonomy_term_data';
++    }
++    else {
++      $node_table = 'node_field_data';
++      $taxonomy_term_table = 'taxonomy_term_field_data';
++    }
++
++    $data[$node_table]['term_node_tid'] = [
+       'title' => t('Taxonomy terms on node'),
+       'help' => t('Relate nodes to taxonomy terms, specifying which vocabulary or vocabularies to use. This relationship will cause duplicated records if there are multiple terms.'),
+       'relationship' => [
+         'id' => 'node_term_data',
+         'label' => t('term'),
+-        'base' => 'taxonomy_term_field_data',
++        'base' => $taxonomy_term_table,
+       ],
+       'field' => [
+         'title' => t('All taxonomy terms'),
+@@ -31,7 +40,7 @@ public function viewsDataAlter(&$data): void {
+         'click sortable' => FALSE,
+       ],
+     ];
+-    $data['node_field_data']['term_node_tid_depth'] = [
++    $data[$node_table]['term_node_tid_depth'] = [
+       'help' => t('Display content if it has the selected taxonomy terms, or children of the selected terms. Due to additional complexity, this has fewer options than the versions without depth.'),
+       'real field' => 'nid',
+       'argument' => [
+@@ -44,7 +53,7 @@ public function viewsDataAlter(&$data): void {
+         'id' => 'taxonomy_index_tid_depth',
+       ],
+     ];
+-    $data['node_field_data']['term_node_tid_depth_modifier'] = [
++    $data[$node_table]['term_node_tid_depth_modifier'] = [
+       'title' => t('Has taxonomy term ID depth modifier'),
+       'help' => t('Allows the "depth" for Taxonomy: Term ID (with depth) to be modified via an additional contextual filter value.'),
+       'argument' => [
+diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d6/TermLocalizedTranslation.php b/core/modules/taxonomy/src/Plugin/migrate/source/d6/TermLocalizedTranslation.php
+index 63dfc9fa2ae0ee83fa597e49f76011766c06a9c4..22d1baa2b894e29bf462104a58033fb30ef7daa0 100644
+--- a/core/modules/taxonomy/src/Plugin/migrate/source/d6/TermLocalizedTranslation.php
++++ b/core/modules/taxonomy/src/Plugin/migrate/source/d6/TermLocalizedTranslation.php
+@@ -39,13 +39,13 @@ public function query() {
+ 
+     // Add in the property, which is either name or description.
+     // Cast td.tid as char for PostgreSQL compatibility.
+-    $query->leftJoin('i18n_strings', 'i18n', 'CAST([td].[tid] AS CHAR(255)) = [i18n].[objectid]');
++    $query->leftJoin('i18n_strings', 'i18n', $query->joinCondition()->where('CAST([td].[tid] AS CHAR(255)) = [i18n].[objectid]'));
+     $query->condition('i18n.type', 'term');
+     $query->addField('i18n', 'lid');
+     $query->addField('i18n', 'property');
+ 
+     // Add in the translation for the property.
+-    $query->innerJoin('locales_target', 'lt', '[i18n].[lid] = [lt].[lid]');
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('i18n.lid', 'lt.lid'));
+     $query->addField('lt', 'language', 'lt.language');
+     $query->addField('lt', 'translation');
+     return $query;
+@@ -76,7 +76,7 @@ public function prepareRow(Row $row) {
+       ->condition('i18n.type', 'term')
+       ->condition('i18n.property', $other_property)
+       ->condition('i18n.objectid', $tid);
+-    $query->leftJoin('locales_target', 'lt', '[i18n].[lid] = [lt].[lid]');
++    $query->leftJoin('locales_target', 'lt', $query->joinCondition()->compare('i18n.lid', 'lt.lid'));
+     $query->condition('lt.language', $language);
+     $query->addField('lt', 'translation');
+     $results = $query->execute()->fetchAssoc();
+diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyPerType.php b/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyPerType.php
+index 667d9f3cbab512c92edd675a0eeb81b38a943dc2..4176c4a3fd1c9bd543313021c0e44598d70c9046 100644
+--- a/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyPerType.php
++++ b/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyPerType.php
+@@ -26,7 +26,7 @@ class VocabularyPerType extends Vocabulary {
+    */
+   public function query() {
+     $query = parent::query();
+-    $query->join('vocabulary_node_types', 'nt', '[v].[vid] = [nt].[vid]');
++    $query->join('vocabulary_node_types', 'nt', $query->joinCondition()->compare('v.vid', 'nt.vid'));
+     $query->fields('nt', ['type']);
+     return $query;
+   }
+diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyTranslation.php b/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyTranslation.php
+index daf0731fe8a56f3839fcd5698c84d7da9020f826..c5580220f0f469074758737ac48311e3e199e033 100644
+--- a/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyTranslation.php
++++ b/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyTranslation.php
+@@ -36,8 +36,8 @@ public function query() {
+     // and objectindex. The objectid column is a text field. Therefore, for the
+     // join to work in PostgreSQL, use the objectindex field as this is numeric
+     // like the vid field.
+-    $query->join('i18n_strings', 'i18n', '[v].[vid] = [i18n].[objectindex]');
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->join('i18n_strings', 'i18n', $query->joinCondition()->compare('v.vid', 'i18n.objectindex'));
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+ 
+     return $query;
+   }
+diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermEntityTranslation.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermEntityTranslation.php
+index 88cb561b8537249c9cd8a0e8cbd19674f7ffb294..96803dc1cba91161e750c04e01c54b5a63b3f753 100644
+--- a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermEntityTranslation.php
++++ b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermEntityTranslation.php
+@@ -62,8 +62,8 @@ public function query() {
+       ->condition('et.entity_type', 'taxonomy_term')
+       ->condition('et.source', '', '<>');
+ 
+-    $query->innerJoin('taxonomy_term_data', 'td', '[td].[tid] = [et].[entity_id]');
+-    $query->innerJoin('taxonomy_vocabulary', 'tv', '[td].[vid] = [tv].[vid]');
++    $query->innerJoin('taxonomy_term_data', 'td', $query->joinCondition()->compare('td.tid', 'et.entity_id'));
++    $query->innerJoin('taxonomy_vocabulary', 'tv', $query->joinCondition()->compare('td.vid', 'tv.vid'));
+ 
+     if (isset($this->configuration['bundle'])) {
+       $query->condition('tv.machine_name', (array) $this->configuration['bundle'], 'IN');
+diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php
+index 5ca0dfe7d503c3a6ec90d09426f3361ddcdb194b..cb7781aa9a2ec4f7abacb9095a419c317e32b689 100644
+--- a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php
++++ b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php
+@@ -42,13 +42,13 @@ public function query() {
+ 
+     // Add in the property, which is either name or description.
+     // Cast td.tid as char for PostgreSQL compatibility.
+-    $query->leftJoin('i18n_string', 'i18n', 'CAST([td].[tid] AS CHAR(255)) = [i18n].[objectid]');
++    $query->leftJoin('i18n_string', 'i18n', $query->joinCondition()->where('CAST([td].[tid] AS CHAR(255)) = [i18n].[objectid]'));
+     $query->condition('i18n.type', 'term');
+     $query->addField('i18n', 'lid');
+     $query->addField('i18n', 'property');
+ 
+     // Add in the translation for the property.
+-    $query->innerJoin('locales_target', 'lt', '[i18n].[lid] = [lt].[lid]');
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('i18n.lid', 'lt.lid'));
+     $query->addField('lt', 'language', 'lt.language');
+     $query->addField('lt', 'translation');
+     return $query;
+diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php
+index edb74619b4e67fe5636a696f9c465599dd1ce853..f9ae9568c28992e7f409337d5b86c8a7562d4396 100644
+--- a/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php
++++ b/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php
+@@ -55,7 +55,7 @@ public function query() {
+       ->fields('td')
+       ->distinct()
+       ->orderBy('tid');
+-    $query->leftJoin('taxonomy_vocabulary', 'tv', '[td].[vid] = [tv].[vid]');
++    $query->leftJoin('taxonomy_vocabulary', 'tv', $query->joinCondition()->compare('td.vid', 'tv.vid'));
+     $query->addField('tv', 'machine_name');
+ 
+     if ($this->getDatabase()
+diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/VocabularyTranslation.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/VocabularyTranslation.php
+index f189f2d41a0b59443234fd46abf575afbb59f3d8..8ff9720eee666954f27d23de6d69d61595de4e8b 100644
+--- a/core/modules/taxonomy/src/Plugin/migrate/source/d7/VocabularyTranslation.php
++++ b/core/modules/taxonomy/src/Plugin/migrate/source/d7/VocabularyTranslation.php
+@@ -24,8 +24,8 @@ class VocabularyTranslation extends Vocabulary {
+    */
+   public function query() {
+     $query = parent::query();
+-    $query->leftJoin('i18n_string', 'i18n', 'CAST ([v].[vid] AS CHAR(222)) = [i18n].[objectid]');
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->leftJoin('i18n_string', 'i18n', $query->joinCondition()->where('CAST ([v].[vid] AS CHAR(222)) = [i18n].[objectid]'));
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+     $query
+       ->condition('type', 'vocabulary')
+       ->fields('lt')
+diff --git a/core/modules/taxonomy/src/Plugin/views/field/TaxonomyIndexTid.php b/core/modules/taxonomy/src/Plugin/views/field/TaxonomyIndexTid.php
+index 5dd75072810cfec1aaa4af0dfb32c4240239a5ff..3be6d54e71b2a4f4450fc4514b6c08d1f0152eb5 100644
+--- a/core/modules/taxonomy/src/Plugin/views/field/TaxonomyIndexTid.php
++++ b/core/modules/taxonomy/src/Plugin/views/field/TaxonomyIndexTid.php
+@@ -62,10 +62,22 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$
+ 
+     // @todo Wouldn't it be possible to use $this->base_table and no if here?
+     if ($view->storage->get('base_table') == 'node_field_revision') {
+-      $this->additional_fields['nid'] = ['table' => 'node_field_revision', 'field' => 'nid'];
++      if ($this->view->getDatabaseDriver() == 'mongodb') {
++        $table = 'node';
++      }
++      else {
++        $table = 'node_field_revision';
++      }
++      $this->additional_fields['nid'] = ['table' => $table, 'field' => 'nid'];
+     }
+     else {
+-      $this->additional_fields['nid'] = ['table' => 'node_field_data', 'field' => 'nid'];
++      if ($this->view->getDatabaseDriver() == 'mongodb') {
++        $table = 'node';
++      }
++      else {
++        $table = 'node_field_data';
++      }
++      $this->additional_fields['nid'] = ['table' => $table, 'field' => 'nid'];
+     }
+   }
+ 
+diff --git a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
+index 4cad71a81d16460675e3516009d168b4378c7fb5..fb29854fd20b2efdb10cc6df949dd5ac6e28f670 100644
+--- a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
++++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
+@@ -401,6 +401,71 @@ public function adminSummary() {
+     return parent::adminSummary();
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function query() {
++    if ($this->view->getDatabaseDriver() == 'mongodb') {
++      if ($this->table == $this->view->storage->get('base_table')) {
++        $this->mongodbField = $this->realField;
++      }
++      elseif (!empty($this->relationship)) {
++        $this->mongodbField = "$this->relationship.$this->realField";
++      }
++      else {
++        // Throw an exception.
++        $this->mongodbField = $this->realField;
++      }
++
++      $info = $this->operators();
++      if (!empty($info[$this->operator]['method'])) {
++        $this->{$info[$this->operator]['method']}();
++      }
++    }
++    else {
++      parent::query();
++    }
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  protected function opSimple() {
++    if ($this->view->getDatabaseDriver() == 'mongodb') {
++      if (empty($this->value)) {
++        return;
++      }
++      $this->ensureMyTable();
++
++      // We use array_values() because the checkboxes keep keys and that can cause
++      // array addition problems.
++      $this->query->addCondition($this->options['group'], $this->mongodbField, array_values($this->value), $this->operator);
++    }
++    else {
++      parent::opSimple();
++    }
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  protected function opEmpty() {
++    if ($this->view->getDatabaseDriver() == 'mongodb') {
++      $this->ensureMyTable();
++      if ($this->operator == 'empty') {
++        $operator = "IS NULL";
++      }
++      else {
++        $operator = "IS NOT NULL";
++      }
++
++      $this->query->addCondition($this->options['group'], $this->mongodbField, NULL, $operator);
++    }
++    else {
++      parent::opSimple();
++    }
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+diff --git a/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php b/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
+index edb8619e4ee8a2c197498cb27b67f592263de082..182d14c363f57e0e9a8b455b216be4ae8ddb42ee 100644
+--- a/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
++++ b/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
+@@ -111,7 +111,7 @@ public function query() {
+       $def['adjusted'] = TRUE;
+ 
+       $query = Database::getConnection()->select('taxonomy_term_field_data', 'td');
+-      $query->addJoin($def['type'], 'taxonomy_index', 'tn', '[tn].[tid] = [td].[tid]');
++      $query->addJoin($def['type'], 'taxonomy_index', 'tn', $query->joinCondition()->compare('tn.tid', 'td.tid'));
+       $query->condition('td.vid', array_filter($this->options['vids']), 'IN');
+       if (empty($this->query->options['disable_sql_rewrite'])) {
+         $query->addTag('taxonomy_term_access');
+diff --git a/core/modules/taxonomy/src/Plugin/views/wizard/TaxonomyTerm.php b/core/modules/taxonomy/src/Plugin/views/wizard/TaxonomyTerm.php
+index 751f365c493e1acf153a4e3de265339356fd75ee..bdb330aa1f6e23cf11316025bc46245458f8f83e 100644
+--- a/core/modules/taxonomy/src/Plugin/views/wizard/TaxonomyTerm.php
++++ b/core/modules/taxonomy/src/Plugin/views/wizard/TaxonomyTerm.php
+@@ -31,7 +31,12 @@ protected function defaultDisplayOptions() {
+ 
+     /* Field: Taxonomy: Term */
+     $display_options['fields']['name']['id'] = 'name';
+-    $display_options['fields']['name']['table'] = 'taxonomy_term_field_data';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['fields']['name']['table'] = 'taxonomy_term_data';
++    }
++    else {
++      $display_options['fields']['name']['table'] = 'taxonomy_term_field_data';
++    }
+     $display_options['fields']['name']['field'] = 'name';
+     $display_options['fields']['name']['entity_type'] = 'taxonomy_term';
+     $display_options['fields']['name']['entity_field'] = 'name';
+diff --git a/core/modules/taxonomy/src/TaxonomyIndexDepthQueryTrait.php b/core/modules/taxonomy/src/TaxonomyIndexDepthQueryTrait.php
+index f1f47ff96c4e3ba926ff61916d6df86bf3bc00e9..04c946ef58f17c1bac1d96ad52683178544bec4e 100644
+--- a/core/modules/taxonomy/src/TaxonomyIndexDepthQueryTrait.php
++++ b/core/modules/taxonomy/src/TaxonomyIndexDepthQueryTrait.php
+@@ -66,11 +66,11 @@ protected function addSubQueryJoin($tids): void {
+         $union_query->addField('tn', 'nid');
+         $left_join = "[tn].[tid]";
+         if ($this->options['depth'] > 0) {
+-          $union_query->join('taxonomy_term__parent', "th", "$left_join = [th].[entity_id]");
++          $union_query->join('taxonomy_term__parent', "th", $union_query->joinCondition()->compare($left_join, 'th.entity_id'));
+           $left_join = "[th].[$left_field]";
+         }
+         foreach (range(1, $count) as $inner_count) {
+-          $union_query->join('taxonomy_term__parent', "th$inner_count", "$left_join = [th$inner_count].[$right_field]");
++          $union_query->join('taxonomy_term__parent', "th$inner_count", $union_query->joinCondition()->compare($left_join, "th$inner_count.$right_field"));
+           $left_join = "[th$inner_count].[$left_field]";
+         }
+         $union_query->condition("th$inner_count.entity_id", $tids, $operator);
+diff --git a/core/modules/taxonomy/src/TermStorage.php b/core/modules/taxonomy/src/TermStorage.php
+index 5248840121c4ae80e50b36592d31c190d82b7a42..afc70795fd8cb6bbe0dc4288b7bc2b541d7382ff 100644
+--- a/core/modules/taxonomy/src/TermStorage.php
++++ b/core/modules/taxonomy/src/TermStorage.php
+@@ -198,7 +198,7 @@ public function loadChildren($tid, $vid = NULL) {
+   public function getChildren(TermInterface $term) {
+     $query = \Drupal::entityQuery('taxonomy_term')
+       ->accessCheck(TRUE)
+-      ->condition('parent', $term->id());
++      ->condition('parent', (int) $term->id());
+     return static::loadMultiple($query->execute());
+   }
+ 
+@@ -214,21 +214,61 @@ public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities =
+         $this->treeChildren[$vid] = [];
+         $this->treeParents[$vid] = [];
+         $this->treeTerms[$vid] = [];
+-        $query = $this->database->select($this->getDataTable(), 't');
+-        $query->join('taxonomy_term__parent', 'p', '[t].[tid] = [p].[entity_id]');
+-        $query->addExpression('[parent_target_id]', 'parent');
+-        $result = $query
+-          ->addTag('taxonomy_term_access')
+-          ->fields('t')
+-          ->condition('t.vid', $vid)
+-          ->condition('t.default_langcode', 1)
+-          ->orderBy('t.weight')
+-          ->orderBy('t.name')
+-          ->execute();
+-        foreach ($result as $term) {
+-          $this->treeChildren[$vid][$term->parent][] = $term->tid;
+-          $this->treeParents[$vid][$term->tid][] = $term->parent;
+-          $this->treeTerms[$vid][$term->tid] = $term;
++
++        if ($this->database->driver() == 'mongodb') {
++          $query = $this->database->select($this->getBaseTable(), 't')
++            ->fields('t', ['tid', 'taxonomy_term_current_revision'])
++            ->addTag('taxonomy_term_access')
++            ->condition('taxonomy_term_current_revision.vid', $vid)
++            ->condition('taxonomy_term_current_revision.default_langcode', TRUE)
++            ->orderBy('taxonomy_term_current_revision.weight')
++            ->orderBy('taxonomy_term_current_revision.name');
++
++          $result = $query->execute()->fetchAll();
++          foreach ($result as $row) {
++            foreach ($row->taxonomy_term_current_revision as $current_revision) {
++              $term = new \stdClass();
++              $term->name = $current_revision['name'] ?? '';
++              $term->depth = 0;
++              $term->tid = $current_revision['tid'];
++              $term->vid = $current_revision['vid'];
++              $term->weight = $current_revision['weight'];
++
++              if (is_array($current_revision['taxonomy_term_current_revision__parent'])) {
++                foreach ($current_revision['taxonomy_term_current_revision__parent'] as $current_revision_parent) {
++                  $term->parent = NULL;
++                  if (isset($current_revision_parent['parent_target_id'])) {
++                    $term->parent = $current_revision_parent['parent_target_id'];
++                  }
++                  if (!is_null($term->parent)) {
++                    $this->treeChildren[$vid][$term->parent][] = $term->tid;
++                    $this->treeParents[$vid][$term->tid][] = $term->parent;
++                    $this->treeTerms[$vid][$term->tid] = $term;
++                  }
++                }
++              }
++            }
++            unset($term->taxonomy_term_current_revision);
++          }
++        }
++        else {
++          $query = $this->database->select($this->getDataTable(), 't');
++          $query->join('taxonomy_term__parent', 'p', $query->joinCondition()
++            ->compare('t.tid', 'p.entity_id'));
++          $query->addExpressionField('parent_target_id', 'parent');
++          $result = $query
++            ->addTag('taxonomy_term_access')
++            ->fields('t')
++            ->condition('t.vid', $vid)
++            ->condition('t.default_langcode', 1)
++            ->orderBy('t.weight')
++            ->orderBy('t.name')
++            ->execute();
++          foreach ($result as $term) {
++            $this->treeChildren[$vid][$term->parent][] = $term->tid;
++            $this->treeParents[$vid][$term->tid][] = $term->parent;
++            $this->treeTerms[$vid][$term->tid] = $term;
++          }
+         }
+       }
+ 
+@@ -306,48 +346,118 @@ public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities =
+    * {@inheritdoc}
+    */
+   public function nodeCount($vid) {
+-    $query = $this->database->select('taxonomy_index', 'ti');
+-    $query->addExpression('COUNT(DISTINCT [ti].[nid])');
+-    $query->leftJoin($this->getBaseTable(), 'td', '[ti].[tid] = [td].[tid]');
+-    $query->condition('td.vid', $vid);
+-    $query->addTag('vocabulary_node_count');
+-    return $query->execute()->fetchField();
++    if ($this->database->driver() == 'mongodb') {
++      // @todo There is too little testing for this. Why is there a join in this
++      // query.
++      // @see \Drupal\Tests\taxonomy\Functional\TokenReplaceTest.
++      $query = $this->database->select('taxonomy_index', 'ti');
++      $query->addJoin('LEFT', 'taxonomy_term_data', 'td', $query->joinCondition()->compare('ti.tid', 'td.tid')->condition('taxonomy_term_current_revision.vid', $vid));
++      $query->addTag('vocabulary_node_count');
++      $results = $query->execute()->fetchAll();
++      $nids = [];
++      foreach ($results as $result) {
++        if (isset($result->nid) && !in_array($result->nid, $nids)) {
++          $nids[] = $result->nid;
++        }
++      }
++      return count($nids);
++    }
++    else {
++      $query = $this->database->select('taxonomy_index', 'ti');
++      $query->addExpressionCountDistinct('ti.nid');
++      $query->leftJoin($this->getBaseTable(), 'td', $query->joinCondition()->compare('ti.tid', 'td.tid'));
++      $query->condition('td.vid', $vid);
++      $query->addTag('vocabulary_node_count');
++      return $query->execute()->fetchField();
++    }
+   }
+ 
+   /**
+    * {@inheritdoc}
+    */
+   public function resetWeights($vid) {
+-    $this->database->update($this->getDataTable())
+-      ->fields(['weight' => 0])
+-      ->condition('vid', $vid)
+-      ->execute();
++    if ($this->database->driver() == 'mongodb') {
++      $prefixed_table = $this->database->getPrefix() . 'taxonomy_term_data';
++      $this->database->getConnection()->selectCollection($prefixed_table)->updateMany(
++        [
++          'vid' => $vid,
++        ],
++        [
++          '$set' => [
++            'weight' => 0,
++            "taxonomy_term_current_revision.$[translation].weight" => 0,
++          ],
++        ],
++        [
++          'arrayFilters' => [
++            ["translation.vid" => $vid],
++          ],
++          'session' => $this->database->getMongodbSession(),
++        ],
++      );
++    }
++    else {
++      $this->database->update($this->getDataTable())
++        ->fields(['weight' => 0])
++        ->condition('vid', $vid)
++        ->execute();
++    }
+   }
+ 
+   /**
+    * {@inheritdoc}
+    */
+   public function getNodeTerms(array $nids, array $vids = [], $langcode = NULL) {
+-    $query = $this->database->select($this->getDataTable(), 'td');
+-    $query->innerJoin('taxonomy_index', 'tn', '[td].[tid] = [tn].[tid]');
+-    $query->fields('td', ['tid']);
+-    $query->addField('tn', 'nid', 'node_nid');
+-    $query->orderby('td.weight');
+-    $query->orderby('td.name');
+-    $query->condition('tn.nid', $nids, 'IN');
+-    $query->addTag('taxonomy_term_access');
+-    if (!empty($vids)) {
+-      $query->condition('td.vid', $vids, 'IN');
+-    }
+-    if (!empty($langcode)) {
+-      $query->condition('td.langcode', $langcode);
++    if ($this->database->driver() == 'mongodb') {
++      $query = $this->database->select('taxonomy_term_data', 'td');
++      foreach ($nids as &$nid) {
++        $nid = (int) $nid;
++      }
++      $query->addJoin('INNER', 'taxonomy_index', 'tn', $query->joinCondition()->compare('tn.tid', 'td.tid'));
++      $query->fields('td', ['tid']);
++      $query->addField('tn', 'nid', 'node_nid');
++      $query->condition('tn.nid', $nids, 'IN');
++      $query->orderby('taxonomy_term_current_revision.weight');
++      $query->orderby('taxonomy_term_current_revision.name');
++      $query->addTag('taxonomy_term_access');
++      if (!empty($vids)) {
++        $query->condition('taxonomy_term_current_revision.vid', $vids, 'IN');
++      }
++      if (!empty($langcode)) {
++        $query->condition('taxonomy_term_current_revision.langcode', $langcode);
++      }
++
++      $results = [];
++      $all_tids = [];
++      foreach ($query->execute() as $term_record) {
++        if (isset($term_record->tid) && isset($term_record->node_nid)) {
++          $results[$term_record->node_nid][] = $term_record->tid;
++          $all_tids[] = $term_record->tid;
++        }
++      }
+     }
++    else {
++      $query = $this->database->select($this->getDataTable(), 'td');
++      $query->innerJoin('taxonomy_index', 'tn', $query->joinCondition()->compare('td.tid', 'tn.tid'));
++      $query->fields('td', ['tid']);
++      $query->addField('tn', 'nid', 'node_nid');
++      $query->orderby('td.weight');
++      $query->orderby('td.name');
++      $query->condition('tn.nid', $nids, 'IN');
++      $query->addTag('taxonomy_term_access');
++      if (!empty($vids)) {
++        $query->condition('td.vid', $vids, 'IN');
++      }
++      if (!empty($langcode)) {
++        $query->condition('td.langcode', $langcode);
++      }
+ 
+-    $results = [];
+-    $all_tids = [];
+-    foreach ($query->execute() as $term_record) {
+-      $results[$term_record->node_nid][] = $term_record->tid;
+-      $all_tids[] = $term_record->tid;
++      $results = [];
++      $all_tids = [];
++      foreach ($query->execute() as $term_record) {
++        $results[$term_record->node_nid][] = $term_record->tid;
++        $all_tids[] = $term_record->tid;
++      }
+     }
+ 
+     $all_terms = $this->loadMultiple($all_tids);
+@@ -371,25 +481,59 @@ public function getTermIdsWithPendingRevisions() {
+     $langcode_field = $table_mapping->getColumnNames($this->entityType->getKey('langcode'))['value'];
+     $revision_default_field = $table_mapping->getColumnNames($this->entityType->getRevisionMetadataKey('revision_default'))['value'];
+ 
+-    $query = $this->database->select($this->getRevisionDataTable(), 'tfr');
+-    $query->fields('tfr', [$id_field]);
+-    $query->addExpression("MAX([tfr].[$revision_field])", $revision_field);
+-
+-    $query->join($this->getRevisionTable(), 'tr', "[tfr].[$revision_field] = [tr].[$revision_field] AND [tr].[$revision_default_field] = 0");
+-
+-    $inner_select = $this->database->select($this->getRevisionDataTable(), 't');
+-    $inner_select->condition("t.$rta_field", '1');
+-    $inner_select->fields('t', [$id_field, $langcode_field]);
+-    $inner_select->addExpression("MAX([t].[$revision_field])", $revision_field);
+-    $inner_select
+-      ->groupBy("t.$id_field")
+-      ->groupBy("t.$langcode_field");
+-
+-    $query->join($inner_select, 'mr', "[tfr].[$revision_field] = [mr].[$revision_field] AND [tfr].[$langcode_field] = [mr].[$langcode_field]");
+-
+-    $query->groupBy("tfr.$id_field");
++    if ($this->database->driver() == 'mongodb') {
++      $latest_revision_table = $this->getJsonStorageLatestRevisionTable();
++
++      $results = $this->database->select($this->getBaseTable(), 't')
++        ->fields('t', [$id_field, $latest_revision_table])
++        ->execute()
++        ->fetchAll();
++
++      $term_ids_with_pending_revisions = [];
++      foreach ($results as $result) {
++        $latest_revision = $result->{$latest_revision_table};
++        $revision_id = NULL;
++        foreach ($latest_revision as $latest_revision_language) {
++          if (($latest_revision_language[$rta_field] === TRUE) && ($latest_revision_language[$revision_default_field] === FALSE)) {
++            $revision_id = $latest_revision_language[$revision_field];
++          }
++        }
++        if (!is_null($revision_id)) {
++          $term_ids_with_pending_revisions[$result->{$id_field}] = $revision_id;
++        }
++      }
+ 
+-    return $query->execute()->fetchAllKeyed(1, 0);
++      return $term_ids_with_pending_revisions;
++    }
++    else {
++      $query = $this->database->select($this->getRevisionDataTable(), 'tfr');
++      $query->fields('tfr', [$id_field]);
++      $query->addExpressionMax("tfr.$revision_field", $revision_field);
++
++      $query->join($this->getRevisionTable(), 'tr',
++        $query->joinCondition()
++          ->compare("tfr.$revision_field", "tr.$revision_field")
++          ->condition("tr.$revision_default_field", 0)
++      );
++
++      $inner_select = $this->database->select($this->getRevisionDataTable(), 't');
++      $inner_select->condition("t.$rta_field", '1');
++      $inner_select->fields('t', [$id_field, $langcode_field]);
++      $inner_select->addExpressionMax("t.$revision_field", $revision_field);
++      $inner_select
++        ->groupBy("t.$id_field")
++        ->groupBy("t.$langcode_field");
++
++      $query->join($inner_select, 'mr',
++        $query->joinCondition()
++          ->compare("tfr.$revision_field", "mr.$revision_field")
++          ->compare("tfr.$langcode_field", "mr.$langcode_field")
++      );
++
++      $query->groupBy("tfr.$id_field");
++
++      return $query->execute()->fetchAllKeyed(1, 0);
++    }
+   }
+ 
+   /**
+@@ -407,12 +551,53 @@ public function getVocabularyHierarchyType($vid) {
+     $target_id_column = $table_mapping->getFieldColumnName($parent_field_storage, 'target_id');
+     $delta_column = $table_mapping->getFieldColumnName($parent_field_storage, TableMappingInterface::DELTA);
+ 
+-    $query = $this->database->select($table_mapping->getFieldTableName('parent'), 'p');
+-    $query->addExpression("MAX([$target_id_column])", 'max_parent_id');
+-    $query->addExpression("MAX([$delta_column])", 'max_delta');
+-    $query->condition('bundle', $vid);
++    if ($this->database->driver() == 'mongodb') {
++      $all_revisions_table = $table_mapping->getJsonStorageAllRevisionsTable(0);
++      $parent_table = $table_mapping->getJsonStorageDedicatedTableName($parent_field_storage, $all_revisions_table);
++
++      $rows = $this->database->select($this->getBaseTable())
++        ->fields($this->getBaseTable(), [$all_revisions_table])
++        ->condition("$all_revisions_table.vid", $vid)
++        ->execute()
++        ->fetchAll();
++
++      $max_parent_id = 0;
++      $max_delta = 0;
++      foreach ($rows as $row) {
++        if (isset($row->{$all_revisions_table})) {
++          foreach ($row->{$all_revisions_table} as $taxonomy_term_revision) {
++            if (isset($taxonomy_term_revision[$parent_table])) {
++              foreach ($taxonomy_term_revision[$parent_table] as $parent_table_row) {
++                $parent_id = (int) $parent_table_row['parent_target_id'];
++                if ($parent_id > $max_parent_id) {
++                  $max_parent_id = (int) $parent_id;
++                }
++                $delta = (int) $parent_table_row['delta'];
++                if ($delta > $max_delta) {
++                  $max_delta = (int) $delta;
++                }
++              }
++            }
++          }
++        }
++      }
++
++      // Create the result as it is created for a relational database.
++      $result = [
++        0 => (object) [
++          'max_parent_id' => $max_parent_id,
++          'max_delta' => $max_delta,
++        ],
++      ];
++    }
++    else {
++      $query = $this->database->select($table_mapping->getFieldTableName('parent'), 'p');
++      $query->addExpressionMax("$target_id_column", 'max_parent_id');
++      $query->addExpressionMax("$delta_column", 'max_delta');
++      $query->condition('bundle', $vid);
+ 
+-    $result = $query->execute()->fetchAll();
++      $result = $query->execute()->fetchAll();
++    }
+ 
+     // If all the terms have the same parent, the parent can only be root (0).
+     if ((int) $result[0]->max_parent_id === 0) {
+diff --git a/core/modules/taxonomy/src/TermStorageSchema.php b/core/modules/taxonomy/src/TermStorageSchema.php
+index d9e1bbf7aa85807ddae4fdfe385b201fdb00f076..3f20d5c2768c9b2bfd60af22f4e42903895e160c 100644
+--- a/core/modules/taxonomy/src/TermStorageSchema.php
++++ b/core/modules/taxonomy/src/TermStorageSchema.php
+@@ -17,13 +17,6 @@ class TermStorageSchema extends SqlContentEntityStorageSchema {
+   protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
+     $schema = parent::getEntitySchema($entity_type, $reset);
+ 
+-    if ($data_table = $this->storage->getDataTable()) {
+-      $schema[$data_table]['indexes'] += [
+-        'taxonomy_term__tree' => ['vid', 'weight', 'name'],
+-        'taxonomy_term__vid_name' => ['vid', 'name'],
+-      ];
+-    }
+-
+     $schema['taxonomy_index'] = [
+       'description' => 'Maintains denormalized information about node/term relationships.',
+       'fields' => [
+@@ -77,6 +70,21 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
+       ],
+     ];
+ 
++    if ($this->database->driver() == 'mongodb') {
++      // Boolean fields in MongoDB are stored as a boolean value.
++      $schema['taxonomy_index']['fields']['status']['type'] = 'bool';
++      $schema['taxonomy_index']['fields']['sticky']['type'] = 'bool';
++
++      // Date fields in MongoDB are stored as a date value.
++      $schema['taxonomy_index']['fields']['created']['type'] = 'date';
++    }
++    elseif ($data_table = $this->storage->getDataTable()) {
++      $schema[$data_table]['indexes'] += [
++        'taxonomy_term__tree' => ['vid', 'weight', 'name'],
++        'taxonomy_term__vid_name' => ['vid', 'name'],
++      ];
++    }
++
+     return $schema;
+   }
+ 
+@@ -120,14 +128,16 @@ protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $stor
+     if ($storage_definition->getName() === 'parent') {
+       /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+       $table_mapping = $this->storage->getTableMapping();
+-      $dedicated_table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
+ 
+-      unset($dedicated_table_schema[$dedicated_table_name]['indexes']['bundle']);
+-      $dedicated_table_schema[$dedicated_table_name]['indexes']['bundle_delta_target_id'] = [
+-        'bundle',
+-        'delta',
+-        $table_mapping->getFieldColumnName($storage_definition, 'target_id'),
+-      ];
++      if ($this->database->driver() != 'mongodb') {
++        $dedicated_table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
++        unset($dedicated_table_schema[$dedicated_table_name]['indexes']['bundle']);
++        $dedicated_table_schema[$dedicated_table_name]['indexes']['bundle_delta_target_id'] = [
++          'bundle',
++          'delta',
++          $table_mapping->getFieldColumnName($storage_definition, 'target_id'),
++        ];
++      }
+     }
+ 
+     return $dedicated_table_schema;
+diff --git a/core/modules/taxonomy/src/TermViewsData.php b/core/modules/taxonomy/src/TermViewsData.php
+index b997aecc3b3e5661a2a4f445001bf702bef0b989..38f94eb7577bf9ec6a9c8bcd25eb06a3bae7220c 100644
+--- a/core/modules/taxonomy/src/TermViewsData.php
++++ b/core/modules/taxonomy/src/TermViewsData.php
+@@ -15,11 +15,22 @@ class TermViewsData extends EntityViewsData {
+   public function getViewsData() {
+     $data = parent::getViewsData();
+ 
+-    $data['taxonomy_term_field_data']['table']['base']['help'] = $this->t('Taxonomy terms are attached to nodes.');
+-    $data['taxonomy_term_field_data']['table']['base']['access query tag'] = 'taxonomy_term_access';
+-    $data['taxonomy_term_field_data']['table']['wizard_id'] = 'taxonomy_term';
+-
+-    $data['taxonomy_term_field_data']['table']['join'] = [
++    if ($this->connection->driver() == 'mongodb') {
++      $data_table = 'taxonomy_term_data';
++      $parent_table = 'taxonomy_term_data';
++      $node_table = 'node';
++    }
++    else {
++      $data_table = 'taxonomy_term_field_data';
++      $parent_table = 'taxonomy_term__parent';
++      $node_table = 'node_field_data';
++    }
++
++    $data[$data_table]['table']['base']['help'] = $this->t('Taxonomy terms are attached to nodes.');
++    $data[$data_table]['table']['base']['access query tag'] = 'taxonomy_term_access';
++    $data[$data_table]['table']['wizard_id'] = 'taxonomy_term';
++
++    $data[$data_table]['table']['join'] = [
+       // This is provided for the many_to_one argument.
+       'taxonomy_index' => [
+         'field' => 'tid',
+@@ -27,19 +38,19 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['taxonomy_term_field_data']['tid']['help'] = $this->t('The tid of a taxonomy term.');
++    $data[$data_table]['tid']['help'] = $this->t('The tid of a taxonomy term.');
+ 
+-    $data['taxonomy_term_field_data']['tid']['argument']['id'] = 'taxonomy';
+-    $data['taxonomy_term_field_data']['tid']['argument']['name field'] = 'name';
+-    $data['taxonomy_term_field_data']['tid']['argument']['zero is null'] = TRUE;
++    $data[$data_table]['tid']['argument']['id'] = 'taxonomy';
++    $data[$data_table]['tid']['argument']['name field'] = 'name';
++    $data[$data_table]['tid']['argument']['zero is null'] = TRUE;
+ 
+-    $data['taxonomy_term_field_data']['tid']['filter']['id'] = 'taxonomy_index_tid';
+-    $data['taxonomy_term_field_data']['tid']['filter']['title'] = $this->t('Term');
+-    $data['taxonomy_term_field_data']['tid']['filter']['help'] = $this->t('Taxonomy term chosen from autocomplete or select widget.');
+-    $data['taxonomy_term_field_data']['tid']['filter']['hierarchy table'] = 'taxonomy_term__parent';
+-    $data['taxonomy_term_field_data']['tid']['filter']['numeric'] = TRUE;
++    $data[$data_table]['tid']['filter']['id'] = 'taxonomy_index_tid';
++    $data[$data_table]['tid']['filter']['title'] = $this->t('Term');
++    $data[$data_table]['tid']['filter']['help'] = $this->t('Taxonomy term chosen from autocomplete or select widget.');
++    $data[$data_table]['tid']['filter']['hierarchy table'] = $parent_table;
++    $data[$data_table]['tid']['filter']['numeric'] = TRUE;
+ 
+-    $data['taxonomy_term_field_data']['tid_raw'] = [
++    $data[$data_table]['tid_raw'] = [
+       'title' => $this->t('Term ID'),
+       'help' => $this->t('The tid of a taxonomy term.'),
+       'real field' => 'tid',
+@@ -49,7 +60,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['taxonomy_term_field_data']['tid_representative'] = [
++    $data[$data_table]['tid_representative'] = [
+       'relationship' => [
+         'title' => $this->t('Representative node'),
+         'label'  => $this->t('Representative node'),
+@@ -57,31 +68,31 @@ public function getViewsData() {
+         'id' => 'groupwise_max',
+         'relationship field' => 'tid',
+         'outer field' => 'taxonomy_term_field_data.tid',
+-        'argument table' => 'taxonomy_term_field_data',
++        'argument table' => $data_table,
+         'argument field' => 'tid',
+-        'base'   => 'node_field_data',
++        'base'   => $node_table,
+         'field'  => 'nid',
+-        'relationship' => 'node_field_data:term_node_tid',
++        'relationship' => "$node_table:term_node_tid",
+       ],
+     ];
+ 
+-    $data['taxonomy_term_field_data']['vid']['help'] = $this->t('Filter the results of "Taxonomy: Term" to a particular vocabulary.');
+-    $data['taxonomy_term_field_data']['vid']['field']['help'] = t('The vocabulary name.');
+-    $data['taxonomy_term_field_data']['vid']['argument']['id'] = 'vocabulary_vid';
++    $data[$data_table]['vid']['help'] = $this->t('Filter the results of "Taxonomy: Term" to a particular vocabulary.');
++    $data[$data_table]['vid']['field']['help'] = t('The vocabulary name.');
++    $data[$data_table]['vid']['argument']['id'] = 'vocabulary_vid';
+ 
+-    $data['taxonomy_term_field_data']['vid']['sort']['title'] = t('Vocabulary ID');
+-    $data['taxonomy_term_field_data']['vid']['sort']['help'] = t('The raw vocabulary ID.');
++    $data[$data_table]['vid']['sort']['title'] = t('Vocabulary ID');
++    $data[$data_table]['vid']['sort']['help'] = t('The raw vocabulary ID.');
+ 
+-    $data['taxonomy_term_field_data']['name']['field']['id'] = 'term_name';
+-    $data['taxonomy_term_field_data']['name']['argument']['many to one'] = TRUE;
+-    $data['taxonomy_term_field_data']['name']['argument']['empty field name'] = $this->t('Uncategorized');
++    $data[$data_table]['name']['field']['id'] = 'term_name';
++    $data[$data_table]['name']['argument']['many to one'] = TRUE;
++    $data[$data_table]['name']['argument']['empty field name'] = $this->t('Uncategorized');
+ 
+-    $data['taxonomy_term_field_data']['description__value']['field']['click sortable'] = FALSE;
++    $data[$data_table]['description__value']['field']['click sortable'] = FALSE;
+ 
+-    $data['taxonomy_term_field_data']['changed']['title'] = $this->t('Updated date');
+-    $data['taxonomy_term_field_data']['changed']['help'] = $this->t('The date the term was last updated.');
++    $data[$data_table]['changed']['title'] = $this->t('Updated date');
++    $data[$data_table]['changed']['help'] = $this->t('The date the term was last updated.');
+ 
+-    $data['taxonomy_term_field_data']['changed_fulldate'] = [
++    $data[$data_table]['changed_fulldate'] = [
+       'title' => $this->t('Updated date'),
+       'help' => $this->t('Date in the form of CCYYMMDD.'),
+       'argument' => [
+@@ -90,7 +101,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['taxonomy_term_field_data']['changed_year_month'] = [
++    $data[$data_table]['changed_year_month'] = [
+       'title' => $this->t('Updated year + month'),
+       'help' => $this->t('Date in the form of YYYYMM.'),
+       'argument' => [
+@@ -99,7 +110,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['taxonomy_term_field_data']['changed_year'] = [
++    $data[$data_table]['changed_year'] = [
+       'title' => $this->t('Updated year'),
+       'help' => $this->t('Date in the form of YYYY.'),
+       'argument' => [
+@@ -108,7 +119,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['taxonomy_term_field_data']['changed_month'] = [
++    $data[$data_table]['changed_month'] = [
+       'title' => $this->t('Updated month'),
+       'help' => $this->t('Date in the form of MM (01 - 12).'),
+       'argument' => [
+@@ -117,7 +128,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['taxonomy_term_field_data']['changed_day'] = [
++    $data[$data_table]['changed_day'] = [
+       'title' => $this->t('Updated day'),
+       'help' => $this->t('Date in the form of DD (01 - 31).'),
+       'argument' => [
+@@ -126,7 +137,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['taxonomy_term_field_data']['changed_week'] = [
++    $data[$data_table]['changed_week'] = [
+       'title' => $this->t('Updated week'),
+       'help' => $this->t('Date in the form of WW (01 - 53).'),
+       'argument' => [
+@@ -138,31 +149,34 @@ public function getViewsData() {
+     $data['taxonomy_index']['table']['group'] = $this->t('Taxonomy term');
+ 
+     $data['taxonomy_index']['table']['join'] = [
+-      'taxonomy_term_field_data' => [
++      $data_table => [
+         // Links directly to taxonomy_term_field_data via tid
+         'left_field' => 'tid',
+         'field' => 'tid',
+       ],
+-      'node_field_data' => [
++      $node_table => [
+         // Links directly to node via nid
+         'left_field' => 'nid',
+         'field' => 'nid',
+       ],
+-      'taxonomy_term__parent' => [
++    ];
++
++    if ($this->connection->driver() != 'mongodb') {
++      $data['taxonomy_index']['table']['join']['taxonomy_term__parent'] = [
+         'left_field' => 'entity_id',
+         'field' => 'tid',
+-      ],
+-    ];
++      ];
++    }
+ 
+     $data['taxonomy_index']['nid'] = [
+       'title' => $this->t('Content with term'),
+       'help' => $this->t('Relate all content tagged with a term.'),
+       'relationship' => [
+         'id' => 'standard',
+-        'base' => 'node_field_data',
++        'base' => $node_table,
+         'base field' => 'nid',
+         'label' => $this->t('node'),
+-        'skip base' => 'node_field_data',
++        'skip base' => $node_table,
+       ],
+     ];
+ 
+@@ -174,18 +188,18 @@ public function getViewsData() {
+       'help' => $this->t('Display content if it has the selected taxonomy terms.'),
+       'argument' => [
+         'id' => 'taxonomy_index_tid',
+-        'name table' => 'taxonomy_term_field_data',
++        'name table' => $data_table,
+         'name field' => 'name',
+         'empty field name' => $this->t('Uncategorized'),
+         'numeric' => TRUE,
+-        'skip base' => 'taxonomy_term_field_data',
++        'skip base' => $data_table,
+       ],
+       'filter' => [
+         'title' => $this->t('Has taxonomy term'),
+         'id' => 'taxonomy_index_tid',
+-        'hierarchy table' => 'taxonomy_term__parent',
++        'hierarchy table' => $parent_table,
+         'numeric' => TRUE,
+-        'skip base' => 'taxonomy_term_field_data',
++        'skip base' => $data_table,
+         'allow empty' => TRUE,
+       ],
+     ];
+@@ -225,15 +239,17 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    // Link to self through left.parent = right.tid (going down in depth).
+-    $data['taxonomy_term__parent']['table']['join']['taxonomy_term__parent'] = [
+-      'left_field' => 'entity_id',
+-      'field' => 'parent_target_id',
+-    ];
++    if ($this->connection->driver() != 'mongodb') {
++      // Link to self through left.parent = right.tid (going down in depth).
++      $data['taxonomy_term__parent']['table']['join']['taxonomy_term__parent'] = [
++        'left_field' => 'entity_id',
++        'field' => 'parent_target_id',
++      ];
+ 
+-    $data['taxonomy_term__parent']['parent_target_id']['help'] = $this->t('The parent term of the term. This can produce duplicate entries if you are using a vocabulary that allows multiple parents.');
+-    $data['taxonomy_term__parent']['parent_target_id']['relationship']['label'] = $this->t('Parent');
+-    $data['taxonomy_term__parent']['parent_target_id']['argument']['id'] = 'taxonomy';
++      $data['taxonomy_term__parent']['parent_target_id']['help'] = $this->t('The parent term of the term. This can produce duplicate entries if you are using a vocabulary that allows multiple parents.');
++      $data['taxonomy_term__parent']['parent_target_id']['relationship']['label'] = $this->t('Parent');
++      $data['taxonomy_term__parent']['parent_target_id']['argument']['id'] = 'taxonomy';
++    }
+ 
+     return $data;
+   }
+diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
+index d00cf3f80502039b46696c3679bb7433916380ba..26f78ed3488c16bb84e65e03e8f0c3f3457c8965 100644
+--- a/core/modules/taxonomy/taxonomy.module
++++ b/core/modules/taxonomy/taxonomy.module
+@@ -131,7 +131,7 @@ function taxonomy_build_node_index($node) {
+       $connection = \Drupal::database();
+       foreach ($tid_all as $tid) {
+         $connection->merge('taxonomy_index')
+-          ->keys(['nid' => $node->id(), 'tid' => $tid, 'status' => $node->isPublished()])
++          ->keys(['nid' => (int) $node->id(), 'tid' => (int) $tid, 'status' => (bool) $node->isPublished()])
+           ->fields(['sticky' => $sticky, 'created' => $node->getCreatedTime()])
+           ->execute();
+       }
+@@ -147,7 +147,7 @@ function taxonomy_build_node_index($node) {
+  */
+ function taxonomy_delete_node_index(EntityInterface $node) {
+   if (\Drupal::config('taxonomy.settings')->get('maintain_index_table')) {
+-    \Drupal::database()->delete('taxonomy_index')->condition('nid', $node->id())->execute();
++    \Drupal::database()->delete('taxonomy_index')->condition('nid', (int) $node->id())->execute();
+   }
+ }
+ 
+diff --git a/core/modules/user/src/Authentication/Provider/Cookie.php b/core/modules/user/src/Authentication/Provider/Cookie.php
+index fb2c9cd5845585db4107daf9258f6287e82d4d72..64b33fd6645292711d9b23d03d9fce3b43784eef 100644
+--- a/core/modules/user/src/Authentication/Provider/Cookie.php
++++ b/core/modules/user/src/Authentication/Provider/Cookie.php
+@@ -93,19 +93,78 @@ public function authenticate(Request $request) {
+    */
+   protected function getUserFromSession(SessionInterface $session) {
+     if ($uid = $session->get('uid')) {
+-      // @todo Load the User entity in SessionHandler so we don't need queries.
+-      // @see https://www.drupal.org/node/2345611
+-      $values = $this->connection
+-        ->query('SELECT * FROM {users_field_data} [u] WHERE [u].[uid] = :uid AND [u].[default_langcode] = 1', [':uid' => $uid])
+-        ->fetchAssoc();
+-
+-      // Check if the user data was found and the user is active.
+-      if (!empty($values) && $values['status'] == 1) {
+-        // Add the user's roles.
+-        $rids = $this->connection
+-          ->query('SELECT [roles_target_id] FROM {user__roles} WHERE [entity_id] = :uid', [':uid' => $values['uid']])
+-          ->fetchCol();
+-        $values['roles'] = array_merge([AccountInterface::AUTHENTICATED_ROLE], $rids);
++      if ($this->connection->driver() == 'mongodb') {
++        $prefixed_table = $this->connection->getPrefix() . 'users';
++        $result = $this->connection->getConnection()->selectCollection($prefixed_table)->findOne(
++          ['uid' => ['$eq' => (int) $uid]],
++          [
++            'projection' => ['user_translations' => 1, '_id' => 0],
++            'session' => $this->connection->getMongodbSession(),
++          ],
++        );
++
++        $values = [];
++        if (isset($result->user_translations)) {
++          $user_translations = (array) $result->user_translations;
++          foreach ($user_translations as $user_translation) {
++            if (isset($user_translation->default_langcode) && ($user_translation->default_langcode === TRUE)) {
++              if (isset($user_translation->uid)) {
++                $values['uid'] = (string) $user_translation->uid;
++              }
++              if (isset($user_translation->access)) {
++                $values['access'] = (int) $user_translation->access->__toString();
++                $values['access'] = $values['access'] / 1000;
++                $values['access'] = (string) $values['access'];
++              }
++              if (isset($user_translation->name)) {
++                $values['name'] = $user_translation->name;
++              }
++              if (isset($user_translation->preferred_langcode)) {
++                $values['preferred_langcode'] = $user_translation->preferred_langcode;
++              }
++              if (isset($user_translation->preferred_admin_langcode)) {
++                $values['preferred_admin_langcode'] = $user_translation->preferred_admin_langcode;
++              }
++              if (isset($user_translation->mail)) {
++                $values['mail'] = $user_translation->mail;
++              }
++              if (isset($user_translation->timezone)) {
++                $values['timezone'] = $user_translation->timezone;
++              }
++
++              // Add the user role authenticated.
++              $values['roles'] = [AccountInterface::AUTHENTICATED_ROLE];
++              if (isset($user_translation->user_translations__roles)) {
++                $user_translations__roles = (array) $user_translation->user_translations__roles;
++                foreach ($user_translations__roles as $user_translations__role) {
++                  if (isset($user_translations__role->roles_target_id)) {
++                    $values['roles'][] = $user_translations__role->roles_target_id;
++                  }
++                }
++              }
++            }
++          }
++        }
++
++        if (!empty($values)) {
++          return new UserSession($values);
++        }
++      }
++      else {
++        // @todo Load the User entity in SessionHandler so we don't need queries.
++        // @see https://www.drupal.org/node/2345611
++        $values = $this->connection
++          ->query('SELECT * FROM {users_field_data} [u] WHERE [u].[uid] = :uid AND [u].[default_langcode] = 1', [':uid' => $uid])
++          ->fetchAssoc();
++
++        // Check if the user data was found and the user is active.
++        if (!empty($values) && $values['status'] == 1) {
++          // Add the user's roles.
++          $rids = $this->connection
++            ->query('SELECT [roles_target_id] FROM {user__roles} WHERE [entity_id] = :uid', [':uid' => $values['uid']])
++            ->fetchCol();
++          $values['roles'] = array_merge([AccountInterface::AUTHENTICATED_ROLE], $rids);
++        }
+ 
+         return new UserSession($values);
+       }
+diff --git a/core/modules/user/src/Controller/UserAuthenticationController.php b/core/modules/user/src/Controller/UserAuthenticationController.php
+index af31a878ddcc957bbd2581bf257ab38703c0fbd4..4e4cd7ea27a7d170aa709f7737dbc2a68b69cf24 100644
+--- a/core/modules/user/src/Controller/UserAuthenticationController.php
++++ b/core/modules/user/src/Controller/UserAuthenticationController.php
+@@ -420,7 +420,7 @@ protected function floodControl(Request $request, $username) {
+    */
+   protected function getLoginFloodIdentifier(Request $request, $username) {
+     $flood_config = $this->config('user.flood');
+-    $accounts = $this->userStorage->loadByProperties(['name' => $username, 'status' => 1]);
++    $accounts = $this->userStorage->loadByProperties(['name' => $username, 'status' => TRUE]);
+     if ($account = reset($accounts)) {
+       if ($flood_config->get('uid_only')) {
+         // Register flood events based on the uid only, so they apply for any
+diff --git a/core/modules/user/src/Hook/UserViewsExecutionHooks.php b/core/modules/user/src/Hook/UserViewsExecutionHooks.php
+index 0acca99b3ea2d471757d43484a25eee4417424d5..03ac68152ed0d8005d58eceebe7d6383978dafcf 100644
+--- a/core/modules/user/src/Hook/UserViewsExecutionHooks.php
++++ b/core/modules/user/src/Hook/UserViewsExecutionHooks.php
+@@ -17,7 +17,7 @@ class UserViewsExecutionHooks {
+    */
+   #[Hook('views_query_substitutions')]
+   public function viewsQuerySubstitutions(ViewExecutable $view) {
+-    return ['***CURRENT_USER***' => \Drupal::currentUser()->id()];
++    return ['***CURRENT_USER***' => (int) \Drupal::currentUser()->id()];
+   }
+ 
+ }
+diff --git a/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php b/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php
+index 719538201c4ddbfcd32db68d4ba6646ebd72fdfe..2e4e06ce7e83ba3b2a69aae4ba279b2681a2cb77 100644
+--- a/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php
++++ b/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php
+@@ -2,6 +2,7 @@
+ 
+ namespace Drupal\user\Plugin\EntityReferenceSelection;
+ 
++use Daffie\SqlLikeToRegularExpression;
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\Query\SelectInterface;
+ use Drupal\Core\Entity\Attribute\EntityReferenceSelection;
+@@ -178,7 +179,7 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
+     // Adding the permission check is sadly insufficient for users: core
+     // requires us to also know about the concept of 'blocked' and 'active'.
+     if (!$this->currentUser->hasPermission('administer users')) {
+-      $query->condition('status', 1);
++      $query->condition('status', TRUE);
+     }
+     return $query;
+   }
+@@ -236,7 +237,7 @@ public function entityQueryAlter(SelectInterface $query) {
+       // database.
+       $conditions = &$query->conditions();
+       foreach ($conditions as $key => $condition) {
+-        if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users_field_data.name') {
++        if ($key !== '#conjunction' && is_string($condition['field']) && (($condition['field'] === 'users_field_data.name') || ($condition['field'] === 'user_translations.name'))) {
+           // Remove the condition.
+           unset($conditions[$key]);
+ 
+@@ -245,18 +246,31 @@ public function entityQueryAlter(SelectInterface $query) {
+           // WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
+           $or = $this->connection->condition('OR');
+           $or->condition($condition['field'], $condition['value'], $condition['operator']);
+-          // Sadly, the Database layer doesn't allow us to build a condition
+-          // in the form ':placeholder = :placeholder2', because the 'field'
+-          // part of a condition is always escaped.
+-          // As a (cheap) workaround, we separately build a condition with no
+-          // field, and concatenate the field and the condition separately.
+-          $value_part = $this->connection->condition('AND');
+-          $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
+-          $value_part->compile($this->connection, $query);
+-          $or->condition(($this->connection->condition('AND'))
+-            ->where(str_replace($query->escapeField('anonymous_name'), ':anonymous_name', (string) $value_part), $value_part->arguments() + [':anonymous_name' => \Drupal::config('user.settings')->get('anonymous')])
+-            ->condition('base_table.uid', 0)
+-          );
++
++          if ($condition['field'] === 'user_translations.name') {
++            $pattern = SqlLikeToRegularExpression::convert($condition['value']);
++            preg_match('/' . $pattern . '/i', \Drupal::config('user.settings')->get('anonymous'), $matches);
++            if ($matches) {
++              $or->condition('uid', 0);
++            }
++          }
++          else {
++            // Sadly, the Database layer doesn't allow us to build a condition
++            // in the form ':placeholder = :placeholder2', because the 'field'
++            // part of a condition is always escaped.
++            // As a (cheap) workaround, we separately build a condition with no
++            // field, and concatenate the field and the condition separately.
++            $value_part = $this->connection->condition('AND');
++            $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
++            $value_part->compile($this->connection, $query);
++            $or->condition(($this->connection->condition('AND'))
++              ->where(str_replace($query->escapeField('anonymous_name'), ':anonymous_name', (string) $value_part), $value_part->arguments() + [
++                ':anonymous_name' => \Drupal::config('user.settings')->get('anonymous'),
++              ])
++              ->condition('base_table.uid', 0)
++            );
++          }
++
+           $query->condition($or);
+         }
+       }
+diff --git a/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldOptionTranslation.php b/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldOptionTranslation.php
+index ddaf3093f0df30f717ac379ad2101cd1ed676e01..46f91d0cdc0961bf8d4d136498cb433f4a525988 100644
+--- a/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldOptionTranslation.php
++++ b/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldOptionTranslation.php
+@@ -31,8 +31,8 @@ public function query() {
+       ->fields('lt', ['translation', 'language'])
+       ->condition('i18n.type', 'field')
+       ->condition('property', 'options');
+-    $query->leftJoin('i18n_strings', 'i18n', '[pf].[name] = [i18n].[objectid]');
+-    $query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
++    $query->leftJoin('i18n_strings', 'i18n', $query->joinCondition()->compare('pf.name', 'i18n.objectid'));
++    $query->innerJoin('locales_target', 'lt', $query->joinCondition()->compare('lt.lid', 'i18n.lid'));
+ 
+     return $query;
+   }
+diff --git a/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php b/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php
+index 8445be5cc3f8ab898335790aefad3a857c345618..5a958786a6c694ef341c69c010cb0523c3f6adac 100644
+--- a/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php
++++ b/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php
+@@ -38,7 +38,7 @@ public function prepareRow(Row $row) {
+     // Find profile values for this row.
+     $query = $this->select('profile_values', 'pv')
+       ->fields('pv', ['fid', 'value']);
+-    $query->leftJoin('profile_fields', 'pf', '[pf].[fid] = [pv].[fid]');
++    $query->leftJoin('profile_fields', 'pf', $query->joinCondition()->compare('pf.fid', 'pv.fid'));
+     $query->fields('pf', ['name', 'type']);
+     $query->condition('uid', $row->getSourceProperty('uid'));
+     $results = $query->execute();
+@@ -74,7 +74,7 @@ public function fields() {
+ 
+     $query = $this->select('profile_values', 'pv')
+       ->fields('pv', ['fid', 'value']);
+-    $query->leftJoin('profile_fields', 'pf', '[pf].[fid] = [pv].[fid]');
++    $query->leftJoin('profile_fields', 'pf', $query->joinCondition()->compare('pf.fid', 'pv.fid'));
+     $query->fields('pf', ['name', 'title']);
+     $results = $query->execute();
+     foreach ($results as $profile) {
+diff --git a/core/modules/user/src/Plugin/migrate/source/d7/User.php b/core/modules/user/src/Plugin/migrate/source/d7/User.php
+index affcf58b2076d40fd8d2c4b477ed9418c0013453..e5ac71a2931255a5bddf9c561e9135c2d865bbf4 100644
+--- a/core/modules/user/src/Plugin/migrate/source/d7/User.php
++++ b/core/modules/user/src/Plugin/migrate/source/d7/User.php
+@@ -100,7 +100,7 @@ public function prepareRow(Row $row) {
+     if ($this->getDatabase()->schema()->tableExists('profile_value')) {
+       $query = $this->select('profile_value', 'pv')
+         ->fields('pv', ['fid', 'value']);
+-      $query->leftJoin('profile_field', 'pf', '[pf].[fid] = [pv].[fid]');
++      $query->leftJoin('profile_field', 'pf', $query->joinCondition()->compare('pf.fid', 'pv.fid'));
+       $query->fields('pf', ['name', 'type']);
+       $query->condition('uid', $row->getSourceProperty('uid'));
+       $results = $query->execute();
+diff --git a/core/modules/user/src/Plugin/views/wizard/Users.php b/core/modules/user/src/Plugin/views/wizard/Users.php
+index 963f5c16b5922abd75a3cb2e4b4c2ad09c2e0ebc..9d4eeb284044806c15ff1c59e8ad28287844e89c 100644
+--- a/core/modules/user/src/Plugin/views/wizard/Users.php
++++ b/core/modules/user/src/Plugin/views/wizard/Users.php
+@@ -2,9 +2,13 @@
+ 
+ namespace Drupal\user\Plugin\views\wizard;
+ 
++use Drupal\Core\Database\Connection;
++use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
++use Drupal\Core\Menu\MenuParentFormSelectorInterface;
+ use Drupal\Core\StringTranslation\TranslatableMarkup;
+ use Drupal\views\Attribute\ViewsWizard;
+ use Drupal\views\Plugin\views\wizard\WizardPluginBase;
++use Symfony\Component\DependencyInjection\ContainerInterface;
+ 
+ /**
+  * @todo Replace numbers with constants.
+@@ -43,6 +47,32 @@ class Users extends WizardPluginBase {
+     ],
+   ];
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
++    return new static(
++      $configuration,
++      $plugin_id,
++      $plugin_definition,
++      $container->get('entity_type.bundle.info'),
++      $container->get('menu.parent_form_selector'),
++      $container->get('database')
++    );
++  }
++
++  /**
++   * Constructs a WizardPluginBase object.
++   */
++  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, MenuParentFormSelectorInterface $parent_form_selector, Connection $connection) {
++    parent::__construct($configuration, $plugin_id, $plugin_definition, $bundle_info_service, $parent_form_selector, $connection);
++
++    if ($connection->driver() == 'mongodb') {
++      $this->base_table = 'users';
++      $this->filters['status']['table'] = 'users';
++    }
++  }
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -58,7 +88,12 @@ protected function defaultDisplayOptions() {
+ 
+     /* Field: User: Name */
+     $display_options['fields']['name']['id'] = 'name';
+-    $display_options['fields']['name']['table'] = 'users_field_data';
++    if ($this->connection->driver() == 'mongodb') {
++      $display_options['fields']['name']['table'] = 'users';
++    }
++    else {
++      $display_options['fields']['name']['table'] = 'users_field_data';
++    }
+     $display_options['fields']['name']['field'] = 'name';
+     $display_options['fields']['name']['entity_type'] = 'user';
+     $display_options['fields']['name']['entity_field'] = 'name';
+diff --git a/core/modules/user/src/UserData.php b/core/modules/user/src/UserData.php
+index 8056f33ce13d103fadc5366281be9feb164a28db..129105dedbbba641c90b3f1b551df22e570f907f 100644
+--- a/core/modules/user/src/UserData.php
++++ b/core/modules/user/src/UserData.php
+@@ -34,7 +34,7 @@ public function get($module, $uid = NULL, $name = NULL) {
+       ->fields('ud')
+       ->condition('module', $module);
+     if (isset($uid)) {
+-      $query->condition('uid', $uid);
++      $query->condition('uid', (int) $uid);
+     }
+     if (isset($name)) {
+       $query->condition('name', $name);
+diff --git a/core/modules/user/src/UserStorage.php b/core/modules/user/src/UserStorage.php
+index 9d24d4c1b4d728bf7025a7707989045849822e82..d66049525af1ba133aee3d0434cec65afc0ab4cb 100644
+--- a/core/modules/user/src/UserStorage.php
++++ b/core/modules/user/src/UserStorage.php
+@@ -58,7 +58,7 @@ public function updateLastAccessTimestamp(AccountInterface $account, $timestamp)
+       ->fields([
+         'access' => $timestamp,
+       ])
+-      ->condition('uid', $account->id())
++      ->condition('uid', (int) $account->id())
+       ->execute();
+     // Ensure that the entity cache is cleared.
+     $this->resetCache([$account->id()]);
+diff --git a/core/modules/user/src/UserViewsData.php b/core/modules/user/src/UserViewsData.php
+index 7581d67b67cbb2544651d809814067a1a2749d41..87a04bd291329c6dc66068efd4563571ba473b2d 100644
+--- a/core/modules/user/src/UserViewsData.php
++++ b/core/modules/user/src/UserViewsData.php
+@@ -15,31 +15,40 @@ class UserViewsData extends EntityViewsData {
+   public function getViewsData() {
+     $data = parent::getViewsData();
+ 
+-    $data['users_field_data']['table']['base']['help'] = $this->t('Users who have created accounts on your site.');
+-    $data['users_field_data']['table']['base']['access query tag'] = 'user_access';
+-
+-    $data['users_field_data']['table']['wizard_id'] = 'user';
+-
+-    $data['users_field_data']['uid']['argument']['id'] = 'user_uid';
+-    $data['users_field_data']['uid']['argument'] += [
+-      'name table' => 'users_field_data',
++    if ($this->connection->driver() == 'mongodb') {
++      $data_table = 'users';
++      $roles_table = 'users';
++    }
++    else {
++      $data_table = 'users_field_data';
++      $roles_table = 'user__roles';
++    }
++
++    $data[$data_table]['table']['base']['help'] = $this->t('Users who have created accounts on your site.');
++    $data[$data_table]['table']['base']['access query tag'] = 'user_access';
++
++    $data[$data_table]['table']['wizard_id'] = 'user';
++
++    $data[$data_table]['uid']['argument']['id'] = 'user_uid';
++    $data[$data_table]['uid']['argument'] += [
++      'name table' => $data_table,
+       'name field' => 'name',
+       'empty field name' => \Drupal::config('user.settings')->get('anonymous'),
+     ];
+-    $data['users_field_data']['uid']['filter']['id'] = 'user_name';
+-    $data['users_field_data']['uid']['filter']['title'] = $this->t('Name (autocomplete)');
+-    $data['users_field_data']['uid']['filter']['help'] = $this->t('The user or author name. Uses an autocomplete widget to find a user name, the actual filter uses the resulting user ID.');
+-    $data['users_field_data']['uid']['relationship'] = [
++    $data[$data_table]['uid']['filter']['id'] = 'user_name';
++    $data[$data_table]['uid']['filter']['title'] = $this->t('Name (autocomplete)');
++    $data[$data_table]['uid']['filter']['help'] = $this->t('The user or author name. Uses an autocomplete widget to find a user name, the actual filter uses the resulting user ID.');
++    $data[$data_table]['uid']['relationship'] = [
+       'title' => $this->t('Content authored'),
+       'help' => $this->t('Relate content to the user who created it. This relationship will create one record for each content item created by the user.'),
+       'id' => 'standard',
+-      'base' => 'node_field_data',
++      'base' => ($this->connection->driver() == 'mongodb' ? 'node' : 'node_field_data'),
+       'base field' => 'uid',
+       'field' => 'uid',
+       'label' => $this->t('nodes'),
+     ];
+ 
+-    $data['users_field_data']['uid_raw'] = [
++    $data[$data_table]['uid_raw'] = [
+       'help' => $this->t('The raw numeric user ID.'),
+       'real field' => 'uid',
+       'filter' => [
+@@ -48,19 +57,19 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['uid_representative'] = [
++    $data[$data_table]['uid_representative'] = [
+       'relationship' => [
+         'title' => $this->t('Representative node'),
+         'label'  => $this->t('Representative node'),
+         'help' => $this->t('Obtains a single representative node for each user, according to a chosen sort criterion.'),
+         'id' => 'groupwise_max',
+         'relationship field' => 'uid',
+-        'outer field' => 'users_field_data.uid',
+-        'argument table' => 'users_field_data',
++        'outer field' => "$data_table.uid",
++        'argument table' => $data_table,
+         'argument field' => 'uid',
+-        'base' => 'node_field_data',
++        'base' => ($this->connection->driver() == 'mongodb' ? 'node' : 'node_field_data'),
+         'field' => 'nid',
+-        'relationship' => 'node_field_data:uid',
++        'relationship' => ($this->connection->driver() == 'mongodb' ? 'node:uid' : 'node_field_data:uid'),
+       ],
+     ];
+ 
+@@ -74,22 +83,22 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['name']['help'] = $this->t('The user or author name.');
+-    $data['users_field_data']['name']['field']['default_formatter'] = 'user_name';
+-    $data['users_field_data']['name']['filter']['title'] = $this->t('Name (raw)');
+-    $data['users_field_data']['name']['filter']['help'] = $this->t('The user or author name. This filter does not check if the user exists and allows partial matching. Does not use autocomplete.');
++    $data[$data_table]['name']['help'] = $this->t('The user or author name.');
++    $data[$data_table]['name']['field']['default_formatter'] = 'user_name';
++    $data[$data_table]['name']['filter']['title'] = $this->t('Name (raw)');
++    $data[$data_table]['name']['filter']['help'] = $this->t('The user or author name. This filter does not check if the user exists and allows partial matching. Does not use autocomplete.');
+ 
+     // Note that this field implements field level access control.
+-    $data['users_field_data']['mail']['help'] = $this->t('Email address for a given user. This field is normally not shown to users, so be cautious when using it.');
++    $data[$data_table]['mail']['help'] = $this->t('Email address for a given user. This field is normally not shown to users, so be cautious when using it.');
+ 
+-    $data['users_field_data']['langcode']['help'] = $this->t('Language of the translation of user information');
++    $data[$data_table]['langcode']['help'] = $this->t('Language of the translation of user information');
+ 
+-    $data['users_field_data']['preferred_langcode']['title'] = $this->t('Preferred language');
+-    $data['users_field_data']['preferred_langcode']['help'] = $this->t('Preferred language of the user');
+-    $data['users_field_data']['preferred_admin_langcode']['title'] = $this->t('Preferred admin language');
+-    $data['users_field_data']['preferred_admin_langcode']['help'] = $this->t('Preferred administrative language of the user');
++    $data[$data_table]['preferred_langcode']['title'] = $this->t('Preferred language');
++    $data[$data_table]['preferred_langcode']['help'] = $this->t('Preferred language of the user');
++    $data[$data_table]['preferred_admin_langcode']['title'] = $this->t('Preferred admin language');
++    $data[$data_table]['preferred_admin_langcode']['help'] = $this->t('Preferred administrative language of the user');
+ 
+-    $data['users_field_data']['created_fulldate'] = [
++    $data[$data_table]['created_fulldate'] = [
+       'title' => $this->t('Created date'),
+       'help' => $this->t('Date in the form of CCYYMMDD.'),
+       'argument' => [
+@@ -98,7 +107,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['created_year_month'] = [
++    $data[$data_table]['created_year_month'] = [
+       'title' => $this->t('Created year + month'),
+       'help' => $this->t('Date in the form of YYYYMM.'),
+       'argument' => [
+@@ -107,7 +116,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['created_year'] = [
++    $data[$data_table]['created_year'] = [
+       'title' => $this->t('Created year'),
+       'help' => $this->t('Date in the form of YYYY.'),
+       'argument' => [
+@@ -116,7 +125,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['created_month'] = [
++    $data[$data_table]['created_month'] = [
+       'title' => $this->t('Created month'),
+       'help' => $this->t('Date in the form of MM (01 - 12).'),
+       'argument' => [
+@@ -125,7 +134,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['created_day'] = [
++    $data[$data_table]['created_day'] = [
+       'title' => $this->t('Created day'),
+       'help' => $this->t('Date in the form of DD (01 - 31).'),
+       'argument' => [
+@@ -134,7 +143,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['created_week'] = [
++    $data[$data_table]['created_week'] = [
+       'title' => $this->t('Created week'),
+       'help' => $this->t('Date in the form of WW (01 - 53).'),
+       'argument' => [
+@@ -143,12 +152,12 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['status']['filter']['label'] = $this->t('Active');
+-    $data['users_field_data']['status']['filter']['type'] = 'yes-no';
++    $data[$data_table]['status']['filter']['label'] = $this->t('Active');
++    $data[$data_table]['status']['filter']['type'] = 'yes-no';
+ 
+-    $data['users_field_data']['changed']['title'] = $this->t('Updated date');
++    $data[$data_table]['changed']['title'] = $this->t('Updated date');
+ 
+-    $data['users_field_data']['changed_fulldate'] = [
++    $data[$data_table]['changed_fulldate'] = [
+       'title' => $this->t('Updated date'),
+       'help' => $this->t('Date in the form of CCYYMMDD.'),
+       'argument' => [
+@@ -157,7 +166,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['changed_year_month'] = [
++    $data[$data_table]['changed_year_month'] = [
+       'title' => $this->t('Updated year + month'),
+       'help' => $this->t('Date in the form of YYYYMM.'),
+       'argument' => [
+@@ -166,7 +175,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['changed_year'] = [
++    $data[$data_table]['changed_year'] = [
+       'title' => $this->t('Updated year'),
+       'help' => $this->t('Date in the form of YYYY.'),
+       'argument' => [
+@@ -175,7 +184,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['changed_month'] = [
++    $data[$data_table]['changed_month'] = [
+       'title' => $this->t('Updated month'),
+       'help' => $this->t('Date in the form of MM (01 - 12).'),
+       'argument' => [
+@@ -184,7 +193,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['changed_day'] = [
++    $data[$data_table]['changed_day'] = [
+       'title' => $this->t('Updated day'),
+       'help' => $this->t('Date in the form of DD (01 - 31).'),
+       'argument' => [
+@@ -193,7 +202,7 @@ public function getViewsData() {
+       ],
+     ];
+ 
+-    $data['users_field_data']['changed_week'] = [
++    $data[$data_table]['changed_week'] = [
+       'title' => $this->t('Updated week'),
+       'help' => $this->t('Date in the form of WW (01 - 53).'),
+       'argument' => [
+@@ -219,14 +228,20 @@ public function getViewsData() {
+       ],
+     ];
+ 
++    // Not sure if this is needed anymore.
++    if ($this->connection->driver() == 'mongodb') {
++      $data[$roles_table]['roles_target_id']['title'] = $this->t('Roles');
++      $data[$roles_table]['roles_target_id']['help'] = $this->t('Roles that a user belongs to.');
++    }
++
+     // Alter the user roles target_id column.
+-    $data['user__roles']['roles_target_id']['field']['id'] = 'user_roles';
+-    $data['user__roles']['roles_target_id']['field']['no group by'] = TRUE;
++    $data[$roles_table]['roles_target_id']['field']['id'] = 'user_roles';
++    $data[$roles_table]['roles_target_id']['field']['no group by'] = TRUE;
+ 
+-    $data['user__roles']['roles_target_id']['filter']['id'] = 'user_roles';
+-    $data['user__roles']['roles_target_id']['filter']['allow empty'] = TRUE;
++    $data[$roles_table]['roles_target_id']['filter']['id'] = 'user_roles';
++    $data[$roles_table]['roles_target_id']['filter']['allow empty'] = TRUE;
+ 
+-    $data['user__roles']['roles_target_id']['argument'] = [
++    $data[$roles_table]['roles_target_id']['argument'] = [
+       'id' => 'user__roles_rid',
+       'name table' => 'role',
+       'name field' => 'name',
+@@ -235,7 +250,7 @@ public function getViewsData() {
+       'numeric' => FALSE,
+     ];
+ 
+-    $data['user__roles']['permission'] = [
++    $data[$roles_table]['permission'] = [
+       'title' => $this->t('Permission'),
+       'help' => $this->t('The user permissions.'),
+       'field' => [
+@@ -250,7 +265,7 @@ public function getViewsData() {
+ 
+     // Unset the "pass" field because the access control handler for the user
+     // entity type allows editing the password, but not viewing it.
+-    unset($data['users_field_data']['pass']);
++    unset($data[$data_table]['pass']);
+ 
+     return $data;
+   }
+diff --git a/core/modules/user/user.install b/core/modules/user/user.install
+index 99ede9e59b764db9271cb034132102277b2d4a89..bc9199b5e64ee5b9dba8c832147ee65abf60db7e 100644
+--- a/core/modules/user/user.install
++++ b/core/modules/user/user.install
+@@ -5,6 +5,8 @@
+  * Install, update and uninstall functions for the user module.
+  */
+ 
++use Drupal\mongodb\Driver\Database\mongodb\Statement;
++
+ /**
+  * Implements hook_schema().
+  */
+@@ -62,6 +64,14 @@ function user_schema(): array {
+     ],
+   ];
+ 
++  if (\Drupal::database()->driver() == 'mongodb') {
++    $schema['users_data']['fields']['serialized'] = [
++      'description' => 'Whether value is serialized.',
++      'type' => 'bool',
++      'default' => FALSE,
++    ];
++  }
++
+   return $schema;
+ }
+ 
+@@ -116,12 +126,58 @@ function user_requirements($phase): array {
+     ];
+   }
+ 
+-  $query = \Drupal::database()->select('users_field_data');
+-  $query->addExpression('LOWER(mail)', 'lower_mail');
+-  $query->isNotNull('mail');
+-  $query->groupBy('lower_mail');
+-  $query->having('COUNT(uid) > :matches', [':matches' => 1]);
+-  $conflicts = $query->countQuery()->execute()->fetchField();
++  $connection = \Drupal::database();
++  if ($connection->driver() === 'mongodb') {
++    $prefixed_table = $connection->getPrefix() . 'users';
++    $cursor = $connection->getConnection()->selectCollection($prefixed_table)->aggregate(
++      [
++        [
++          '$unwind' => ['path' => '$user_translations'],
++        ],
++        [
++          '$replaceRoot' => [
++            'newRoot' => [
++              '$mergeObjects' => [
++                '$$ROOT',
++                '$user_translations',
++              ],
++            ],
++          ],
++        ],
++        [
++          '$match' => [
++            'mail' => ['$ne' => NULL],
++          ],
++        ],
++        [
++          '$addFields' => [
++            'lower_mail' => ['$toLower' => '$mail'],
++          ],
++        ],
++        [
++          '$group' => [
++            '_id' => '$lower_mail',
++            'mail_count' => ['$sum' => 1],
++          ],
++        ],
++        [
++          '$match' => [
++            'mail_count' => ['$gt' => 1],
++          ],
++        ],
++      ],
++    );
++    $statement = new Statement($connection, $cursor, ['mail_count']);
++    $conflicts = $statement->execute()->fetchField();
++  }
++  else {
++    $query = \Drupal::database()->select('users_field_data');
++    $query->addExpression('LOWER(mail)', 'lower_mail');
++    $query->isNotNull('mail');
++    $query->groupBy('lower_mail');
++    $query->having('COUNT(uid) > :matches', [':matches' => 1]);
++    $conflicts = $query->countQuery()->execute()->fetchField();
++  }
+ 
+   if ($conflicts > 0) {
+     $return['conflicting emails'] = [
+diff --git a/core/modules/user/user.module b/core/modules/user/user.module
+index 907fa44f56c92999a4485aaff462de1349cf9d01..5c3ebf38ad9795c9d44eb8daf6b7a1d802c5e792 100644
+--- a/core/modules/user/user.module
++++ b/core/modules/user/user.module
+@@ -108,7 +108,7 @@ function user_is_blocked($name) {
+   return (bool) \Drupal::entityQuery('user')
+     ->accessCheck(FALSE)
+     ->condition('name', $name)
+-    ->condition('status', 0)
++    ->condition('status', FALSE)
+     ->execute();
+ }
+ 
+diff --git a/core/modules/views/src/Entity/View.php b/core/modules/views/src/Entity/View.php
+index b93be8c7e0bcc85d7dd0fb79bb102d0bac7b4ac0..e06d44c17bb00985836c9c5331d911347f99a0f4 100644
+--- a/core/modules/views/src/Entity/View.php
++++ b/core/modules/views/src/Entity/View.php
+@@ -114,6 +114,13 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
+    */
+   protected $module = 'views';
+ 
++  /**
++   * The MongoDB base table.
++   *
++   * @var string
++   */
++  public $mongodb_base_table;
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -448,7 +455,7 @@ public function mergeDefaultDisplaysOptions() {
+    * {@inheritdoc}
+    */
+   public function isInstallable() {
+-    $table_definition = \Drupal::service('views.views_data')->get($this->base_table);
++    $table_definition = \Drupal::service('views.views_data')->get($this->get('base_table'));
+     // Check whether the base table definition exists and contains a base table
+     // definition. For example, taxonomy_views_data_alter() defines
+     // node_field_data even if it doesn't exist as a base table.
+@@ -530,4 +537,27 @@ public function onDependencyRemoval(array $dependencies) {
+     return $changed;
+   }
+ 
++  /**
++   * {@inheritdoc}
++   */
++  public function get($key) {
++    if (($key == 'base_table') && isset($this->mongodb_base_table)) {
++      return $this->mongodb_base_table;
++    }
++    return parent::get($key);
++  }
++
++  /**
++   * {@inheritdoc}
++   */
++  public function toArray() {
++    $properties = parent::toArray();
++
++    if (!empty($this->original_base_table)) {
++      $properties['base_table'] = $this->original_base_table;
++    }
++
++    return $properties;
++  }
++
+ }
+diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php
+index 67e30a95cf442f40150fb417612ee1eb90bff91c..e8afc1a90034845eadd1939901b11a3dfadb5420 100644
+--- a/core/modules/views/src/EntityViewsData.php
++++ b/core/modules/views/src/EntityViewsData.php
+@@ -3,6 +3,7 @@
+ namespace Drupal\views;
+ 
+ use Drupal\Component\Utility\NestedArray;
++use Drupal\Core\Database\Connection;
+ use Drupal\Core\Entity\ContentEntityType;
+ use Drupal\Core\Entity\EntityFieldManagerInterface;
+ use Drupal\Core\Entity\EntityHandlerInterface;
+@@ -73,6 +74,13 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
+    */
+   protected $entityFieldManager;
+ 
++  /**
++   * The database connection.
++   *
++   * @var \Drupal\Core\Database\Connection
++   */
++  protected $connection;
++
+   /**
+    * Constructs an EntityViewsData object.
+    *
+@@ -88,14 +96,17 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
+    *   The translation manager.
+    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+    *   The entity field manager.
++   * @param \Drupal\Core\Database\Connection $connection
++   *   The database connection.
+    */
+-  public function __construct(EntityTypeInterface $entity_type, SqlEntityStorageInterface $storage_controller, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, TranslationInterface $translation_manager, EntityFieldManagerInterface $entity_field_manager) {
++  public function __construct(EntityTypeInterface $entity_type, SqlEntityStorageInterface $storage_controller, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, TranslationInterface $translation_manager, EntityFieldManagerInterface $entity_field_manager, Connection $connection) {
+     $this->entityType = $entity_type;
+     $this->entityTypeManager = $entity_type_manager;
+     $this->storage = $storage_controller;
+     $this->moduleHandler = $module_handler;
+     $this->setStringTranslation($translation_manager);
+     $this->entityFieldManager = $entity_field_manager;
++    $this->connection = $connection;
+   }
+ 
+   /**
+@@ -108,7 +119,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
+       $container->get('entity_type.manager'),
+       $container->get('module_handler'),
+       $container->get('string_translation'),
+-      $container->get('entity_field.manager')
++      $container->get('entity_field.manager'),
++      $container->get('database')
+     );
+   }
+ 
+@@ -131,75 +143,58 @@ public function getViewsData() {
+     $data = [];
+ 
+     $base_table = $this->entityType->getBaseTable() ?: $this->entityType->id();
+-    $views_revision_base_table = NULL;
+-    $revisionable = $this->entityType->isRevisionable();
+     $entity_id_key = $this->entityType->getKey('id');
++    $entity_revision_key = $this->entityType->getKey('revision');
++    $revision_field = $entity_revision_key;
++    $revisionable = $this->entityType->isRevisionable();
++    $translatable = $this->entityType->isTranslatable();
+     $entity_keys = $this->entityType->getKeys();
+ 
+-    $revision_table = '';
+-    if ($revisionable) {
+-      $revision_table = $this->entityType->getRevisionTable() ?: $this->entityType->id() . '_revision';
+-    }
++    if ($this->connection->driver() == 'mongodb') {
++      // Setup base information of the views data.
++      $data[$base_table]['table']['group'] = $this->entityType->getLabel();
++      $data[$base_table]['table']['provider'] = $this->entityType->getProvider();
+ 
+-    $translatable = $this->entityType->isTranslatable();
+-    $data_table = '';
+-    if ($translatable) {
+-      $data_table = $this->entityType->getDataTable() ?: $this->entityType->id() . '_field_data';
+-    }
++      $all_revisions_table = '';
++      $current_revision_table = '';
++      if ($revisionable) {
++        $all_revisions_table = $this->storage->getJsonStorageAllRevisionsTable();
++        $data[$base_table]['table']['all revisions table'] = $all_revisions_table;
+ 
+-    // Some entity types do not have a revision data table defined, but still
+-    // have a revision table name set in
+-    // \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout() so we
+-    // apply the same kind of logic.
+-    $revision_data_table = '';
+-    if ($revisionable && $translatable) {
+-      $revision_data_table = $this->entityType->getRevisionDataTable() ?: $this->entityType->id() . '_field_revision';
+-    }
+-    $entity_revision_key = $this->entityType->getKey('revision');
+-    $revision_field = $entity_revision_key;
++        $current_revision_table = $this->storage->getJsonStorageCurrentRevisionTable();
++        $data[$base_table]['table']['current revision table'] = $current_revision_table;
+ 
+-    // Setup base information of the views data.
+-    $data[$base_table]['table']['group'] = $this->entityType->getLabel();
+-    $data[$base_table]['table']['provider'] = $this->entityType->getProvider();
++        $data[$base_table]['table']['entity revision field'] = $revision_field;
++      }
++      else {
++        $data[$base_table]['table']['all revisions table'] = FALSE;
++        $data[$base_table]['table']['current revision table'] = FALSE;
++        $data[$base_table]['table']['entity revision field'] = FALSE;
++      }
+ 
+-    $views_base_table = $base_table;
+-    if ($data_table) {
+-      $views_base_table = $data_table;
+-    }
+-    $data[$views_base_table]['table']['base'] = [
+-      'field' => $entity_id_key,
+-      'title' => $this->entityType->getLabel(),
+-      'cache_contexts' => $this->entityType->getListCacheContexts(),
+-      'access query tag' => $this->entityType->id() . '_access',
+-    ];
+-    $data[$base_table]['table']['entity revision'] = FALSE;
+-
+-    if ($label_key = $this->entityType->getKey('label')) {
+-      if ($data_table) {
+-        $data[$views_base_table]['table']['base']['defaults'] = [
+-          'field' => $label_key,
+-          'table' => $data_table,
+-        ];
++      if ($translatable && !$revisionable) {
++        $data[$base_table]['table']['translations table'] = $this->storage->getJsonStorageTranslationsTable();
+       }
+       else {
+-        $data[$views_base_table]['table']['base']['defaults'] = [
++        $data[$base_table]['table']['translations table'] = FALSE;
++      }
++
++      $data[$base_table]['table']['base'] = [
++        'field' => $entity_id_key,
++        'title' => $this->entityType->getLabel(),
++        'cache_contexts' => $this->entityType->getListCacheContexts(),
++        'access query tag' => $this->entityType->id() . '_access',
++      ];
++      $data[$base_table]['table']['entity revision'] = $revisionable;
++
++      if ($label_key = $this->entityType->getKey('label')) {
++        $data[$base_table]['table']['base']['defaults'] = [
+           'field' => $label_key,
+         ];
+       }
+-    }
+ 
+-    // Entity types must implement a list_builder in order to use Views'
+-    // entity operations field.
+-    if ($this->entityType->hasListBuilderClass()) {
+-      $data[$base_table]['operations'] = [
+-        'field' => [
+-          'title' => $this->t('Operations links'),
+-          'help' => $this->t('Provides links to perform entity operations.'),
+-          'id' => 'entity_operations',
+-        ],
+-      ];
+-      if ($revision_table) {
+-        $data[$revision_table]['operations'] = [
++      if ($this->entityType->hasListBuilderClass()) {
++        $data[$base_table]['operations'] = [
+           'field' => [
+             'title' => $this->t('Operations links'),
+             'help' => $this->t('Provides links to perform entity operations.'),
+@@ -207,168 +202,300 @@ public function getViewsData() {
+           ],
+         ];
+       }
+-    }
+ 
+-    if ($this->entityType->hasViewBuilderClass()) {
+-      $data[$base_table]['rendered_entity'] = [
+-        'field' => [
+-          'title' => $this->t('Rendered entity'),
+-          'help' => $this->t('Renders an entity in a view mode.'),
+-          'id' => 'rendered_entity',
+-        ],
+-      ];
+-    }
++      if ($this->entityType->hasViewBuilderClass()) {
++        $data[$base_table]['rendered_entity'] = [
++          'field' => [
++            'title' => $this->t('Rendered entity'),
++            'help' => $this->t('Renders an entity in a view mode.'),
++            'id' => 'rendered_entity',
++          ],
++        ];
++      }
+ 
+-    // Setup relations to the revisions/property data.
+-    if ($data_table) {
+-      $data[$base_table]['table']['join'][$data_table] = [
+-        'left_field' => $entity_id_key,
+-        'field' => $entity_id_key,
+-        'type' => 'INNER',
+-      ];
+-      $data[$data_table]['table']['group'] = $this->entityType->getLabel();
+-      $data[$data_table]['table']['provider'] = $this->entityType->getProvider();
+-      $data[$data_table]['table']['entity revision'] = FALSE;
++      if ($revisionable) {
++        $data[$base_table]['latest_revision'] = [
++          'title' => $this->t('Is Latest Revision'),
++          'help' => $this->t('Restrict the view to only revisions that are the latest revision of their entity.'),
++          'filter' => ['id' => 'latest_revision'],
++        ];
++        if ($translatable) {
++          $data[$base_table]['latest_translation_affected_revision'] = [
++            'title' => $this->t('Is Latest Translation Affected Revision'),
++            'help' => $this->t('Restrict the view to only revisions that are the latest translation affected revision of their entity.'),
++            'filter' => ['id' => 'latest_translation_affected_revision'],
++          ];
++        }
++      }
++
++      $this->addEntityLinks($data[$base_table]);
++
++      $field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id());
++
++      $field_storage_definitions = array_map(function (FieldDefinitionInterface $definition) {
++        return $definition->getFieldStorageDefinition();
++      }, $field_definitions);
++
++      if ($table_mapping = $this->storage->getTableMapping($field_storage_definitions)) {
++        $duplicate_fields = array_intersect_key($entity_keys, array_flip(['id', 'bundle']));
++
++        foreach ($table_mapping->getTableNames() as $table) {
++          foreach ($table_mapping->getFieldNames($table) as $field_name) {
++            if (($table === $current_revision_table) && in_array($field_name, $duplicate_fields)) {
++              continue;
++            }
++            if ($table === $all_revisions_table) {
++              continue;
++            }
++            $this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$base_table]);
++          }
++        }
++      }
++
++      if (($uid_key = $entity_keys['uid'] ?? '')) {
++        $data[$base_table][$uid_key]['filter']['id'] = 'user_name';
++      }
++      if ($revision_uid_key = $this->entityType->getRevisionMetadataKeys()['revision_user'] ?? '') {
++        $data[$base_table][$revision_uid_key]['filter']['id'] = 'user_name';
++      }
+     }
+-    if ($revision_table) {
+-      $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
+-      $data[$revision_table]['table']['provider'] = $this->entityType->getProvider();
+-      $data[$revision_table]['table']['entity revision'] = TRUE;
+-
+-      $views_revision_base_table = $revision_table;
+-      if ($revision_data_table) {
+-        $views_revision_base_table = $revision_data_table;
++    else {
++      $views_revision_base_table = NULL;
++
++      $revision_table = '';
++      if ($revisionable) {
++        $revision_table = $this->entityType->getRevisionTable() ?: $this->entityType->id() . '_revision';
+       }
+-      $data[$views_revision_base_table]['table']['entity revision'] = TRUE;
+-      $data[$views_revision_base_table]['table']['base'] = [
+-        'field' => $revision_field,
+-        'title' => $this->t('@entity_type revisions', ['@entity_type' => $this->entityType->getLabel()]),
+-      ];
+-      // Join the revision table to the base table.
+-      $data[$views_revision_base_table]['table']['join'][$views_base_table] = [
+-        'left_field' => $revision_field,
+-        'field' => $revision_field,
+-        'type' => 'INNER',
++
++      $translatable = $this->entityType->isTranslatable();
++      $data_table = '';
++      if ($translatable) {
++        $data_table = $this->entityType->getDataTable() ?: $this->entityType->id() . '_field_data';
++      }
++
++      // Some entity types do not have a revision data table defined, but still
++      // have a revision table name set in
++      // \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout() so we
++      // apply the same kind of logic.
++      $revision_data_table = '';
++      if ($revisionable && $translatable) {
++        $revision_data_table = $this->entityType->getRevisionDataTable() ?: $this->entityType->id() . '_field_revision';
++      }
++      $entity_revision_key = $this->entityType->getKey('revision');
++      $revision_field = $entity_revision_key;
++
++      // Setup base information of the views data.
++      $data[$base_table]['table']['group'] = $this->entityType->getLabel();
++      $data[$base_table]['table']['provider'] = $this->entityType->getProvider();
++
++      $views_base_table = $base_table;
++      if ($data_table) {
++        $views_base_table = $data_table;
++      }
++      $data[$views_base_table]['table']['base'] = [
++        'field' => $entity_id_key,
++        'title' => $this->entityType->getLabel(),
++        'cache_contexts' => $this->entityType->getListCacheContexts(),
++        'access query tag' => $this->entityType->id() . '_access',
+       ];
++      $data[$base_table]['table']['entity revision'] = FALSE;
+ 
+-      if ($revision_data_table) {
+-        $data[$revision_data_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
+-        $data[$revision_data_table]['table']['entity revision'] = TRUE;
++      if ($label_key = $this->entityType->getKey('label')) {
++        if ($data_table) {
++          $data[$views_base_table]['table']['base']['defaults'] = [
++            'field' => $label_key,
++            'table' => $data_table,
++          ];
++        }
++        else {
++          $data[$views_base_table]['table']['base']['defaults'] = [
++            'field' => $label_key,
++          ];
++        }
++      }
+ 
+-        $data[$revision_table]['table']['join'][$revision_data_table] = [
+-          'left_field' => $revision_field,
+-          'field' => $revision_field,
+-          'type' => 'INNER',
++      // Entity types must implement a list_builder in order to use Views'
++      // entity operations field.
++      if ($this->entityType->hasListBuilderClass()) {
++        $data[$base_table]['operations'] = [
++          'field' => [
++            'title' => $this->t('Operations links'),
++            'help' => $this->t('Provides links to perform entity operations.'),
++            'id' => 'entity_operations',
++          ],
+         ];
++        if ($revision_table) {
++          $data[$revision_table]['operations'] = [
++            'field' => [
++              'title' => $this->t('Operations links'),
++              'help' => $this->t('Provides links to perform entity operations.'),
++              'id' => 'entity_operations',
++            ],
++          ];
++        }
+       }
+ 
+-      // Add a filter for showing only the latest revisions of an entity.
+-      $data[$revision_table]['latest_revision'] = [
+-        'title' => $this->t('Is Latest Revision'),
+-        'help' => $this->t('Restrict the view to only revisions that are the latest revision of their entity.'),
+-        'filter' => ['id' => 'latest_revision'],
+-      ];
+-      if ($this->entityType->isTranslatable()) {
+-        $data[$revision_table]['latest_translation_affected_revision'] = [
+-          'title' => $this->t('Is Latest Translation Affected Revision'),
+-          'help' => $this->t('Restrict the view to only revisions that are the latest translation affected revision of their entity.'),
+-          'filter' => ['id' => 'latest_translation_affected_revision'],
++      if ($this->entityType->hasViewBuilderClass()) {
++        $data[$base_table]['rendered_entity'] = [
++          'field' => [
++            'title' => $this->t('Rendered entity'),
++            'help' => $this->t('Renders an entity in a view mode.'),
++            'id' => 'rendered_entity',
++          ],
+         ];
+       }
+-      // Add a relationship from the revision table back to the main table.
+-      $entity_type_label = $this->entityType->getLabel();
+-      $data[$views_revision_base_table][$entity_id_key]['relationship'] = [
+-        'id' => 'standard',
+-        'base' => $views_base_table,
+-        'base field' => $entity_id_key,
+-        'title' => $entity_type_label,
+-        'help' => $this->t('Get the actual @label from a @label revision', ['@label' => $entity_type_label]),
+-      ];
+-      $data[$views_revision_base_table][$entity_revision_key]['relationship'] = [
+-        'id' => 'standard',
+-        'base' => $views_base_table,
+-        'base field' => $entity_revision_key,
+-        'title' => $this->t('@label revision', ['@label' => $entity_type_label]),
+-        'help' => $this->t('Get the actual @label from a @label revision', ['@label' => $entity_type_label]),
+-      ];
+-      if ($translatable) {
+-        $extra = [
+-          'field' => $entity_keys['langcode'],
+-          'left_field' => $entity_keys['langcode'],
++
++      // Setup relations to the revisions/property data.
++      if ($data_table) {
++        $data[$base_table]['table']['join'][$data_table] = [
++          'left_field' => $entity_id_key,
++          'field' => $entity_id_key,
++          'type' => 'INNER',
+         ];
+-        $data[$views_revision_base_table][$entity_id_key]['relationship']['extra'][] = $extra;
+-        $data[$views_revision_base_table][$entity_revision_key]['relationship']['extra'][] = $extra;
+-        $data[$revision_table]['table']['join'][$views_base_table]['left_field'] = $entity_revision_key;
+-        $data[$revision_table]['table']['join'][$views_base_table]['field'] = $entity_revision_key;
++        $data[$data_table]['table']['group'] = $this->entityType->getLabel();
++        $data[$data_table]['table']['provider'] = $this->entityType->getProvider();
++        $data[$data_table]['table']['entity revision'] = FALSE;
+       }
++      if ($revision_table) {
++        $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
++        $data[$revision_table]['table']['provider'] = $this->entityType->getProvider();
++        $data[$revision_table]['table']['entity revision'] = TRUE;
+ 
+-    }
++        $views_revision_base_table = $revision_table;
++        if ($revision_data_table) {
++          $views_revision_base_table = $revision_data_table;
++        }
++        $data[$views_revision_base_table]['table']['entity revision'] = TRUE;
++        $data[$views_revision_base_table]['table']['base'] = [
++          'field' => $revision_field,
++          'title' => $this->t('@entity_type revisions', ['@entity_type' => $this->entityType->getLabel()]),
++        ];
++        // Join the revision table to the base table.
++        $data[$views_revision_base_table]['table']['join'][$views_base_table] = [
++          'left_field' => $revision_field,
++          'field' => $revision_field,
++          'type' => 'INNER',
++        ];
+ 
+-    $this->addEntityLinks($data[$base_table]);
+-    if ($views_revision_base_table) {
+-      $this->addEntityLinks($data[$views_revision_base_table]);
+-    }
++        if ($revision_data_table) {
++          $data[$revision_data_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
++          $data[$revision_data_table]['table']['entity revision'] = TRUE;
+ 
+-    // Load all typed data definitions of all fields. This should cover each of
+-    // the entity base, revision, data tables.
+-    $field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id());
+-    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+-    $table_mapping = $this->storage->getTableMapping($field_definitions);
+-    // Fetch all fields that can appear in both the base table and the data
+-    // table.
+-    $duplicate_fields = array_intersect_key($entity_keys, array_flip(['id', 'revision', 'bundle']));
+-    // Iterate over each table we have so far and collect field data for each.
+-    // Based on whether the field is in the field_definitions provided by the
+-    // entity field manager.
+-    // @todo We should better just rely on information coming from the entity
+-    //   storage.
+-    // @todo https://www.drupal.org/node/2337511
+-    foreach ($table_mapping->getTableNames() as $table) {
+-      foreach ($table_mapping->getFieldNames($table) as $field_name) {
+-        // To avoid confusing duplication in the user interface, for fields
+-        // that are on both base and data tables, only add them on the data
+-        // table (same for revision vs. revision data).
+-        if ($data_table && ($table === $base_table || $table === $revision_table) && in_array($field_name, $duplicate_fields)) {
+-          continue;
++          $data[$revision_table]['table']['join'][$revision_data_table] = [
++            'left_field' => $revision_field,
++            'field' => $revision_field,
++            'type' => 'INNER',
++          ];
++        }
++
++        // Add a filter for showing only the latest revisions of an entity.
++        $data[$revision_table]['latest_revision'] = [
++          'title' => $this->t('Is Latest Revision'),
++          'help' => $this->t('Restrict the view to only revisions that are the latest revision of their entity.'),
++          'filter' => ['id' => 'latest_revision'],
++        ];
++        if ($this->entityType->isTranslatable()) {
++          $data[$revision_table]['latest_translation_affected_revision'] = [
++            'title' => $this->t('Is Latest Translation Affected Revision'),
++            'help' => $this->t('Restrict the view to only revisions that are the latest translation affected revision of their entity.'),
++            'filter' => ['id' => 'latest_translation_affected_revision'],
++          ];
++        }
++        // Add a relationship from the revision table back to the main table.
++        $entity_type_label = $this->entityType->getLabel();
++        $data[$views_revision_base_table][$entity_id_key]['relationship'] = [
++          'id' => 'standard',
++          'base' => $views_base_table,
++          'base field' => $entity_id_key,
++          'title' => $entity_type_label,
++          'help' => $this->t('Get the actual @label from a @label revision', ['@label' => $entity_type_label]),
++        ];
++        $data[$views_revision_base_table][$entity_revision_key]['relationship'] = [
++          'id' => 'standard',
++          'base' => $views_base_table,
++          'base field' => $entity_revision_key,
++          'title' => $this->t('@label revision', ['@label' => $entity_type_label]),
++          'help' => $this->t('Get the actual @label from a @label revision', ['@label' => $entity_type_label]),
++        ];
++        if ($translatable) {
++          $extra = [
++            'field' => $entity_keys['langcode'],
++            'left_field' => $entity_keys['langcode'],
++          ];
++          $data[$views_revision_base_table][$entity_id_key]['relationship']['extra'][] = $extra;
++          $data[$views_revision_base_table][$entity_revision_key]['relationship']['extra'][] = $extra;
++          $data[$revision_table]['table']['join'][$views_base_table]['left_field'] = $entity_revision_key;
++          $data[$revision_table]['table']['join'][$views_base_table]['field'] = $entity_revision_key;
+         }
+-        $this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]);
++
+       }
+-    }
+ 
+-    foreach ($field_definitions as $field_definition) {
+-      if ($table_mapping->requiresDedicatedTableStorage($field_definition->getFieldStorageDefinition())) {
+-        $table = $table_mapping->getDedicatedDataTableName($field_definition->getFieldStorageDefinition());
++      $this->addEntityLinks($data[$base_table]);
++      if ($views_revision_base_table) {
++        $this->addEntityLinks($data[$views_revision_base_table]);
++      }
+ 
+-        $data[$table]['table']['group'] = $this->entityType->getLabel();
+-        $data[$table]['table']['provider'] = $this->entityType->getProvider();
+-        $data[$table]['table']['join'][$views_base_table] = [
+-          'left_field' => $entity_id_key,
+-          'field' => 'entity_id',
+-          'extra' => [
+-            ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
+-          ],
+-        ];
++      // Load all typed data definitions of all fields. This should cover each of
++      // the entity base, revision, data tables.
++      $field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id());
++      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
++      $table_mapping = $this->storage->getTableMapping($field_definitions);
++      // Fetch all fields that can appear in both the base table and the data
++      // table.
++      $duplicate_fields = array_intersect_key($entity_keys, array_flip(['id', 'revision', 'bundle']));
++      // Iterate over each table we have so far and collect field data for each.
++      // Based on whether the field is in the field_definitions provided by the
++      // entity field manager.
++      // @todo We should better just rely on information coming from the entity
++      //   storage.
++      // @todo https://www.drupal.org/node/2337511
++      foreach ($table_mapping->getTableNames() as $table) {
++        foreach ($table_mapping->getFieldNames($table) as $field_name) {
++          // To avoid confusing duplication in the user interface, for fields
++          // that are on both base and data tables, only add them on the data
++          // table (same for revision vs. revision data).
++          if ($data_table && ($table === $base_table || $table === $revision_table) && in_array($field_name, $duplicate_fields)) {
++            continue;
++          }
++          $this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]);
++        }
++      }
+ 
+-        if ($revisionable) {
+-          $revision_table = $table_mapping->getDedicatedRevisionTableName($field_definition->getFieldStorageDefinition());
++      foreach ($field_definitions as $field_definition) {
++        if ($table_mapping->requiresDedicatedTableStorage($field_definition->getFieldStorageDefinition())) {
++          $table = $table_mapping->getDedicatedDataTableName($field_definition->getFieldStorageDefinition());
+ 
+-          $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
+-          $data[$revision_table]['table']['provider'] = $this->entityType->getProvider();
+-          $data[$revision_table]['table']['join'][$views_revision_base_table] = [
+-            'left_field' => $revision_field,
++          $data[$table]['table']['group'] = $this->entityType->getLabel();
++          $data[$table]['table']['provider'] = $this->entityType->getProvider();
++          $data[$table]['table']['join'][$views_base_table] = [
++            'left_field' => $entity_id_key,
+             'field' => 'entity_id',
+             'extra' => [
+               ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
+             ],
+           ];
++
++          if ($revisionable) {
++            $revision_table = $table_mapping->getDedicatedRevisionTableName($field_definition->getFieldStorageDefinition());
++
++            $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
++            $data[$revision_table]['table']['provider'] = $this->entityType->getProvider();
++            $data[$revision_table]['table']['join'][$views_revision_base_table] = [
++              'left_field' => $revision_field,
++              'field' => 'entity_id',
++              'extra' => [
++                ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
++              ],
++            ];
++          }
+         }
+       }
+-    }
+-    if (($uid_key = $entity_keys['uid'] ?? '')) {
+-      $data[$data_table][$uid_key]['filter']['id'] = 'user_name';
+-    }
+-    if ($revision_table && ($revision_uid_key = $this->entityType->getRevisionMetadataKeys()['revision_user'] ?? '')) {
+-      $data[$revision_table][$revision_uid_key]['filter']['id'] = 'user_name';
++      if (($uid_key = $entity_keys['uid'] ?? '')) {
++        $data[$data_table][$uid_key]['filter']['id'] = 'user_name';
++      }
++      if ($revision_table && ($revision_uid_key = $this->entityType->getRevisionMetadataKeys()['revision_user'] ?? '')) {
++        $data[$revision_table][$revision_uid_key]['filter']['id'] = 'user_name';
++      }
+     }
+ 
+     // Add the entity type key to each table generated.
+@@ -438,20 +565,73 @@ protected function mapFieldDefinition($table, $field_name, FieldDefinitionInterf
+     $field_column_mapping = $table_mapping->getColumnNames($field_name);
+     $field_schema = $this->getFieldStorageDefinitions()[$field_name]->getSchema();
+ 
+-    $field_definition_type = $field_definition->getType();
+-    // Add all properties to views table data. We need an entry for each
+-    // column of each field, with the first one given special treatment.
+-    // @todo Introduce concept of the "main" column for a field, rather than
+-    //   assuming the first one is the main column. See also what the
+-    //   mapSingleFieldViewsData() method does with $first.
+-    $first = TRUE;
+-    foreach ($field_column_mapping as $field_column_name => $schema_field_name) {
+-      // The fields might be defined before the actual table.
+-      $table_data = $table_data ?: [];
+-      $table_data += [$schema_field_name => []];
+-      $table_data[$schema_field_name] = NestedArray::mergeDeep($table_data[$schema_field_name], $this->mapSingleFieldViewsData($table, $field_name, $field_definition_type, $field_column_name, $field_schema['columns'][$field_column_name]['type'], $first, $field_definition));
+-      $table_data[$schema_field_name]['entity field'] = $field_name;
+-      $first = FALSE;
++    if ($this->connection->driver() == 'mongodb') {
++      $base_table = $this->entityType->getBaseTable() ?: $this->entityType->id();
++      $field_definition_type = $field_definition->getType();
++
++      // $multiple = (count($field_column_mapping) > 1);
++      $first = TRUE;
++      foreach ($field_column_mapping as $field_column_name => $schema_field_name) {
++        // $views_field_name = ($multiple) ? $field_name . '__' . $field_column_name : $field_name;
++        // $table_data[$views_field_name] = $this->mapSingleFieldViewsData($table, $field_name, $field_definition_type, $field_column_name, $field_schema['columns'][$field_column_name]['type'], $first, $field_definition);
++        // $table_data[$views_field_name]['entity field'] = $field_name;
++
++        $table_data[$schema_field_name] = $this->mapSingleFieldViewsData($table, $field_name, $field_definition_type, $field_column_name, $field_schema['columns'][$field_column_name]['type'], $first, $field_definition);
++        $table_data[$schema_field_name]['entity field'] = $field_name;
++        $first = FALSE;
++
++        if ($table != $base_table) {
++          if ($table_mapping->requiresDedicatedTableStorage($field_definition->getFieldStorageDefinition())) {
++            if ($this->entityType->isRevisionable()) {
++              // $table_data[$views_field_name]['real field'] = $this->storage->getCurrentRevisionTable() . '.' . $table . '.' . $views_field_name;
++              $table_data[$schema_field_name]['real field'] = $this->storage->getJsonStorageCurrentRevisionTable() . '.' . $table . '.' . $schema_field_name;
++            }
++            elseif ($this->entityType->isTranslatable()) {
++              // $table_data[$views_field_name]['real field'] = $this->storage->getTranslationsTable() . '.' . $table . '.' . $views_field_name;
++              $table_data[$schema_field_name]['real field'] = $this->storage->getJsonStorageTranslationsTable() . '.' . $table . '.' . $schema_field_name;
++            }
++            else {
++              // $table_data[$views_field_name]['real field'] = $table . '.' . $views_field_name;
++              $table_data[$schema_field_name]['real field'] = $table . '.' . $schema_field_name;
++            }
++          }
++          elseif ($field_name != $this->entityType->getKey('id')) {
++            if ($this->entityType->isRevisionable()) {
++              // $table_data[$views_field_name]['real field'] = $this->storage->getCurrentRevisionTable() . '.' . $views_field_name;
++              $table_data[$schema_field_name]['real field'] = $this->storage->getJsonStorageCurrentRevisionTable() . '.' . $schema_field_name;
++            }
++            elseif ($this->entityType->isTranslatable()) {
++              // $table_data[$views_field_name]['real field'] = $this->storage->getTranslationsTable() . '.' . $views_field_name;
++              $table_data[$schema_field_name]['real field'] = $this->storage->getJsonStorageTranslationsTable() . '.' . $schema_field_name;
++            }
++            else {
++              // $table_data[$views_field_name]['real field'] = $views_field_name;
++              $table_data[$schema_field_name]['real field'] = $schema_field_name;
++            }
++          }
++          else {
++            // $table_data[$views_field_name]['real field'] = $views_field_name;
++            $table_data[$schema_field_name]['real field'] = $schema_field_name;
++          }
++        }
++      }
++    }
++    else {
++      $field_definition_type = $field_definition->getType();
++      // Add all properties to views table data. We need an entry for each
++      // column of each field, with the first one given special treatment.
++      // @todo Introduce concept of the "main" column for a field, rather than
++      //   assuming the first one is the main column. See also what the
++      //   mapSingleFieldViewsData() method does with $first.
++      $first = TRUE;
++      foreach ($field_column_mapping as $field_column_name => $schema_field_name) {
++        // The fields might be defined before the actual table.
++        $table_data = $table_data ?: [];
++        $table_data += [$schema_field_name => []];
++        $table_data[$schema_field_name] = NestedArray::mergeDeep($table_data[$schema_field_name], $this->mapSingleFieldViewsData($table, $field_name, $field_definition_type, $field_column_name, $field_schema['columns'][$field_column_name]['type'], $first, $field_definition));
++        $table_data[$schema_field_name]['entity field'] = $field_name;
++        $first = FALSE;
++      }
+     }
+   }
+ 
+@@ -705,7 +885,13 @@ protected function processViewsDataForUuid($table, FieldDefinitionInterface $fie
+    * {@inheritdoc}
+    */
+   public function getViewsTableForEntityType(EntityTypeInterface $entity_type) {
+-    return $entity_type->getDataTable() ?: $entity_type->getBaseTable();
++    if ($this->connection->driver() == 'mongodb') {
++      // For MongoDB this is always the entity base table.
++      return $entity_type->getBaseTable();
++    }
++    else {
++      return $entity_type->getDataTable() ?: $entity_type->getBaseTable();
++    }
+   }
+ 
+ }
+diff --git a/core/modules/views/src/Hook/ViewsHooks.php b/core/modules/views/src/Hook/ViewsHooks.php
+index f26b51ef05d1c6d3813a06472568861b1f85ed85..c4aa8533d779156de1af27e8a8d1bc496277214d 100644
+--- a/core/modules/views/src/Hook/ViewsHooks.php
++++ b/core/modules/views/src/Hook/ViewsHooks.php
+@@ -6,6 +6,7 @@
+ use Drupal\views\ViewEntityInterface;
+ use Drupal\views\Plugin\Derivative\ViewsLocalTask;
+ use Drupal\Core\Database\Query\AlterableInterface;
++use Drupal\Core\Database\Query\ConditionInterface;
+ use Drupal\Core\Form\FormStateInterface;
+ use Drupal\Core\Entity\EntityInterface;
+ use Drupal\views\ViewExecutable;
+@@ -350,6 +351,10 @@ public function queryViewsAlter(AlterableInterface $query): void {
+           }
+         }
+       }
++      if (isset($table_metadata['condition']) && ($table_metadata['condition'] instanceof ConditionInterface)) {
++        $table_conditions = &$tables[$table_name]['condition']->conditions();
++        _views_query_tag_alter_condition($query, $table_conditions, $substitutions);
++      }
+     }
+     // Replaces substitutions in filter criteria.
+     _views_query_tag_alter_condition($query, $where, $substitutions);
+diff --git a/core/modules/views/src/Hook/ViewsViewsHooks.php b/core/modules/views/src/Hook/ViewsViewsHooks.php
+index 623d32e9c0594ff47140d84cca3a80950c7ba266..3f4aa4e43061c94515e15ff037977306ca73adcc 100644
+--- a/core/modules/views/src/Hook/ViewsViewsHooks.php
++++ b/core/modules/views/src/Hook/ViewsViewsHooks.php
+@@ -218,6 +218,7 @@ public function viewsDataAlter(&$data): void {
+    */
+   #[Hook('field_views_data')]
+   public function fieldViewsData(FieldStorageConfigInterface $field_storage): array {
++    $driver = \Drupal::database()->driver();
+     $data = views_field_default_views_data($field_storage);
+     // The code below only deals with the Entity reference field type.
+     if ($field_storage->getType() != 'entity_reference') {
+@@ -233,8 +234,19 @@ public function fieldViewsData(FieldStorageConfigInterface $field_storage): arra
+       $target_entity_type = $entity_type_manager->getDefinition($target_entity_type_id);
+       $entity_type_id = $field_storage->getTargetEntityTypeId();
+       $entity_type = $entity_type_manager->getDefinition($entity_type_id);
+-      $target_base_table = $target_entity_type->getDataTable() ?: $target_entity_type->getBaseTable();
++      if ($driver === 'mongodb') {
++        $target_base_table = $target_entity_type->getBaseTable();
++      }
++      else {
++        $target_base_table = $target_entity_type->getDataTable() ?: $target_entity_type->getBaseTable();
++      }
+       $field_name = $field_storage->getName();
++
++      $relationship_field = $field_name . '_target_id';
++      if (($driver === 'mongodb') && isset($table_data[$field_name]['field']['real field'])) {
++        $relationship_field = $table_data[$field_name]['field']['real field'];
++      }
++
+       if ($target_entity_type instanceof ContentEntityTypeInterface) {
+         // Provide a relationship for the entity type with the entity reference
+         // field.
+@@ -250,35 +262,38 @@ public function fieldViewsData(FieldStorageConfigInterface $field_storage): arra
+           'base' => $target_base_table,
+           'entity type' => $target_entity_type_id,
+           'base field' => $target_entity_type->getKey('id'),
+-          'relationship field' => $field_name . '_target_id',
+-        ];
+-        // Provide a reverse relationship for the entity type that is referenced by
+-        // the field.
+-        $args['@entity'] = $entity_type->getLabel();
+-        $args['@label'] = $target_entity_type->getSingularLabel();
+-        $pseudo_field_name = 'reverse__' . $entity_type_id . '__' . $field_name;
+-        $data[$target_base_table][$pseudo_field_name]['relationship'] = [
+-          'title' => t('@entity using @field_name', $args),
+-          'label' => t('@field_name', [
+-            '@field_name' => $field_name,
+-          ]),
+-          'group' => $target_entity_type->getLabel(),
+-          'help' => t('Relate each @entity with a @field_name set to the @label.', $args),
+-          'id' => 'entity_reverse',
+-          'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
+-          'entity_type' => $entity_type_id,
+-          'base field' => $entity_type->getKey('id'),
+-          'field_name' => $field_name,
+-          'field table' => $table_mapping->getDedicatedDataTableName($field_storage),
+-          'field field' => $field_name . '_target_id',
+-          'join_extra' => [
+-                  [
+-                    'field' => 'deleted',
+-                    'value' => 0,
+-                    'numeric' => TRUE,
+-                  ],
+-          ],
++          'relationship field' => $relationship_field,
+         ];
++        // MongoDB does not need reverse relationships.
++        if ($driver != 'mongodb') {
++          // Provide a reverse relationship for the entity type that is referenced by
++          // the field.
++          $args['@entity'] = $entity_type->getLabel();
++          $args['@label'] = $target_entity_type->getSingularLabel();
++          $pseudo_field_name = 'reverse__' . $entity_type_id . '__' . $field_name;
++          $data[$target_base_table][$pseudo_field_name]['relationship'] = [
++            'title' => t('@entity using @field_name', $args),
++            'label' => t('@field_name', [
++              '@field_name' => $field_name,
++            ]),
++            'group' => $target_entity_type->getLabel(),
++            'help' => t('Relate each @entity with a @field_name set to the @label.', $args),
++            'id' => 'entity_reverse',
++            'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
++            'entity_type' => $entity_type_id,
++            'base field' => $entity_type->getKey('id'),
++            'field_name' => $field_name,
++            'field table' => $table_mapping->getDedicatedDataTableName($field_storage),
++            'field field' => $field_name . '_target_id',
++            'join_extra' => [
++              [
++                'field' => 'deleted',
++                'value' => 0,
++                'numeric' => TRUE,
++              ],
++            ],
++          ];
++        }
+       }
+       // Provide an argument plugin that has a meaningful titleQuery()
+       // implementation getting the entity label.
+diff --git a/core/modules/views/src/ManyToOneHelper.php b/core/modules/views/src/ManyToOneHelper.php
+index b1583b2a9f00500f6ae1cd1e2884843d5640c488..53274210260c8143b1c91817c9d16596bddf586f 100644
+--- a/core/modules/views/src/ManyToOneHelper.php
++++ b/core/modules/views/src/ManyToOneHelper.php
+@@ -65,7 +65,12 @@ public function getField() {
+       return $this->handler->getFormula();
+     }
+     else {
+-      return $this->handler->tableAlias . '.' . $this->handler->realField;
++      if (($this->handler->view->getDatabaseDriver() == 'mongodb') && ($this->handler->table == $this->handler->view->storage->get('base_table'))) {
++        return $this->handler->realField;
++      }
++      else {
++        return $this->handler->tableAlias . '.' . $this->handler->realField;
++      }
+     }
+   }
+ 
+@@ -330,18 +335,44 @@ public function addFilter() {
+           $placeholder .= '[]';
+ 
+           if ($operator == 'IS NULL') {
+-            $this->handler->query->addWhereExpression($options['group'], "$field $operator");
++            if ($this->handler->view->getDatabaseDriver() == 'mongodb') {
++              $condition = $this->handler->view->getDatabaseCondition('AND');
++              $condition->condition($field, $this->handler->value, 'NOT IN');
++              $this->handler->query->addCondition($options['group'], $condition);
++            }
++            else {
++              $this->handler->query->addWhereExpression($options['group'], "$field $operator");
++            }
+           }
+           else {
+-            $this->handler->query->addWhereExpression($options['group'], "$field $operator($placeholder)", [$placeholder => $value]);
++            if ($this->handler->view->getDatabaseDriver() == 'mongodb') {
++              $condition = $this->handler->view->getDatabaseCondition('AND');
++              $condition->condition($field, $this->handler->value, 'IN');
++              $this->handler->query->addCondition($options['group'], $condition);
++            }
++            else {
++              $this->handler->query->addWhereExpression($options['group'], "$field $operator($placeholder)", [$placeholder => $value]);
++            }
+           }
+         }
+         else {
+           if ($operator == 'IS NULL') {
+-            $this->handler->query->addWhereExpression($options['group'], "$field $operator");
++            if ($this->handler->view->getDatabaseDriver() == 'mongodb') {
++              $condition = $this->handler->view->getDatabaseCondition('AND');
++              $condition->condition($field, $this->handler->value, 'NOT IN');
++              $this->handler->query->addCondition($options['group'], $condition);
++            }
++            else {
++              $this->handler->query->addWhereExpression($options['group'], "$field $operator");
++            }
+           }
+           else {
+-            $this->handler->query->addWhereExpression($options['group'], "$field $operator $placeholder", [$placeholder => $value]);
++            if ($this->handler->view->getDatabaseDriver() == 'mongodb') {
++              $this->handler->query->addCondition($options['group'], $field, $value, $operator);
++            }
++            else {
++              $this->handler->query->addWhereExpression($options['group'], "$field $operator $placeholder", [$placeholder => $value]);
++            }
+           }
+         }
+       }
+@@ -351,11 +382,22 @@ public function addFilter() {
+       $field = $this->handler->realField;
+       $clause = $operator == 'or' ? $this->handler->query->getConnection()->condition('OR') : $this->handler->query->getConnection()->condition('AND');
+       foreach ($this->handler->tableAliases as $value => $alias) {
+-        $clause->condition("$alias.$field", $value);
++        if ($this->handler->view->getDatabaseDriver() == 'mongodb' && (in_array($alias, [NULL, $this->handler->table, $this->handler->tableAlias], TRUE))) {
++          $clause->condition($field, $value);
++        }
++        else {
++          $clause->condition("$alias.$field", $value);
++        }
+       }
+ 
+-      // Implode on either AND or OR.
+-      $this->handler->query->addWhere($options['group'], $clause);
++      if ($this->handler->view->getDatabaseDriver() == 'mongodb') {
++        $clause->useElementMatch(FALSE);
++        $this->handler->query->addCondition($options['group'], $clause);
++      }
++      else {
++        // Implode on either AND or OR.
++        $this->handler->query->addWhere($options['group'], $clause);
++      }
+     }
+   }
+ 
+diff --git a/core/modules/views/src/Plugin/Derivative/ViewsEntityRow.php b/core/modules/views/src/Plugin/Derivative/ViewsEntityRow.php
+index 757b574189b48bf2a73098d0fb3099e657a527f3..0b78129a34a9a6740df924c84eb1889d76ea58a1 100644
+--- a/core/modules/views/src/Plugin/Derivative/ViewsEntityRow.php
++++ b/core/modules/views/src/Plugin/Derivative/ViewsEntityRow.php
+@@ -2,6 +2,7 @@
+ 
+ namespace Drupal\views\Plugin\Derivative;
+ 
++use Drupal\Core\Database\Connection;
+ use Drupal\Core\Entity\EntityTypeManagerInterface;
+ use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+ use Drupal\Core\StringTranslation\StringTranslationTrait;
+@@ -47,6 +48,13 @@ class ViewsEntityRow implements ContainerDeriverInterface {
+    */
+   protected $viewsData;
+ 
++  /**
++   * The database connection.
++   *
++   * @var \Drupal\Core\Database\Connection
++   */
++  protected $connection;
++
+   /**
+    * Constructs a ViewsEntityRow object.
+    *
+@@ -56,11 +64,14 @@ class ViewsEntityRow implements ContainerDeriverInterface {
+    *   The entity type manager.
+    * @param \Drupal\views\ViewsData $views_data
+    *   The views data service.
++   * @param \Drupal\Core\Database\Connection $connection
++   *   The database connection.
+    */
+-  public function __construct($base_plugin_id, EntityTypeManagerInterface $entity_type_manager, ViewsData $views_data) {
++  public function __construct($base_plugin_id, EntityTypeManagerInterface $entity_type_manager, ViewsData $views_data, Connection $connection) {
+     $this->basePluginId = $base_plugin_id;
+     $this->entityTypeManager = $entity_type_manager;
+     $this->viewsData = $views_data;
++    $this->connection = $connection;
+   }
+ 
+   /**
+@@ -70,7 +81,8 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
+     return new static(
+       $base_plugin_id,
+       $container->get('entity_type.manager'),
+-      $container->get('views.views_data')
++      $container->get('views.views_data'),
++      $container->get('database')
+     );
+   }
+ 
+@@ -102,6 +114,9 @@ public function getDerivativeDefinitions($base_plugin_definition) {
+           'display_types' => ['normal'],
+           'class' => $base_plugin_definition['class'],
+         ];
++        if ($this->connection->driver() == 'mongodb') {
++          $this->derivatives[$entity_type_id]['base'] = [$entity_type->getBaseTable()];
++        }
+       }
+     }
+ 
+diff --git a/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php b/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php
+index ab4f399f72b0e0efce06f3a082d759ec41ee53ae..09c18178d7584f9f848bf64430546d959b4154fe 100644
+--- a/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php
++++ b/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php
+@@ -1022,7 +1022,18 @@ public function summaryName($data) {
+    */
+   public function query($group_by = FALSE) {
+     $this->ensureMyTable();
+-    $this->query->addWhere(0, "$this->tableAlias.$this->realField", $this->argument);
++    if ($this->view->getDatabaseDriver() == 'mongodb') {
++      if ($this->table == $this->view->storage->get('base_table')) {
++        $field = $this->realField;
++      }
++      else {
++        $field = "$this->tableAlias.$this->realField";
++      }
++      $this->query->addCondition(0, $field, $this->argument);
++    }
++    else {
++      $this->query->addWhere(0, "$this->tableAlias.$this->realField", $this->argument);
++    }
+   }
+ 
+   /**
+diff --git a/core/modules/views/src/Plugin/views/argument/Formula.php b/core/modules/views/src/Plugin/views/argument/Formula.php
+index 1bfc914d5ae960074c8df473177992cc073b08e3..434fc2d49a10f2897df6e7d5f87146615685b22e 100644
+--- a/core/modules/views/src/Plugin/views/argument/Formula.php
++++ b/core/modules/views/src/Plugin/views/argument/Formula.php
+@@ -43,12 +43,19 @@ public function getFormula() {
+    */
+   protected function summaryQuery() {
+     $this->ensureMyTable();
+-    // Now that our table is secure, get our formula.
+-    $formula = $this->getFormula();
+ 
+-    // Add the field.
+-    $this->base_alias = $this->name_alias = $this->query->addField(NULL, $formula, $this->field);
+-    $this->query->setCountField(NULL, $formula, $this->field);
++    if ($this->view->getDatabaseDriver() == 'mongodb') {
++      $this->query->addDateDateFormattedField($this->field, $this->realField, $this->getDateFormat($this->argFormat));
++      $this->base_alias = $this->name_alias = $this->query->addField(NULL, $this->realField, $this->field);
++    }
++    else {
++      // Now that our table is secure, get our formula.
++      $formula = $this->getFormula();
++
++      // Add the field.
++      $this->base_alias = $this->name_alias = $this->query->addField(NULL, $formula, $this->field);
++      $this->query->setCountField(NULL, $formula, $this->field);
++    }
+ 
+     return $this->summaryBasics(FALSE);
+   }
+@@ -58,13 +65,32 @@ protected function summaryQuery() {
+    */
+   public function query($group_by = FALSE) {
+     $this->ensureMyTable();
+-    // Now that our table is secure, get our formula.
+-    $placeholder = $this->placeholder();
+-    $formula = $this->getFormula() . ' = ' . $placeholder;
+-    $placeholders = [
+-      $placeholder => $this->argument,
+-    ];
+-    $this->query->addWhere(0, $formula, $placeholders, 'formula');
++
++    if ($this->view->getDatabaseDriver() == 'mongodb') {
++      if ($this->relationship) {
++        $field = "$this->tableAlias.$this->realField";
++      }
++      else {
++        $field = $this->realField;
++      }
++
++      $values = [
++        'format' => $this->getDateFormat($this->argFormat),
++        'value' => $this->argument,
++        'timezone' => $this->query->setupTimezone(),
++      ];
++
++      $this->query->addCondition(0, $field, $values, $this->mongodbOperator);
++    }
++    else {
++      // Now that our table is secure, get our formula.
++      $placeholder = $this->placeholder();
++      $formula = $this->getFormula() . ' = ' . $placeholder;
++      $placeholders = [
++        $placeholder => $this->argument,
++      ];
++      $this->query->addWhere(0, $formula, $placeholders, 'formula');
++    }
+   }
+ 
+ }
+diff --git a/core/modules/views/src/Plugin/views/argument/NumericArgument.php b/core/modules/views/src/Plugin/views/argument/NumericArgument.php
+index 39bb662ec7980fd3ec16989a8a86d4ebc8039fcc..d12a211169cd2eb728b3d92e92ee4bc1b9a2bb8c 100644
+--- a/core/modules/views/src/Plugin/views/argument/NumericArgument.php
++++ b/core/modules/views/src/Plugin/views/argument/NumericArgument.php
+@@ -104,14 +104,47 @@ public function query($group_by = FALSE) {
+     $placeholder = $this->placeholder();
+     $null_check = empty($this->options['not']) ? '' : " OR $this->tableAlias.$this->realField IS NULL";
+ 
++    if (($this->view->getDatabaseDriver() == 'mongodb') && ($this->table == $this->view->storage->get('base_table'))) {
++      $field = $this->realField;
++    }
++    else {
++      $field = "$this->tableAlias.$this->realField";
++    }
++
+     if (count($this->value) > 1) {
+-      $operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
+-      $placeholder .= '[]';
+-      $this->query->addWhereExpression(0, "$this->tableAlias.$this->realField $operator($placeholder)" . $null_check, [$placeholder => $this->value]);
++      if ($this->view->getDatabaseDriver() == 'mongodb') {
++        if (empty($this->options['not'])) {
++          $this->query->addCondition(0, $field, $this->value, 'IN');
++        }
++        else {
++          $or_condition = $this->view->getDatabaseCondition('OR');
++          $or_condition->condition($field, $this->value, 'NOT IN');
++          $or_condition->isNull($field);
++          $this->query->addCondition(0, $or_condition);
++        }
++      }
++      else {
++        $operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
++        $placeholder .= '[]';
++        $this->query->addWhereExpression(0, "$this->tableAlias.$this->realField $operator($placeholder)" . $null_check, [$placeholder => $this->value]);
++      }
+     }
+     else {
+-      $operator = empty($this->options['not']) ? '=' : '!=';
+-      $this->query->addWhereExpression(0, "$this->tableAlias.$this->realField $operator $placeholder" . $null_check, [$placeholder => $this->argument]);
++      if ($this->view->getDatabaseDriver() == 'mongodb') {
++        if (empty($this->options['not'])) {
++          $this->query->addCondition(0, $field, $this->argument, '=');
++        }
++        else {
++          $or_condition = $this->view->getDatabaseCondition('OR');
++          $or_condition->condition($field, $this->argument, '!=');
++          $or_condition->isNull($field);
++          $this->query->addCondition(0, $or_condition);
++        }
++      }
++      else {
++        $operator = empty($this->options['not']) ? '=' : '!=';
++        $this->query->addWhereExpression(0, "$this->tableAlias.$this->realField $operator $placeholder" . $null_check, [$placeholder => $this->argument]);
++      }
+     }
+   }
+ 
+diff --git a/core/modules/views/src/Plugin/views/argument/StringArgument.php b/core/modules/views/src/Plugin/views/argument/StringArgument.php
+index 15a68e0e608d43737407a7385f31f636a0e844de..bcc7b2859ea992d79dc3a05229758253adb19593 100644
+--- a/core/modules/views/src/Plugin/views/argument/StringArgument.php
++++ b/core/modules/views/src/Plugin/views/argument/StringArgument.php
+@@ -167,9 +167,16 @@ protected function summaryQuery() {
+     }
+     else {
+       // Add the field.
+-      $formula = $this->getFormula();
+-      $this->base_alias = $this->query->addField(NULL, $formula, $this->field . '_truncated');
+-      $this->query->setCountField(NULL, $formula, $this->field, $this->field . '_truncated');
++      if ($this->view->getDatabaseDriver() == 'mongodb') {
++        $this->base_alias = $this->field . '_truncated';
++        $this->query->addSubstringField($this->base_alias, $this->field, 0, intval($this->options['limit']));
++      }
++      else {
++        // Add the field.
++        $formula = $this->getFormula();
++        $this->base_alias = $this->query->addField(NULL, $formula, $this->field . '_truncated');
++        $this->query->setCountField(NULL, $formula, $this->field, $this->field . '_truncated');
++      }
+     }
+ 
+     $this->summaryNameField();
+@@ -238,7 +245,12 @@ public function query($group_by = FALSE) {
+     $this->ensureMyTable();
+     $formula = FALSE;
+     if (empty($this->options['glossary'])) {
+-      $field = "$this->tableAlias.$this->realField";
++      if (($this->view->getDatabaseDriver() == 'mongodb') && ($this->table == $this->view->storage->get('base_table'))) {
++        $field = $this->realField;
++      }
++      else {
++        $field = "$this->tableAlias.$this->realField";
++      }
+     }
+     else {
+       $formula = TRUE;
+@@ -264,10 +276,20 @@ public function query($group_by = FALSE) {
+       $placeholders = [
+         $placeholder => $argument,
+       ];
+-      $this->query->addWhereExpression(0, $field, $placeholders);
++      if ($this->view->getDatabaseDriver() == 'mongodb') {
++        $this->query->addSubstringField($this->realField . '_truncated', $field, 0, intval($this->options['limit']));
++      }
++      else {
++        $this->query->addWhereExpression(0, $field, $placeholders);
++      }
+     }
+     else {
+-      $this->query->addWhere(0, $field, $argument, $operator);
++      if ($this->view->getDatabaseDriver() == 'mongodb') {
++        $this->query->addCondition(0, $field, $argument, $operator);
++      }
++      else {
++        $this->query->addWhere(0, $field, $argument, $operator);
++      }
+     }
+   }
+ 
+diff --git a/core/modules/views/src/Plugin/views/display/EntityReference.php b/core/modules/views/src/Plugin/views/display/EntityReference.php
+index 177e478b19f9b7d8d2dcbc7b59a602f277ec12d9..5cc5209b3c0411fdc7d054156fa9aa7703c5d482 100644
+--- a/core/modules/views/src/Plugin/views/display/EntityReference.php
++++ b/core/modules/views/src/Plugin/views/display/EntityReference.php
+@@ -202,12 +202,14 @@ public function query() {
+         }
+       }
+ 
+-      $this->view->query->addWhere(0, $conditions);
++      $this->view->query->addCondition(0, $conditions);
+     }
+ 
+     // Add an IN condition for validation.
+     if (!empty($options['ids'])) {
+-      $this->view->query->addWhere(0, $id_table . '.' . $id_field, $options['ids'], 'IN');
++      $condition = $this->view->query->getConnection()->condition('AND');
++      $condition->condition($id_table . '.' . $id_field, $options['ids'], 'IN');
++      $this->view->query->addCondition(0, $condition);
+     }
+ 
+     $this->view->setItemsPerPage($options['limit']);
+diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+index 4b0e4d57fd446550923a03487cc9b28f9d373c26..4fbbaf90fafba59db3e15f18ffa5e1b0f01669a6 100644
+--- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
++++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+@@ -232,7 +232,24 @@ protected function addAdditionalFields($fields = NULL) {
+           $this->aliases[$identifier] = $this->query->addField($table_alias, $info['field'], NULL, $params);
+         }
+         else {
+-          $this->aliases[$info] = $this->query->addField($this->tableAlias, $info, NULL, $group_params);
++          if ($this->view->getDatabaseDriver() == 'mongodb') {
++            $real_field_last_part = '';
++            if (!empty($this->realField)) {
++              $real_field_parts = explode('.', $this->realField);
++              $real_field_last_part = end($real_field_parts);
++            }
++
++            if (!empty($real_field_last_part) && ($real_field_last_part == $info)) {
++              $alias = $this->tableAlias . '_' . str_replace('.', '_', $info);
++              $this->aliases[$info] = $this->query->addField($this->tableAlias, $this->realField, $alias, $group_params);
++            }
++            else {
++              $this->aliases[$info] = $this->query->addField($this->tableAlias, $info, NULL, $group_params);
++            }
++          }
++          else {
++            $this->aliases[$info] = $this->query->addField($this->tableAlias, $info, NULL, $group_params);
++          }
+         }
+       }
+     }
+diff --git a/core/modules/views/src/Plugin/views/filter/LatestRevision.php b/core/modules/views/src/Plugin/views/filter/LatestRevision.php
+index 90a6d202697f65687bf1d0b1ff69d71c33fb7797..e9013fa538952ea78835aeef925b7bac4697dd9a 100644
+--- a/core/modules/views/src/Plugin/views/filter/LatestRevision.php
++++ b/core/modules/views/src/Plugin/views/filter/LatestRevision.php
+@@ -94,7 +94,7 @@ public function query() {
+     $keys = $entity_type->getKeys();
+ 
+     $subquery = $query->getConnection()->select($query_base_table, 'base_table');
+-    $subquery->addExpression("MAX(base_table.{$keys['revision']})", $keys['revision']);
++    $subquery->addExpressionMax("base_table.{$keys['revision']}", $keys['revision']);
+     $subquery->groupBy("base_table.{$keys['id']}");
+     $query->addWhere($this->options['group'], "$query_base_table.{$keys['revision']}", $subquery, 'IN');
+   }
+diff --git a/core/modules/views/src/Plugin/views/filter/LatestTranslationAffectedRevision.php b/core/modules/views/src/Plugin/views/filter/LatestTranslationAffectedRevision.php
+index 9afc64e929ead7ff49cc304922cc511b47fcf23d..b6d6b330a8c05104d569af080412962cfefe0fcf 100644
+--- a/core/modules/views/src/Plugin/views/filter/LatestTranslationAffectedRevision.php
++++ b/core/modules/views/src/Plugin/views/filter/LatestTranslationAffectedRevision.php
+@@ -94,7 +94,7 @@ public function query() {
+     $keys = $entity_type->getKeys();
+ 
+     $subquery = $query->getConnection()->select($query_base_table, 'base_table');
+-    $subquery->addExpression("MAX(base_table.{$keys['revision']})", $keys['revision']);
++    $subquery->addExpressionMax("base_table.{$keys['revision']}", $keys['revision']);
+     $subquery->fields('base_table', [$keys['id'], 'langcode']);
+     $subquery->groupBy("base_table.{$keys['id']}");
+     $subquery->groupBy('base_table.langcode');
+diff --git a/core/modules/views/src/Plugin/views/HandlerBase.php b/core/modules/views/src/Plugin/views/HandlerBase.php
+index 7f40d5ec2a94ebe5c4bace1be898cfd7791e573c..fec84a04f7b62e222d4cda5426683d67b28911ff 100644
+--- a/core/modules/views/src/Plugin/views/HandlerBase.php
++++ b/core/modules/views/src/Plugin/views/HandlerBase.php
+@@ -797,7 +797,19 @@ public function getEntityType() {
+     if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
+       $relationship = $this->displayHandler->getOption('relationships')[$this->options['relationship']];
+       $table_data = $this->getViewsData()->get($relationship['table']);
+-      $views_data = $this->getViewsData()->get($table_data[$relationship['field']]['relationship']['base']);
++      if ($this->view->getDatabaseDriver() == 'mongodb') {
++        if (isset($table_data[$relationship['field']]['relationship']['base'])) {
++          $views_data = $this->getViewsData()->get($table_data[$relationship['field']]['relationship']['base']);
++        }
++        elseif (isset($relationship['relationship']) && ($relationship['relationship'] == 'none')) {
++          // Some relationships are removed, because in MongoDB's entity storage
++          // they live in the same document instead of in separate tables.
++          $views_data = $this->getViewsData()->get($this->view->storage->get('base_table'));
++        }
++      }
++      else {
++        $views_data = $this->getViewsData()->get($table_data[$relationship['field']]['relationship']['base']);
++      }
+     }
+     else {
+       $views_data = $this->getViewsData()->get($this->view->storage->get('base_table'));
+diff --git a/core/modules/views/src/Plugin/views/join/CastedIntFieldJoin.php b/core/modules/views/src/Plugin/views/join/CastedIntFieldJoin.php
+index c091222ae51545e230444addd2e9f8e6913d7980..39c9e6ddbe7ca740a8b4a6d6dc0a55bb38f3193b 100644
+--- a/core/modules/views/src/Plugin/views/join/CastedIntFieldJoin.php
++++ b/core/modules/views/src/Plugin/views/join/CastedIntFieldJoin.php
+@@ -49,7 +49,7 @@ public function buildJoin($select_query, $table, $view_query) {
+       $right_field = \Drupal::service('views.cast_sql')->getFieldAsInt($right_field);
+     }
+ 
+-    $condition = "$left_field {$this->configuration['operator']} $right_field";
++    $condition = $select_query->joinCondition()->where("$left_field {$this->configuration['operator']} $right_field");
+     $arguments = [];
+ 
+     // Tack on the extra.
+diff --git a/core/modules/views/src/Plugin/views/join/JoinPluginBase.php b/core/modules/views/src/Plugin/views/join/JoinPluginBase.php
+index f2b61f306c09eff445844413304b5a5d7622632d..506010fdb1b1d659eeed48f1aa3110d9cd47fa58 100644
+--- a/core/modules/views/src/Plugin/views/join/JoinPluginBase.php
++++ b/core/modules/views/src/Plugin/views/join/JoinPluginBase.php
+@@ -2,6 +2,7 @@
+ 
+ namespace Drupal\views\Plugin\views\join;
+ 
++use Drupal\Component\Assertion\Inspector;
+ use Drupal\Core\Database\Query\SelectInterface;
+ use Drupal\Core\Plugin\PluginBase;
+ 
+@@ -311,15 +312,20 @@ public function buildJoin($select_query, $table, $view_query) {
+       $left_table = NULL;
+     }
+ 
+-    $condition = "$left_field " . $this->configuration['operator'] . " $table[alias].$this->field";
+     $arguments = [];
++    if ($this->leftFormula || is_null($this->leftTable)) {
++      $condition = $select_query->joinCondition()->where("$left_field " . $this->configuration['operator'] . " $table[alias].$this->field");
++    }
++    else {
++      $condition = $select_query->joinCondition()->compare($left_field, "$table[alias].$this->field", $this->configuration['operator']);
++    }
+ 
+     // Tack on the extra.
+     if (isset($this->extra)) {
+       $this->joinAddExtra($arguments, $condition, $table, $select_query, $left_table);
+     }
+ 
+-    $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
++    $select_query->addJoin($this->type, $right_table, $table['alias'], $condition);
+   }
+ 
+   /**
+@@ -340,15 +346,40 @@ protected function joinAddExtra(&$arguments, &$condition, $table, SelectInterfac
+     if (is_array($this->extra)) {
+       $extras = [];
+       foreach ($this->extra as $info) {
+-        $extras[] = $this->buildExtra($info, $arguments, $table, $select_query, $left_table);
++        $extras[] = $this->buildExtra($info, $arguments, $table, $select_query, $left_table, is_string($condition));
+       }
+ 
+       if ($extras) {
+         if (count($extras) == 1) {
+-          $condition .= ' AND ' . array_shift($extras);
++          $extra = array_shift($extras);
++          if (is_string($extra)) {
++            $condition .= ' AND ' . $extra;
++          }
++          else {
++            if (isset($extra['field2'])) {
++              $condition->compare($extra['field'], $extra['field2'], $extra['operator']);
++            }
++            else {
++              $condition->condition($extra['field'], $extra['value'], $extra['operator']);
++            }
++          }
+         }
+         else {
+-          $condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
++          if (Inspector::assertAllStrings($extras)) {
++            $condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
++          }
++          else {
++            $inner_condition = $select_query->getConnection()->condition($this->extraOperator);
++            foreach ($extras as $extra) {
++              if (isset($extra['field2'])) {
++                $inner_condition->compare($extra['field'], $extra['field2'], $extra['operator']);
++              }
++              else {
++                $inner_condition->condition($extra['field'], $extra['value'], $extra['operator']);
++              }
++            }
++            $condition->condition($inner_condition);
++          }
+         }
+       }
+     }
+@@ -370,11 +401,13 @@ protected function joinAddExtra(&$arguments, &$condition, $table, SelectInterfac
+    *   The current select query being built.
+    * @param array $left
+    *   The left table.
++   * @param bool $condition_as_string
++   *   (optional) Return the condition as a string value.
+    *
+-   * @return string
++   * @return array|string
+    *   The extra condition
+    */
+-  protected function buildExtra($info, &$arguments, $table, SelectInterface $select_query, $left) {
++  protected function buildExtra($info, &$arguments, $table, SelectInterface $select_query, $left, $condition_as_string = FALSE) {
+     // Do not require 'value' to be set; allow for field syntax instead.
+     $info += [
+       'value' => NULL,
+@@ -414,26 +447,51 @@ protected function buildExtra($info, &$arguments, $table, SelectInterface $selec
+       $operator = !empty($info['operator']) ? $info['operator'] : '=';
+       $placeholder = $placeholder_sql = ':views_join_condition_' . $select_query->nextPlaceholder();
+     }
++
+     // Set 'field' as join table field if available or set 'left field' as
+     // join table field is not set.
+     if (isset($info['field'])) {
+       $join_table_field = "$join_table$info[field]";
+       // Allow the value to be set either with the 'value' element or
+-      // with 'left_field'.
++      // with 'left_field' or 'field2'.
+       if (isset($info['left_field'])) {
+-        $placeholder_sql = "$left[alias].$info[left_field]";
++        $field2 = $placeholder_sql = "$left[alias].$info[left_field]";
+       }
+-      else {
++      elseif ($condition_as_string) {
+         $arguments[$placeholder] = $info['value'];
+       }
++      if (isset($info['field2'])) {
++        if (isset($left['alias'])) {
++          $field2 = "$left[alias].$info[field2]";
++        }
++        else {
++          $field2 = "$info[field2]";
++        }
++      }
+     }
+     // Set 'left field' as join table field is not set.
+     else {
+       $join_table_field = "$left[alias].$info[left_field]";
+-      $arguments[$placeholder] = $info['value'];
+     }
+-    // Render out the SQL fragment with parameters.
+-    return "$join_table_field $operator $placeholder_sql";
++
++    if ($condition_as_string) {
++      // Render out the SQL fragment with parameters.
++      return "$join_table_field $operator $placeholder_sql";
++    }
++    elseif (isset($field2)) {
++      return [
++        'field' => $join_table_field,
++        'field2' => $field2,
++        'operator' => $operator,
++      ];
++    }
++    else {
++      return [
++        'field' => $join_table_field,
++        'value' => $info['value'],
++        'operator' => $operator,
++      ];
++    }
+   }
+ 
+ }
+diff --git a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
+index 446b636f37571b24b5bc225909432053f0444b73..60e2e5aceaffeb04ca0734bea5c8627b6f3d4af8 100644
+--- a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
++++ b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
+@@ -299,27 +299,29 @@ public function getEntityTableInfo() {
+ 
+     // Include all relationships.
+     foreach ((array) $this->view->relationship as $relationship_id => $relationship) {
+-      $table_data = $views_data->get($relationship->definition['base']);
+-      if (isset($table_data['table']['entity type'])) {
+-
+-        // If this is not one of the entity base tables, skip it.
+-        $entity_type = \Drupal::entityTypeManager()->getDefinition($table_data['table']['entity type']);
+-        $entity_base_tables = [$entity_type->getBaseTable(), $entity_type->getDataTable(), $entity_type->getRevisionTable(), $entity_type->getRevisionDataTable()];
+-        if (!in_array($relationship->definition['base'], $entity_base_tables)) {
+-          continue;
+-        }
+-
+-        $entity_tables[$relationship_id . '__' . $relationship->tableAlias] = [
+-          'base' => $relationship->definition['base'],
+-          'relationship_id' => $relationship_id,
+-          'alias' => $relationship->alias,
+-          'entity_type' => $table_data['table']['entity type'],
+-          'revision' => $table_data['table']['entity revision'],
+-        ];
+-
+-        // Include the entity provider.
+-        if (!empty($table_data['table']['provider'])) {
+-          $entity_tables[$relationship_id . '__' . $relationship->tableAlias]['provider'] = $table_data['table']['provider'];
++      if (isset($relationship->definition['base'])) {
++        $table_data = $views_data->get($relationship->definition['base']);
++        if (isset($table_data['table']['entity type'])) {
++
++          // If this is not one of the entity base tables, skip it.
++          $entity_type = \Drupal::entityTypeManager()->getDefinition($table_data['table']['entity type']);
++          $entity_base_tables = [$entity_type->getBaseTable(), $entity_type->getDataTable(), $entity_type->getRevisionTable(), $entity_type->getRevisionDataTable()];
++          if (!in_array($relationship->definition['base'], $entity_base_tables)) {
++            continue;
++          }
++
++          $entity_tables[$relationship_id . '__' . $relationship->tableAlias] = [
++            'base' => $relationship->definition['base'],
++            'relationship_id' => $relationship_id,
++            'alias' => $relationship->alias,
++            'entity_type' => $table_data['table']['entity type'],
++            'revision' => $table_data['table']['entity revision'],
++          ];
++
++          // Include the entity provider.
++          if (!empty($table_data['table']['provider'])) {
++            $entity_tables[$relationship_id . '__' . $relationship->tableAlias]['provider'] = $table_data['table']['provider'];
++          }
+         }
+       }
+     }
+diff --git a/core/modules/views/src/Plugin/views/row/EntityRow.php b/core/modules/views/src/Plugin/views/row/EntityRow.php
+index eba9d3fc0a39254f76fdddf6d105e5a1b118b335..3e00464df6ae256a76799a0b4703463f27c01d59 100644
+--- a/core/modules/views/src/Plugin/views/row/EntityRow.php
++++ b/core/modules/views/src/Plugin/views/row/EntityRow.php
+@@ -109,7 +109,12 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$
+ 
+     $this->entityTypeId = $this->definition['entity_type'];
+     $this->entityType = $this->entityTypeManager->getDefinition($this->entityTypeId);
+-    $this->base_table = $this->entityType->getDataTable() ?: $this->entityType->getBaseTable();
++    if (($view->getDatabaseDriver() != 'mongodb') && $this->entityType->getDataTable()) {
++      $this->base_table = $this->entityType->getDataTable();
++    }
++    else {
++      $this->base_table = $this->entityType->getBaseTable();
++    }
+     $this->base_field = $this->entityType->getKey('id');
+   }
+ 
+diff --git a/core/modules/views/src/Plugin/views/sort/Date.php b/core/modules/views/src/Plugin/views/sort/Date.php
+index 7c2719c6316febd75ccda15a2985ae36b21e6a17..34db1b8b02227ae6290d77d717409df1cca8309f 100644
+--- a/core/modules/views/src/Plugin/views/sort/Date.php
++++ b/core/modules/views/src/Plugin/views/sort/Date.php
+@@ -74,7 +74,20 @@ public function query() {
+     }
+ 
+     // Add the field.
+-    $this->query->addOrderBy(NULL, $formula, $this->options['order'], $this->tableAlias . '_' . $this->field . '_' . $this->options['granularity']);
++    if ($this->view->getDatabaseDriver() == 'mongodb') {
++      $placeholder = $this->placeholder();
++      if ($this->getPluginId() == 'datetime') {
++        $this->query->addDateStringFormattedField($placeholder, $this->realField, $formula);
++      }
++      else {
++        $this->query->addDateDateFormattedField($placeholder, $this->realField, $formula);
++      }
++      $this->query->addOrderBy($this->tableAlias, $placeholder, $this->options['order']);
++    }
++    else {
++      // Add the field.
++      $this->query->addOrderBy(NULL, $formula, $this->options['order'], $this->tableAlias . '_' . $this->field . '_' . $this->options['granularity']);
++    }
+   }
+ 
+ }
+diff --git a/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
+index f625dc6cb8709b08c10036c707a4036c991e3f4e..b4604428b7d1591176fc236a75063e67684141e2 100644
+--- a/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
++++ b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
+@@ -3,6 +3,7 @@
+ namespace Drupal\views\Plugin\views\wizard;
+ 
+ use Drupal\Component\Utility\NestedArray;
++use Drupal\Core\Database\Connection;
+ use Drupal\Core\Entity\EntityPublishedInterface;
+ use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+ use Drupal\Core\Form\FormStateInterface;
+@@ -127,6 +128,13 @@ abstract class WizardPluginBase extends PluginBase implements WizardInterface {
+    */
+   protected $parentFormSelector;
+ 
++  /**
++   * The database connection.
++   *
++   * @var \Drupal\Core\Database\Connection
++   */
++  protected $connection;
++
+   /**
+    * {@inheritdoc}
+    */
+@@ -136,18 +144,20 @@ public static function create(ContainerInterface $container, array $configuratio
+       $plugin_id,
+       $plugin_definition,
+       $container->get('entity_type.bundle.info'),
+-      $container->get('menu.parent_form_selector')
++      $container->get('menu.parent_form_selector'),
++      $container->get('database')
+     );
+   }
+ 
+   /**
+    * Constructs a WizardPluginBase object.
+    */
+-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, MenuParentFormSelectorInterface $parent_form_selector) {
++  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, MenuParentFormSelectorInterface $parent_form_selector, Connection $connection) {
+     parent::__construct($configuration, $plugin_id, $plugin_definition);
+ 
+     $this->bundleInfoService = $bundle_info_service;
+     $this->base_table = $this->definition['base_table'];
++    $this->connection = $connection;
+ 
+     $this->parentFormSelector = $parent_form_selector;
+ 
+@@ -156,6 +166,9 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
+       if (in_array($this->base_table, [$entity_type->getBaseTable(), $entity_type->getDataTable(), $entity_type->getRevisionTable(), $entity_type->getRevisionDataTable()], TRUE)) {
+         $this->entityType = $entity_type;
+         $this->entityTypeId = $entity_type_id;
++        if ($this->connection->driver() == 'mongodb') {
++          $this->base_table = $entity_type->getBaseTable();
++        }
+       }
+     }
+   }
+diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php
+index 87739a90c96c9381a6b6936abafdcb39336c9e95..ae906c6a732dbdb91d41b92233bf827cbd623f9f 100644
+--- a/core/modules/views/src/ViewExecutable.php
++++ b/core/modules/views/src/ViewExecutable.php
+@@ -494,6 +494,13 @@ class ViewExecutable {
+    */
+   protected $serializationData;
+ 
++  /**
++   * The database connection. Needed for MongoDB.
++   *
++   * @var \Drupal\Core\Database\Connection|false
++   */
++  protected $database;
++
+   /**
+    * Constructs a new ViewExecutable object.
+    *
+@@ -522,6 +529,37 @@ public function __construct(ViewEntityInterface $storage, AccountInterface $user
+ 
+   }
+ 
++  /**
++   * Returns the database driver. Needed for MongoDB.
++   *
++   * @return string
++   *   The database driver.
++   */
++  public function getDatabaseDriver() {
++    if (empty($this->database)) {
++      $this->database = \Drupal::service('database');
++    }
++
++    return $this->database->driver();
++  }
++
++  /**
++   * Returns a new database condition object.
++   *
++   * @param string $conjunction
++   *   The operator to use to combine conditions: 'AND' or 'OR'.
++   *
++   * @return Drupal\Core\Database\Query\Condition
++   *   A new database condition object.
++   */
++  public function getDatabaseCondition($conjunction) {
++    if (empty($this->database)) {
++      $this->database = \Drupal::service('database');
++    }
++
++    return $this->database->condition($conjunction);
++  }
++
+   /**
+    * Returns the identifier.
+    *
+@@ -2137,6 +2175,8 @@ public function destroy() {
+     foreach ($defaults as $property => $default) {
+       $this->{$property} = $default;
+     }
++
++    $this->database = NULL;
+   }
+ 
+   /**
+@@ -2537,6 +2577,8 @@ public function getDependencies() {
+    *   The names of all variables that should be serialized.
+    */
+   public function __sleep(): array {
++    $this->database = NULL;
++
+     // Limit to only the required data which is needed to properly restore the
+     // state during unserialization.
+     $this->serializationData = [
+diff --git a/core/modules/views/views.module b/core/modules/views/views.module
+index 964b1657c61372b713eb546a4d8bae630cada9d1..0e2c91bc9e439b123b8881ae9b0ec64a034b63fd 100644
+--- a/core/modules/views/views.module
++++ b/core/modules/views/views.module
+@@ -5,6 +5,7 @@
+  */
+ 
+ use Drupal\Core\Database\Query\AlterableInterface;
++use Drupal\Core\Database\Query\SelectInterface;
+ use Drupal\Core\Form\FormStateInterface;
+ use Drupal\views\ViewExecutable;
+ use Drupal\views\Entity\View;
+@@ -339,13 +340,16 @@ function _views_query_tag_alter_condition(AlterableInterface $query, &$condition
+       if (is_string($condition['field'])) {
+         $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
+       }
+-      elseif (is_object($condition['field'])) {
++      elseif (is_object($condition['field']) && ($condition['value'] instanceof SelectInterface)) {
+         $sub_conditions = &$condition['field']->conditions();
+         _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
+       }
++      if (isset($condition['field2']) && is_string($condition['field2'])) {
++        $condition['field2'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field2']);
++      }
+       // $condition['value'] is a subquery so alter the subquery recursive.
+       // Therefore make sure to get the metadata of the main query.
+-      if (is_object($condition['value'])) {
++      if (isset($condition['value']) && is_object($condition['value']) && ($condition['value'] instanceof SelectInterface)) {
+         $subquery = $condition['value'];
+         $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
+         \Drupal::moduleHandler()->invoke('views', 'query_views_alter', [$condition['value']]);
+diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc
+index 3a08f62c900949377212658a26a4775357601f16..842d737409ce86e1cfaa20bc081e24ab5c5a2dcd 100644
+--- a/core/modules/views/views.views.inc
++++ b/core/modules/views/views.views.inc
+@@ -99,6 +99,8 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+     return $data;
+   }
+ 
++  $driver = \Drupal::database()->driver();
++
+   $field_name = $field_storage->getName();
+   $field_columns = $field_storage->getColumns();
+ 
+@@ -111,19 +113,24 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+     // We cannot do anything if for some reason there is no base table.
+     return $data;
+   }
+-  $entity_tables = [$base_table => $entity_type_id];
+-  // Some entities may not have a data table.
+-  $data_table = $entity_type->getDataTable();
+-  if ($data_table) {
+-    $entity_tables[$data_table] = $entity_type_id;
++  if ($driver == 'mongodb') {
++    $entity_storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
+   }
+-  $entity_revision_table = $entity_type->getRevisionTable();
+-  $supports_revisions = $entity_type->hasKey('revision') && $entity_revision_table;
+-  if ($supports_revisions) {
+-    $entity_tables[$entity_revision_table] = $entity_type_id;
+-    $entity_revision_data_table = $entity_type->getRevisionDataTable();
+-    if ($entity_revision_data_table) {
+-      $entity_tables[$entity_revision_data_table] = $entity_type_id;
++  else {
++    $entity_tables = [$base_table => $entity_type_id];
++    // Some entities may not have a data table.
++    $data_table = $entity_type->getDataTable();
++    if ($data_table) {
++      $entity_tables[$data_table] = $entity_type_id;
++    }
++    $entity_revision_table = $entity_type->getRevisionTable();
++    $supports_revisions = $entity_type->hasKey('revision') && $entity_revision_table;
++    if ($supports_revisions) {
++      $entity_tables[$entity_revision_table] = $entity_type_id;
++      $entity_revision_data_table = $entity_type->getRevisionDataTable();
++      if ($entity_revision_data_table) {
++        $entity_tables[$entity_revision_data_table] = $entity_type_id;
++      }
+     }
+   }
+ 
+@@ -131,18 +138,28 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+   // @todo Generalize this code to make it work with any table layout. See
+   //   https://www.drupal.org/node/2079019.
+   $table_mapping = $storage->getTableMapping();
+-  $field_tables = [
+-    EntityStorageInterface::FIELD_LOAD_CURRENT => [
+-      'table' => $table_mapping->getDedicatedDataTableName($field_storage),
+-      'alias' => "{$entity_type_id}__{$field_name}",
+-    ],
+-  ];
+-  if ($supports_revisions) {
+-    $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION] = [
+-      'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
+-      'alias' => "{$entity_type_id}_revision__{$field_name}",
++  if ($driver == 'mongodb') {
++    $field_tables = [
++      EntityStorageInterface::FIELD_LOAD_CURRENT => [
++        'table' => $table_mapping->getJsonStorageDedicatedTableName($field_storage, $base_table),
++        'alias' => "{$entity_type_id}__{$field_name}",
++      ],
+     ];
+   }
++  else {
++    $field_tables = [
++      EntityStorageInterface::FIELD_LOAD_CURRENT => [
++        'table' => $table_mapping->getDedicatedDataTableName($field_storage),
++        'alias' => "{$entity_type_id}__{$field_name}",
++      ],
++    ];
++    if ($supports_revisions) {
++      $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION] = [
++        'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
++        'alias' => "{$entity_type_id}_revision__{$field_name}",
++      ];
++    }
++  }
+ 
+   // Determine if the fields are translatable.
+   $bundles_names = $field_storage->getBundles();
+@@ -191,57 +208,15 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+     $translation_join_type = 'language_bundle';
+   }
+ 
+-  // Build the relationships between the field table and the entity tables.
+-  $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias'];
+-  if ($data_table) {
+-    // Tell Views how to join to the base table, via the data table.
+-    $data[$table_alias]['table']['join'][$data_table] = [
+-      'table' => $table_mapping->getDedicatedDataTableName($field_storage),
+-      'left_field' => $entity_type->getKey('id'),
+-      'field' => 'entity_id',
+-      'extra' => [
+-        ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
+-      ],
+-    ];
+-  }
+-  else {
+-    // If there is no data table, just join directly.
+-    $data[$table_alias]['table']['join'][$base_table] = [
+-      'table' => $table_mapping->getDedicatedDataTableName($field_storage),
+-      'left_field' => $entity_type->getKey('id'),
+-      'field' => 'entity_id',
+-      'extra' => [
+-        ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
+-      ],
+-    ];
+-  }
+-
+-  if ($translation_join_type === 'language_bundle') {
+-    $data[$table_alias]['table']['join'][$data_table]['join_id'] = 'field_or_language_join';
+-    $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+-      'left_field' => 'langcode',
+-      'field' => 'langcode',
+-    ];
+-    $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+-      'field' => 'bundle',
+-      'value' => $untranslatable_config_bundles,
+-    ];
+-  }
+-  elseif ($translation_join_type === 'language') {
+-    $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+-      'left_field' => 'langcode',
+-      'field' => 'langcode',
+-    ];
+-  }
+-
+-  if ($supports_revisions) {
+-    $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION]['alias'];
+-    if ($entity_revision_data_table) {
+-      // Tell Views how to join to the revision table, via the data table.
+-      $data[$table_alias]['table']['join'][$entity_revision_data_table] = [
+-        'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
+-        'left_field' => $entity_type->getKey('revision'),
+-        'field' => 'revision_id',
++  if ($driver != 'mongodb') {
++    // Build the relationships between the field table and the entity tables.
++    $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias'];
++    if ($data_table) {
++      // Tell Views how to join to the base table, via the data table.
++      $data[$table_alias]['table']['join'][$data_table] = [
++        'table' => $table_mapping->getDedicatedDataTableName($field_storage),
++        'left_field' => $entity_type->getKey('id'),
++        'field' => 'entity_id',
+         'extra' => [
+           ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
+         ],
+@@ -249,32 +224,76 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+     }
+     else {
+       // If there is no data table, just join directly.
+-      $data[$table_alias]['table']['join'][$entity_revision_table] = [
+-        'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
+-        'left_field' => $entity_type->getKey('revision'),
+-        'field' => 'revision_id',
++      $data[$table_alias]['table']['join'][$base_table] = [
++        'table' => $table_mapping->getDedicatedDataTableName($field_storage),
++        'left_field' => $entity_type->getKey('id'),
++        'field' => 'entity_id',
+         'extra' => [
+           ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
+         ],
+       ];
+     }
++
+     if ($translation_join_type === 'language_bundle') {
+-      $data[$table_alias]['table']['join'][$entity_revision_data_table]['join_id'] = 'field_or_language_join';
+-      $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
++      $data[$table_alias]['table']['join'][$data_table]['join_id'] = 'field_or_language_join';
++      $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+         'left_field' => 'langcode',
+         'field' => 'langcode',
+       ];
+-      $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
+-        'value' => $untranslatable_config_bundles,
++      $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+         'field' => 'bundle',
++        'value' => $untranslatable_config_bundles,
+       ];
+     }
+     elseif ($translation_join_type === 'language') {
+-      $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
++      $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+         'left_field' => 'langcode',
+         'field' => 'langcode',
+       ];
+     }
++
++    if ($supports_revisions) {
++      $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION]['alias'];
++      if ($entity_revision_data_table) {
++        // Tell Views how to join to the revision table, via the data table.
++        $data[$table_alias]['table']['join'][$entity_revision_data_table] = [
++          'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
++          'left_field' => $entity_type->getKey('revision'),
++          'field' => 'revision_id',
++          'extra' => [
++            ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
++          ],
++        ];
++      }
++      else {
++        // If there is no data table, just join directly.
++        $data[$table_alias]['table']['join'][$entity_revision_table] = [
++          'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
++          'left_field' => $entity_type->getKey('revision'),
++          'field' => 'revision_id',
++          'extra' => [
++            ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
++          ],
++        ];
++      }
++      if ($translation_join_type === 'language_bundle') {
++        $data[$table_alias]['table']['join'][$entity_revision_data_table]['join_id'] = 'field_or_language_join';
++        $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
++          'left_field' => 'langcode',
++          'field' => 'langcode',
++        ];
++        $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
++          'value' => $untranslatable_config_bundles,
++          'field' => 'bundle',
++        ];
++      }
++      elseif ($translation_join_type === 'language') {
++        $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
++          'left_field' => 'langcode',
++          'field' => 'langcode',
++        ];
++      }
++    }
+   }
+ 
+   $group_name = $entity_type->getLabel();
+@@ -295,50 +314,81 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+     $table = $table_info['table'];
+     $table_alias = $table_info['alias'];
+ 
+-    if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
++    if ($driver === 'mongodb') {
+       $group = $group_name;
+       $field_alias = $field_name;
++
++      $data[$base_table][$field_alias] = [
++        'group' => $group,
++        'title' => $label,
++        'title short' => $label,
++        'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
++      ];
+     }
+     else {
+-      $group = t('@group (historical data)', ['@group' => $group_name]);
+-      $field_alias = $field_name . '__revision_id';
+-    }
++      if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
++        $group = $group_name;
++        $field_alias = $field_name;
++      }
++      else {
++        $group = t('@group (historical data)', ['@group' => $group_name]);
++        $field_alias = $field_name . '__revision_id';
++      }
+ 
+-    $data[$table_alias][$field_alias] = [
+-      'group' => $group,
+-      'title' => $label,
+-      'title short' => $label,
+-      'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
+-    ];
++      $data[$table_alias][$field_alias] = [
++        'group' => $group,
++        'title' => $label,
++        'title short' => $label,
++        'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
++      ];
++    }
+ 
+     // Go through and create a list of aliases for all possible combinations of
+     // entity type + name.
+     $aliases = [];
+     $also_known = [];
+     foreach ($all_labels as $label_name => $true) {
+-      if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
++      if ($driver === 'mongodb') {
+         if ($label != $label_name) {
+           $aliases[] = [
+             'base' => $base_table,
+-            'group' => $group_name,
++            'group' => t('@group (historical data)', ['@group' => $group_name]),
+             'title' => $label_name,
+             'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
+           ];
+           $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $label_name]);
+         }
+       }
+-      elseif ($supports_revisions && $label != $label_name) {
+-        $aliases[] = [
+-          'base' => $table,
+-          'group' => t('@group (historical data)', ['@group' => $group_name]),
+-          'title' => $label_name,
+-          'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
+-        ];
+-        $also_known[] = t('@group (historical data): @field', ['@group' => $group_name, '@field' => $label_name]);
++      else {
++        if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
++          if ($label != $label_name) {
++            $aliases[] = [
++              'base' => $base_table,
++              'group' => $group_name,
++              'title' => $label_name,
++              'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
++            ];
++            $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $label_name]);
++          }
++        }
++        elseif ($supports_revisions && $label != $label_name) {
++          $aliases[] = [
++            'base' => $table,
++            'group' => t('@group (historical data)', ['@group' => $group_name]),
++            'title' => $label_name,
++            'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
++          ];
++          $also_known[] = t('@group (historical data): @field', ['@group' => $group_name, '@field' => $label_name]);
++        }
+       }
+     }
+     if ($aliases) {
+-      $data[$table_alias][$field_alias]['aliases'] = $aliases;
++      if ($driver === 'mongodb') {
++        $data[$base_table][$field_alias]['aliases'] = $aliases;
++      }
++      else {
++        $data[$table_alias][$field_alias]['aliases'] = $aliases;
++      }
+       // The $also_known variable contains markup that is HTML escaped and that
+       // loses safeness when imploded. The help text is used in #description
+       // and therefore XSS admin filtered by default. Escaped HTML is not
+@@ -348,23 +398,56 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+       // Considering the dual use of this help data (both as metadata and as
+       // help text), other patterns such as use of #markup would not be correct
+       // here.
+-      $data[$table_alias][$field_alias]['help'] = Markup::create($data[$table_alias][$field_alias]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
++      if ($driver === 'mongodb') {
++        $data[$base_table][$field_alias]['help'] = Markup::create($data[$base_table][$field_alias]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
++      }
++      else {
++        $data[$table_alias][$field_alias]['help'] = Markup::create($data[$table_alias][$field_alias]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
++      }
+     }
+ 
+     $keys = array_keys($field_columns);
+     $real_field = reset($keys);
+-    $data[$table_alias][$field_alias]['field'] = [
+-      'table' => $table,
+-      'id' => 'field',
+-      'field_name' => $field_name,
+-      'entity_type' => $entity_type_id,
+-      // Provide a real field for group by.
+-      'real field' => $field_name . '_' . $real_field,
+-      'additional fields' => $add_fields,
+-      // Default the element type to div, let the UI change it if necessary.
+-      'element type' => 'div',
+-      'is revision' => $type == EntityStorageInterface::FIELD_LOAD_REVISION,
+-    ];
++    if ($driver == 'mongodb') {
++      $real_field = $field_alias . '_' . $real_field;
++      if ($entity_type->isRevisionable()) {
++        $current_revision_table = $entity_storage->getJsonStorageCurrentRevisionTable();
++        $real_field = $current_revision_table . '.' . $table_mapping->getJsonStorageDedicatedTableName($field_storage, $current_revision_table) . '.' . $real_field;
++      }
++      elseif ($entity_type->isTranslatable()) {
++        $translations_table = $entity_storage->getJsonStorageTranslationsTable();
++        $real_field = $translations_table . '.' . $table_mapping->getJsonStorageDedicatedTableName($field_storage, $translations_table) . '.' . $real_field;
++      }
++      else {
++        $real_field = $table_mapping->getJsonStorageDedicatedTableName($field_storage, $base_table) . '.' . $real_field;
++      }
++
++      $data[$base_table][$field_alias]['field'] = [
++        'id' => 'field',
++        'field_name' => $field_alias,
++        'entity field' => $field_alias,
++        // Provide a real field for group by.
++        // Testing to see if we can remove the real field here.
++        'real field' => $real_field,
++        'additional fields' => $add_fields,
++        // Default the element type to div, let the UI change it if necessary.
++        'element type' => 'div',
++      ];
++    }
++    else {
++      $data[$table_alias][$field_alias]['field'] = [
++        'table' => $table,
++        'id' => 'field',
++        'field_name' => $field_name,
++        'entity_type' => $entity_type_id,
++        // Provide a real field for group by.
++        'real field' => $field_name . '_' . $real_field,
++        'additional fields' => $add_fields,
++        // Default the element type to div, let the UI change it if necessary.
++        'element type' => 'div',
++        'is revision' => $type == EntityStorageInterface::FIELD_LOAD_REVISION,
++      ];
++    }
+   }
+ 
+   // Expose data for each field property individually.
+@@ -391,11 +474,20 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+       case 'blob':
+         // It does not make sense to sort by blob.
+         $allow_sort = FALSE;
++      case 'bool':
+       default:
+-        $filter = 'string';
+-        $argument = 'string';
+-        $sort = 'standard';
+-        break;
++        if (\Drupal::database()->driver() == 'mongodb' && $attributes['type'] == 'bool') {
++          $filter = 'boolean';
++          $argument = 'numeric';
++          $sort = 'standard';
++          break;
++        }
++        else {
++          $filter = 'string';
++          $argument = 'string';
++          $sort = 'standard';
++          break;
++        }
+     }
+ 
+     if (count($field_columns) == 1 || $column == 'value') {
+@@ -409,26 +501,56 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+ 
+     // Expose data for the property.
+     foreach ($field_tables as $type => $table_info) {
+-      $table = $table_info['table'];
+-      $table_alias = $table_info['alias'];
+-
+-      if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
++      if ($driver === 'mongodb') {
+         $group = $group_name;
++        $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column);
++
++        // Load all the fields from the table by default.
++        $additional_fields = $table_mapping->getAllColumns($base_table);
++
++        if ($entity_type->isRevisionable()) {
++          $current_revision_table = $entity_storage->getJsonStorageCurrentRevisionTable();
++          $real_field = $current_revision_table . '.' . $table_mapping->getJsonStorageDedicatedTableName($field_storage, $current_revision_table) . '.' . $column_real_name;
++        }
++        elseif ($entity_type->isTranslatable()) {
++          $translations_table = $entity_storage->getJsonStorageTranslationsTable();
++          $real_field = $translations_table . '.' . $table_mapping->getJsonStorageDedicatedTableName($field_storage, $translations_table) . '.' . $column_real_name;
++        }
++        else {
++          $real_field = $table_mapping->getJsonStorageDedicatedTableName($field_storage, $base_table) . '.' . $column_real_name;
++        }
++
++        $data[$base_table][$column_real_name] = [
++          'field' => ['id' => 'field'],
++          'real field' => $real_field,
++          'group' => $group,
++          'title' => $title,
++          'title short' => $title_short,
++          'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
++        ];
+       }
+       else {
+-        $group = t('@group (historical data)', ['@group' => $group_name]);
+-      }
+-      $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column);
++        $table = $table_info['table'];
++        $table_alias = $table_info['alias'];
+ 
+-      // Load all the fields from the table by default.
+-      $additional_fields = $table_mapping->getAllColumns($table);
++        if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
++          $group = $group_name;
++        }
++        else {
++          $group = t('@group (historical data)', ['@group' => $group_name]);
++        }
++        $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column);
+ 
+-      $data[$table_alias][$column_real_name] = [
+-        'group' => $group,
+-        'title' => $title,
+-        'title short' => $title_short,
+-        'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
+-      ];
++        // Load all the fields from the table by default.
++        $additional_fields = $table_mapping->getAllColumns($table);
++
++        $data[$table_alias][$column_real_name] = [
++          'group' => $group,
++          'title' => $title,
++          'title short' => $title_short,
++          'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
++        ];
++      }
+ 
+       // Go through and create a list of aliases for all possible combinations of
+       // entity type + name.
+@@ -451,7 +573,12 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+         }
+       }
+       if ($aliases) {
+-        $data[$table_alias][$column_real_name]['aliases'] = $aliases;
++        if ($driver === 'mongodb') {
++          $data[$base_table][$column_real_name]['aliases'] = $aliases;
++        }
++        else {
++          $data[$table_alias][$column_real_name]['aliases'] = $aliases;
++        }
+         // The $also_known variable contains markup that is HTML escaped and
+         // that loses safeness when imploded. The help text is used in
+         // #description and therefore XSS admin filtered by default. Escaped
+@@ -461,83 +588,112 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
+         // Considering the dual use of this help data (both as metadata and as
+         // help text), other patterns such as use of #markup would not be
+         // correct here.
+-        $data[$table_alias][$column_real_name]['help'] = Markup::create($data[$table_alias][$column_real_name]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
++        if ($driver === 'mongodb') {
++          $data[$base_table][$column_real_name]['help'] = Markup::create($data[$base_table][$column_real_name]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
++        }
++        else {
++          $data[$table_alias][$column_real_name]['help'] = Markup::create($data[$table_alias][$column_real_name]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
++        }
+       }
+ 
+-      $data[$table_alias][$column_real_name]['argument'] = [
+-        'field' => $column_real_name,
+-        'table' => $table,
+-        'id' => $argument,
+-        'additional fields' => $additional_fields,
+-        'field_name' => $field_name,
+-        'entity_type' => $entity_type_id,
+-        'empty field name' => t('- No value -'),
+-      ];
+-      $data[$table_alias][$column_real_name]['filter'] = [
+-        'field' => $column_real_name,
+-        'table' => $table,
+-        'id' => $filter,
+-        'additional fields' => $additional_fields,
+-        'field_name' => $field_name,
+-        'entity_type' => $entity_type_id,
+-        'allow empty' => TRUE,
+-      ];
+-      if (!empty($allow_sort)) {
+-        $data[$table_alias][$column_real_name]['sort'] = [
+-          'field' => $column_real_name,
+-          'table' => $table,
+-          'id' => $sort,
+-          'additional fields' => $additional_fields,
++      if ($driver == 'mongodb') {
++        $data[$base_table][$column_real_name]['argument'] = [
++          'id' => $argument,
+           'field_name' => $field_name,
+-          'entity_type' => $entity_type_id,
+         ];
+-      }
++        $data[$base_table][$column_real_name]['filter'] = [
++          'id' => $filter,
++          'field_name' => $field_name,
++          'allow empty' => TRUE,
++        ];
++        if (!empty($allow_sort)) {
++          $data[$base_table][$column_real_name]['sort'] = [
++            'id' => $sort,
++            'field_name' => $field_name,
++          ];
++        }
+ 
+-      // Set click sortable if there is a field definition.
+-      if (isset($data[$table_alias][$field_name]['field'])) {
+-        $data[$table_alias][$field_name]['field']['click sortable'] = $allow_sort;
++        // Set click sortable if there is a field definition.
++        if (isset($data[$base_table][$field_name]['field'])) {
++          $data[$base_table][$field_name]['field']['click sortable'] = $allow_sort;
++        }
+       }
+-
+-      // Expose additional delta column for multiple value fields.
+-      if ($field_storage->isMultiple()) {
+-        $title_delta = t('@label (@name:delta)', ['@label' => $label, '@name' => $field_name]);
+-        $title_short_delta = t('@label:delta', ['@label' => $label]);
+-
+-        $data[$table_alias]['delta'] = [
+-          'group' => $group,
+-          'title' => $title_delta,
+-          'title short' => $title_short_delta,
+-          'help' => t('Delta - Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
+-        ];
+-        $data[$table_alias]['delta']['field'] = [
+-          'id' => 'numeric',
+-        ];
+-        $data[$table_alias]['delta']['argument'] = [
+-          'field' => 'delta',
++      else {
++        $data[$table_alias][$column_real_name]['argument'] = [
++          'field' => $column_real_name,
+           'table' => $table,
+-          'id' => 'numeric',
++          'id' => $argument,
+           'additional fields' => $additional_fields,
+-          'empty field name' => t('- No value -'),
+           'field_name' => $field_name,
+           'entity_type' => $entity_type_id,
++          'empty field name' => t('- No value -'),
+         ];
+-        $data[$table_alias]['delta']['filter'] = [
+-          'field' => 'delta',
++        $data[$table_alias][$column_real_name]['filter'] = [
++          'field' => $column_real_name,
+           'table' => $table,
+-          'id' => 'numeric',
++          'id' => $filter,
+           'additional fields' => $additional_fields,
+           'field_name' => $field_name,
+           'entity_type' => $entity_type_id,
+           'allow empty' => TRUE,
+         ];
+-        $data[$table_alias]['delta']['sort'] = [
+-          'field' => 'delta',
+-          'table' => $table,
+-          'id' => 'standard',
+-          'additional fields' => $additional_fields,
+-          'field_name' => $field_name,
+-          'entity_type' => $entity_type_id,
+-        ];
++        if (!empty($allow_sort)) {
++          $data[$table_alias][$column_real_name]['sort'] = [
++            'field' => $column_real_name,
++            'table' => $table,
++            'id' => $sort,
++            'additional fields' => $additional_fields,
++            'field_name' => $field_name,
++            'entity_type' => $entity_type_id,
++          ];
++        }
++
++        // Set click sortable if there is a field definition.
++        if (isset($data[$table_alias][$field_name]['field'])) {
++          $data[$table_alias][$field_name]['field']['click sortable'] = $allow_sort;
++        }
++
++        // Expose additional delta column for multiple value fields.
++        if ($field_storage->isMultiple()) {
++          $title_delta = t('@label (@name:delta)', ['@label' => $label, '@name' => $field_name]);
++          $title_short_delta = t('@label:delta', ['@label' => $label]);
++
++          $data[$table_alias]['delta'] = [
++            'group' => $group,
++            'title' => $title_delta,
++            'title short' => $title_short_delta,
++            'help' => t('Delta - Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
++          ];
++          $data[$table_alias]['delta']['field'] = [
++            'id' => 'numeric',
++          ];
++          $data[$table_alias]['delta']['argument'] = [
++            'field' => 'delta',
++            'table' => $table,
++            'id' => 'numeric',
++            'additional fields' => $additional_fields,
++            'empty field name' => t('- No value -'),
++            'field_name' => $field_name,
++            'entity_type' => $entity_type_id,
++          ];
++          $data[$table_alias]['delta']['filter'] = [
++            'field' => 'delta',
++            'table' => $table,
++            'id' => 'numeric',
++            'additional fields' => $additional_fields,
++            'field_name' => $field_name,
++            'entity_type' => $entity_type_id,
++            'allow empty' => TRUE,
++          ];
++          $data[$table_alias]['delta']['sort'] = [
++            'field' => 'delta',
++            'table' => $table,
++            'id' => 'standard',
++            'additional fields' => $additional_fields,
++            'field_name' => $field_name,
++            'entity_type' => $entity_type_id,
++          ];
++        }
+       }
+     }
+   }
+diff --git a/core/modules/workspaces/src/EntityQuery/Query.php b/core/modules/workspaces/src/EntityQuery/Query.php
+index c7aebf61047d4addff1ef7a2737582359b6dfdcb..23bda9da5010e552404ff37dfc6151485900b613 100644
+--- a/core/modules/workspaces/src/EntityQuery/Query.php
++++ b/core/modules/workspaces/src/EntityQuery/Query.php
+@@ -31,8 +31,8 @@ public function prepare() {
+       // relationship, and, as a consequence, the revision ID field is no longer
+       // a simple SQL field but an expression.
+       $this->sqlFields = [];
+-      $this->sqlQuery->addExpression("COALESCE([workspace_association].[target_entity_revision_id], [base_table].[$revision_field])", $revision_field);
+-      $this->sqlQuery->addExpression("[base_table].[$id_field]", $id_field);
++      $this->sqlQuery->addExpressionCoalesce(['workspace_association.target_entity_revision_id', "base_table.$revision_field"], $revision_field);
++      $this->sqlQuery->addExpressionField("base_table.$id_field", $id_field);
+ 
+       $this->sqlGroupBy['workspace_association.target_entity_revision_id'] = 'workspace_association.target_entity_revision_id';
+       $this->sqlGroupBy["base_table.$id_field"] = "base_table.$id_field";
+diff --git a/core/modules/workspaces/src/EntityQuery/QueryTrait.php b/core/modules/workspaces/src/EntityQuery/QueryTrait.php
+index eef973cc45639c56507af443be2586153b0014a4..b33325f079946c01d70beb9ff741a667004fe1b0 100644
+--- a/core/modules/workspaces/src/EntityQuery/QueryTrait.php
++++ b/core/modules/workspaces/src/EntityQuery/QueryTrait.php
+@@ -78,7 +78,11 @@ public function prepare() {
+       // revision.
+       $id_field = $this->entityType->getKey('id');
+       $target_id_field = WorkspaceAssociation::getIdField($this->entityTypeId);
+-      $this->sqlQuery->leftJoin('workspace_association', 'workspace_association', "[%alias].[target_entity_type_id] = '{$this->entityTypeId}' AND [%alias].[$target_id_field] = [base_table].[$id_field] AND [%alias].[workspace] = '{$active_workspace->id()}'");
++      $this->sqlQuery->leftJoin('workspace_association', 'workspace_association', $this->sqlQuery->joinCondition()
++        ->condition("%alias.target_entity_type_id", $this->entityTypeId)
++        ->compare("%alias.$target_id_field", "base_table.$id_field")
++        ->condition("%alias.workspace", $active_workspace->id())
++      );
+     }
+ 
+     return $this;
+diff --git a/core/modules/workspaces/src/EntityQuery/Tables.php b/core/modules/workspaces/src/EntityQuery/Tables.php
+index 199d5cc1559729921f1263198464db62162cf1f6..d15826102a3bf71e015b6de974f0561d89a22d85 100644
+--- a/core/modules/workspaces/src/EntityQuery/Tables.php
++++ b/core/modules/workspaces/src/EntityQuery/Tables.php
+@@ -2,6 +2,7 @@
+ 
+ namespace Drupal\workspaces\EntityQuery;
+ 
++use Drupal\Core\Database\Query\ConditionInterface;
+ use Drupal\Core\Database\Query\SelectInterface;
+ use Drupal\Core\Entity\EntityType;
+ use Drupal\Core\Entity\Query\Sql\Tables as BaseTables;
+@@ -93,9 +94,18 @@ protected function addJoin($type, $table, $join_condition, $langcode, $delta = N
+       // 'revision_id' string used when joining dedicated field tables.
+       // If those two conditions are met, we have to update the join condition
+       // to also look for a possible workspace-specific revision using COALESCE.
+-      $condition_parts = explode(' = ', $join_condition);
+-      $condition_parts_1 = str_replace(['[', ']'], '', $condition_parts[1]);
+-      [$base_table, $id_field] = explode('.', $condition_parts_1);
++      if ($join_condition instanceof ConditionInterface) {
++        $first_condition = $join_condition->conditions()[0];
++        $field = $first_condition['field'];
++        $field2 = $first_condition['field2'];
++        [$base_table, $id_field] = explode('.', $field2);
++        $condition_parts = [];
++      }
++      else {
++        $condition_parts = explode(' = ', $join_condition);
++        $condition_parts_1 = str_replace(['[', ']'], '', $condition_parts[1]);
++        [$base_table, $id_field] = explode('.', $condition_parts_1);
++      }
+ 
+       if (isset($this->baseTablesEntityType[$base_table])) {
+         $entity_type_id = $this->baseTablesEntityType[$base_table];
+@@ -103,7 +113,12 @@ protected function addJoin($type, $table, $join_condition, $langcode, $delta = N
+ 
+         if ($id_field === $revision_key || $id_field === 'revision_id') {
+           $workspace_association_table = $this->contentWorkspaceTables[$base_table];
+-          $join_condition = "{$condition_parts[0]} = COALESCE($workspace_association_table.target_entity_revision_id, {$condition_parts[1]})";
++          if ($join_condition instanceof ConditionInterface) {
++            $join_condition = $this->sqlQuery->joinCondition()->where("$field = COALESCE($workspace_association_table.target_entity_revision_id, $field2)");
++          }
++          else {
++            $join_condition = "{$condition_parts[0]} = COALESCE($workspace_association_table.target_entity_revision_id, {$condition_parts[1]})";
++          }
+         }
+       }
+     }
+@@ -149,7 +164,13 @@ public function addWorkspaceAssociationJoin($entity_type_id, $base_table_alias,
+ 
+       // LEFT join the Workspace association entity's table so we can properly
+       // include live content along with a possible workspace-specific revision.
+-      $this->contentWorkspaceTables[$base_table_alias] = $this->sqlQuery->leftJoin('workspace_association', NULL, "[%alias].[target_entity_type_id] = '$entity_type_id' AND [%alias].[$target_id_field] = [$base_table_alias].[$id_field] AND [%alias].[workspace] = '$active_workspace_id'");
++
++      $this->contentWorkspaceTables[$base_table_alias] = $this->sqlQuery->leftJoin('workspace_association', NULL,
++        $this->sqlQuery->joinCondition()
++          ->condition("%alias.target_entity_type_id", $entity_type_id)
++          ->compare("%alias.$target_id_field", "$base_table_alias.$id_field")
++          ->condition("%alias.workspace", $active_workspace_id)
++      );
+ 
+       $this->baseTablesEntityType[$base_table_alias] = $entity_type->id();
+     }
+diff --git a/core/modules/workspaces/src/ViewsQueryAlter.php b/core/modules/workspaces/src/ViewsQueryAlter.php
+index f409a20b0d9c5fc52793bda381dc25b9d2e16501..46703743b1c674e3a8990507ace19aa1fcc2205a 100644
+--- a/core/modules/workspaces/src/ViewsQueryAlter.php
++++ b/core/modules/workspaces/src/ViewsQueryAlter.php
+@@ -411,7 +411,11 @@ protected function getRevisionTableJoin($relationship, $table, $field, $workspac
+     if ($entity_type->isTranslatable() && $this->languageManager->isMultilingual()) {
+       $langcode_field = $entity_type->getKey('langcode');
+       $definition['extra'] = [
+-        ['field' => $langcode_field, 'left_field' => $langcode_field],
++        [
++          'field' => $langcode_field,
++          'field2' => "$relationship.$langcode_field",
++          'operator' => '=',
++        ],
+       ];
+     }
+ 
+diff --git a/core/modules/workspaces/src/WorkspaceAssociation.php b/core/modules/workspaces/src/WorkspaceAssociation.php
+index 25b476fab864e4c0ef65a82be426c613fb08bcf3..018b522cd7eb4113c98757ebb5156a36b3126408 100644
+--- a/core/modules/workspaces/src/WorkspaceAssociation.php
++++ b/core/modules/workspaces/src/WorkspaceAssociation.php
+@@ -64,20 +64,37 @@ public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $w
+     $id_field = static::getIdField($entity->getEntityTypeId());
+ 
+     try {
+-      $transaction = $this->database->startTransaction();
++      if ($this->database->driver() == 'mongodb') {
++        $session = $this->database->getMongodbSession();
++        $session_started = FALSE;
++        if (!$session->isInTransaction()) {
++          $session->startTransaction();
++          $session_started = TRUE;
++        }
++      }
++      else {
++        $transaction = $this->database->startTransaction();
++      }
++
+       // Update all affected workspaces that were tracking the current revision.
+       // This means they are inheriting content and should be updated.
+       if ($tracked_revision_id) {
++        if ($id_field === 'target_entity_id') {
++          $entity_id = (int) $entity->id();
++        }
++        else {
++          $entity_id = (string) $entity->id();
++        }
+         $this->database->update(static::TABLE)
+           ->fields([
+             'target_entity_revision_id' => $entity->getRevisionId(),
+           ])
+           ->condition('workspace', $affected_workspaces, 'IN')
+           ->condition('target_entity_type_id', $entity->getEntityTypeId())
+-          ->condition($id_field, $entity->id())
++          ->condition($id_field, $entity_id)
+           // Only update descendant workspaces if they have the same initial
+           // revision, which means they are currently inheriting content.
+-          ->condition('target_entity_revision_id', $tracked_revision_id)
++          ->condition('target_entity_revision_id', (int) $tracked_revision_id)
+           ->execute();
+       }
+ 
+@@ -102,11 +119,18 @@ public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $w
+         }
+         $insert_query->execute();
+       }
++
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->commitTransaction();
++      }
+     }
+     catch (\Exception $e) {
+       if (isset($transaction)) {
+         $transaction->rollBack();
+       }
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->abortTransaction();
++      }
+       Error::logException($this->logger, $e);
+       throw $e;
+     }
+@@ -141,10 +165,21 @@ public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entit
+       ->condition('workspace', $workspace_id);
+ 
+     if ($entity_type_id) {
+-      $query->condition('target_entity_type_id', $entity_type_id, '=');
++      $query->condition('target_entity_type_id', $entity_type_id);
+ 
+       if ($entity_ids) {
+-        $query->condition(static::getIdField($entity_type_id), $entity_ids, 'IN');
++        $id_field = static::getIdField($entity_type_id);
++        if ($id_field === 'target_entity_id') {
++          foreach ($entity_ids as &$entity_id) {
++            $entity_id = (int) $entity_id;
++          }
++        }
++        else {
++          foreach ($entity_ids as &$entity_id) {
++            $entity_id = (string) $entity_id;
++          }
++        }
++        $query->condition($id_field, $entity_ids, 'IN');
+       }
+     }
+ 
+@@ -225,21 +260,57 @@ public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_i
+       $workspace_candidates = [$workspace_id];
+     }
+ 
+-    $query = $this->database->select($entity_type->getRevisionTable(), 'revision');
+-    $query->leftJoin($entity_type->getBaseTable(), 'base', "[revision].[$id_field] = [base].[$id_field]");
++    if ($this->database->driver() == 'mongodb') {
++      $all_revisions_table = $table_mapping->getJsonStorageAllRevisionsTable();
+ 
+-    $query
+-      ->fields('revision', [$revision_id_field, $id_field])
+-      ->condition("revision.$workspace_field", $workspace_candidates, 'IN')
+-      ->where("[revision].[$revision_id_field] >= [base].[$revision_id_field]")
+-      ->orderBy("revision.$revision_id_field", 'ASC');
+-
+-    // Restrict the result to a set of entity ID's if provided.
+-    if ($entity_ids) {
+-      $query->condition("revision.$id_field", $entity_ids, 'IN');
++      $query = $this->database->select($entity_type->getBaseTable(), 'base');
++      $query
++        ->fields('base', [$revision_id_field, $id_field, $all_revisions_table])
++        ->condition("$all_revisions_table.$workspace_field", $workspace_candidates, 'IN')
++        ->orderBy("$all_revisions_table.$revision_id_field", 'ASC');
++
++      // Restrict the result to a set of entity ID's if provided.
++      if ($entity_ids) {
++        foreach ($entity_ids as & $entity_id) {
++          $entity_id = (int) $entity_id;
++        }
++        $query->condition($id_field, $entity_ids, 'IN');
++      }
++
++      $result = [];
++
++      $rows = $query->execute()->fetchAll();
++      foreach ($rows as $row) {
++        $id = $row->{$id_field};
++        $revision_id = $row->{$revision_id_field};
++        $all_revisions = $row->{$all_revisions_table};
++        foreach ($all_revisions as $all_revision) {
++          $all_revision_revision_id = $all_revision[$revision_id_field] ?? NULL;
++          $all_revision_workspace = $all_revision[$workspace_field] ?? NULL;
++          // @todo the next if-statement should be moved to the query.
++          if ($all_revision_revision_id && $all_revision_workspace && ($all_revision_revision_id >= $revision_id) && (in_array($all_revision_workspace, $workspace_candidates, TRUE))) {
++            $result[$all_revision_revision_id] = $id;
++          }
++        }
++      }
+     }
++    else {
++      $query = $this->database->select($entity_type->getRevisionTable(), 'revision');
++      $query->leftJoin($entity_type->getBaseTable(), 'base', $query->joinCondition()->compare("revision.$id_field", "base.$id_field"));
+ 
+-    $result = $query->execute()->fetchAllKeyed();
++      $query
++        ->fields('revision', [$revision_id_field, $id_field])
++        ->condition("revision.$workspace_field", $workspace_candidates, 'IN')
++        ->where("[revision].[$revision_id_field] >= [base].[$revision_id_field]")
++        ->orderBy("revision.$revision_id_field", 'ASC');
++
++      // Restrict the result to a set of entity ID's if provided.
++      if ($entity_ids) {
++        $query->condition("revision.$id_field", $entity_ids, 'IN');
++      }
++
++      $result = $query->execute()->fetchAllKeyed();
++    }
+ 
+     // Cache the list of associated entity IDs if the full list was requested.
+     if (!$entity_ids) {
+@@ -279,19 +350,52 @@ public function getAssociatedInitialRevisions(string $workspace_id, string $enti
+     $revision_id_field = $table_mapping->getColumnNames($entity_type->getKey('revision'))['value'];
+ 
+     $query = $this->database->select($entity_type->getBaseTable(), 'base');
+-    $query->leftJoin($entity_type->getRevisionTable(), 'revision', "[base].[$revision_id_field] = [revision].[$revision_id_field]");
++    if ($this->database->driver() == 'mongodb') {
++      $current_revision_table = $table_mapping->getJsonStorageCurrentRevisionTable();
+ 
+-    $query
+-      ->fields('base', [$revision_id_field, $id_field])
+-      ->condition("revision.$workspace_field", $workspace_id, '=')
+-      ->orderBy("base.$revision_id_field", 'ASC');
++      $query
++        ->fields('base', [$revision_id_field, $id_field, $current_revision_table])
++        ->condition("$current_revision_table.$workspace_field", $workspace_id)
++        ->orderBy("$current_revision_table.$revision_id_field", 'ASC');
+ 
+-    // Restrict the result to a set of entity ID's if provided.
+-    if ($entity_ids) {
+-      $query->condition("base.$id_field", $entity_ids, 'IN');
++      // Restrict the result to a set of entity ID's if provided.
++      if ($entity_ids) {
++        foreach ($entity_ids as & $entity_id) {
++          $entity_id = (int) $entity_id;
++        }
++        $query->condition("$current_revision_table.$id_field", $entity_ids, 'IN');
++      }
++
++      $rows = $query->execute()->fetchAll();
++      $result = [];
++      foreach ($rows as $row) {
++        if (isset($row->{$current_revision_table})) {
++          $current_revisions = $row->{$current_revision_table};
++          foreach ($current_revisions as $current_revision) {
++            if (isset($current_revision[$revision_id_field]) && isset($current_revision[$id_field])) {
++              $revision_id = $current_revision[$revision_id_field];
++              $id = $current_revision[$id_field];
++              $result[$revision_id] = $id;
++            }
++          }
++        }
++      }
+     }
++    else {
++      $query->leftJoin($entity_type->getRevisionTable(), 'revision', $query->joinCondition()->compare("base.$revision_id_field", "revision.$revision_id_field"));
++
++      $query
++        ->fields('base', [$revision_id_field, $id_field])
++        ->condition("revision.$workspace_field", $workspace_id, '=')
++        ->orderBy("base.$revision_id_field", 'ASC');
+ 
+-    $result = $query->execute()->fetchAllKeyed();
++      // Restrict the result to a set of entity ID's if provided.
++      if ($entity_ids) {
++        $query->condition("base.$id_field", $entity_ids, 'IN');
++      }
++
++      $result = $query->execute()->fetchAllKeyed();
++    }
+ 
+     // Cache the list of associated entity IDs if the full list was requested.
+     if (!$entity_ids) {
+@@ -306,20 +410,38 @@ public function getAssociatedInitialRevisions(string $workspace_id, string $enti
+    */
+   public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity, bool $latest_revision = FALSE) {
+     $id_field = static::getIdField($entity->getEntityTypeId());
++    if ($id_field === 'target_entity_id') {
++      $entity_id = (int) $entity->id();
++    }
++    else {
++      $entity_id = (string) $entity->id();
++    }
+     $query = $this->database->select(static::TABLE, 'wa')
+       ->fields('wa', ['workspace'])
+-      ->condition('[wa].[target_entity_type_id]', $entity->getEntityTypeId())
+-      ->condition("[wa].[$id_field]", $entity->id());
++      ->condition('wa.target_entity_type_id', $entity->getEntityTypeId())
++      ->condition("wa.$id_field", $entity_id);
+ 
+     // Use a self-join to get only the workspaces in which the latest revision
+     // of the entity is tracked.
+     if ($latest_revision) {
+-      $inner_select = $this->database->select(static::TABLE, 'wai')
+-        ->condition('[wai].[target_entity_type_id]', $entity->getEntityTypeId())
+-        ->condition("[wai].[$id_field]", $entity->id());
+-      $inner_select->addExpression('MAX([wai].[target_entity_revision_id])', 'max_revision_id');
++      if ($this->database->driver() == 'mongodb') {
++        $inner_select = $this->database->select(static::TABLE, 'wai')
++          ->condition('wai.target_entity_type_id', $entity->getEntityTypeId())
++          ->condition("wai.$id_field", $entity_id);
++        $inner_select->addExpressionMax('wai.target_entity_revision_id', 'max_revision_id');
++        $max_revision_id = $inner_select->execute()->fetchField();
++        if (!empty($max_revision_id)) {
++          $query->condition('wa.target_entity_revision_id', (int) $max_revision_id);
++        }
++      }
++      else {
++        $inner_select = $this->database->select(static::TABLE, 'wai')
++          ->condition('[wai].[target_entity_type_id]', $entity->getEntityTypeId())
++          ->condition("[wai].[$id_field]", $entity->id());
++        $inner_select->addExpression('MAX([wai].[target_entity_revision_id])', 'max_revision_id');
+ 
+-      $query->join($inner_select, 'waj', '[wa].[target_entity_revision_id] = [waj].[max_revision_id]');
++        $query->join($inner_select, 'waj', '[wa].[target_entity_revision_id] = [waj].[max_revision_id]');
++      }
+     }
+ 
+     $result = $query->execute()->fetchCol();
+@@ -356,6 +478,13 @@ public function deleteAssociations($workspace_id = NULL, $entity_type_id = NULL,
+       $query->condition('target_entity_type_id', $entity_type_id, '=');
+ 
+       if ($entity_ids) {
++        $entity_ids_as_integers = [];
++        $entity_ids_as_strings = [];
++        foreach ($entity_ids as & $entity_id) {
++          $entity_id = (int) $entity_id;
++          $entity_ids_as_integers[] = (int) $entity_id;
++          $entity_ids_as_strings[] = (string) $entity_id;
++        }
+         try {
+           $query->condition(static::getIdField($entity_type_id), $entity_ids, 'IN');
+         }
+@@ -364,13 +493,16 @@ public function deleteAssociations($workspace_id = NULL, $entity_type_id = NULL,
+           // to retrieve its identifier field type, so we try both.
+           $query->condition(
+             $query->orConditionGroup()
+-              ->condition('target_entity_id', $entity_ids, 'IN')
+-              ->condition('target_entity_id_string', $entity_ids, 'IN')
++              ->condition('target_entity_id', $entity_ids_as_integers, 'IN')
++              ->condition('target_entity_id_string', $entity_ids_as_strings, 'IN')
+           );
+         }
+       }
+ 
+       if ($revision_ids) {
++        foreach ($revision_ids as &$revision_id) {
++          $revision_id = (int) $revision_id;
++        }
+         $query->condition('target_entity_revision_id', $revision_ids, 'IN');
+       }
+     }
+@@ -385,18 +517,50 @@ public function deleteAssociations($workspace_id = NULL, $entity_type_id = NULL,
+    */
+   public function initializeWorkspace(WorkspaceInterface $workspace) {
+     if ($parent_id = $workspace->parent->target_id) {
+-      $indexed_rows = $this->database->select(static::TABLE);
+-      $indexed_rows->addExpression(':new_id', 'workspace', [
+-        ':new_id' => $workspace->id(),
+-      ]);
+-      $indexed_rows->fields(static::TABLE, [
+-        'target_entity_type_id',
+-        'target_entity_id',
+-        'target_entity_id_string',
+-        'target_entity_revision_id',
+-      ]);
+-      $indexed_rows->condition('workspace', $parent_id);
+-      $this->database->insert(static::TABLE)->from($indexed_rows)->execute();
++      if ($this->database->driver() == 'mongodb') {
++        $indexed_rows = $this->database->select(static::TABLE);
++        $indexed_rows->fields(static::TABLE, [
++          'target_entity_type_id',
++          'target_entity_id',
++          'target_entity_id_string',
++          'target_entity_revision_id',
++        ]);
++        $indexed_rows->condition('workspace', $parent_id);
++        $result = $indexed_rows->execute()->fetchAll();
++        if (!empty($result)) {
++          $query = $this->database->insert(static::TABLE)->fields([
++            'workspace',
++            'target_entity_type_id',
++            'target_entity_id',
++            'target_entity_id_string',
++            'target_entity_revision_id',
++          ]);
++          foreach ($result as $row) {
++            $query->values([
++              $workspace->id(),
++              $row->target_entity_type_id,
++              $row->target_entity_id,
++              $row->target_entity_id,
++              $row->target_entity_revision_id,
++            ]);
++          }
++          $query->execute();
++        }
++      }
++      else {
++        $indexed_rows = $this->database->select(static::TABLE);
++        $indexed_rows->addExpression(':new_id', 'workspace', [
++          ':new_id' => $workspace->id(),
++        ]);
++        $indexed_rows->fields(static::TABLE, [
++          'target_entity_type_id',
++          'target_entity_id',
++          'target_entity_id_string',
++          'target_entity_revision_id',
++        ]);
++        $indexed_rows->condition('workspace', $parent_id);
++        $this->database->insert(static::TABLE)->from($indexed_rows)->execute();
++      }
+     }
+ 
+     $this->associatedRevisions = $this->associatedInitialRevisions = [];
+diff --git a/core/modules/workspaces/src/WorkspaceManager.php b/core/modules/workspaces/src/WorkspaceManager.php
+index d6f2e3327c479f65673b8fc01fc539ebb04c39e6..1808dc0484ddb7d1427e62cbb34d6809152bea35 100644
+--- a/core/modules/workspaces/src/WorkspaceManager.php
++++ b/core/modules/workspaces/src/WorkspaceManager.php
+@@ -222,7 +222,10 @@ public function purgeDeletedWorkspacesBatch() {
+         // entity was created inside that workspace), we need to delete the
+         // whole entity after all of its pending revisions are gone.
+         if (isset($initial_revision_ids[$revision_id])) {
+-          $associated_entity_storage->delete([$associated_entity_storage->load($initial_revision_ids[$revision_id])]);
++          $associated_entity = $associated_entity_storage->load($initial_revision_ids[$revision_id]);
++          if ($associated_entity) {
++            $associated_entity_storage->delete([$associated_entity]);
++          }
+         }
+         else {
+           // Delete the associated entity revision.
+diff --git a/core/modules/workspaces/src/WorkspaceMerger.php b/core/modules/workspaces/src/WorkspaceMerger.php
+index 56a198ee0d898e82e68c6982772973247e341022..e2c7278fc52b579ee02c24331903806cb1ceb2a9 100644
+--- a/core/modules/workspaces/src/WorkspaceMerger.php
++++ b/core/modules/workspaces/src/WorkspaceMerger.php
+@@ -31,7 +31,17 @@ public function merge() {
+     }
+ 
+     try {
+-      $transaction = $this->database->startTransaction();
++      if ($this->database->driver() == 'mongodb') {
++        $session = $this->database->getMongodbSession();
++        $session_started = FALSE;
++        if (!$session->isInTransaction()) {
++          $session->startTransaction();
++          $session_started = TRUE;
++        }
++      }
++      else {
++        $transaction = $this->database->startTransaction();
++      }
+       $max_execution_time = ini_get('max_execution_time');
+       $step_size = Settings::get('entity_update_batch_size', 50);
+       $counter = 0;
+@@ -63,11 +73,18 @@ public function merge() {
+           }
+         }
+       }
++
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->commitTransaction();
++      }
+     }
+     catch (\Exception $e) {
+       if (isset($transaction)) {
+         $transaction->rollBack();
+       }
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->abortTransaction();
++      }
+       Error::logException($this->logger, $e);
+       throw $e;
+     }
+diff --git a/core/modules/workspaces/src/WorkspacePublisher.php b/core/modules/workspaces/src/WorkspacePublisher.php
+index f8610247269386eb1fe135a547458a27bea303a6..522e9bb308435b9aa5b56cf91be109c200718a27 100644
+--- a/core/modules/workspaces/src/WorkspacePublisher.php
++++ b/core/modules/workspaces/src/WorkspacePublisher.php
+@@ -45,7 +45,20 @@ public function publish() {
+     }
+ 
+     try {
+-      $transaction = $this->database->startTransaction();
++      if ($this->database->driver() == 'mongodb') {
++        $session = $this->database->getMongodbSession();
++        $session_started = FALSE;
++        if (!$session->isInTransaction()) {
++          $session->startTransaction();
++          $session_started = TRUE;
++        }
++      }
++      else {
++        $transaction = $this->database->startTransaction();
++      }
++
++      // @todo Handle the publishing of a workspace with a batch operation in
++      //   https://www.drupal.org/node/2958752.
+       $this->workspaceManager->executeOutsideWorkspace(function () use ($tracked_entities) {
+         $max_execution_time = ini_get('max_execution_time');
+         $step_size = Settings::get('entity_update_batch_size', 50);
+@@ -82,11 +95,18 @@ public function publish() {
+           }
+         }
+       });
++
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->commitTransaction();
++      }
+     }
+     catch (\Exception $e) {
+       if (isset($transaction)) {
+         $transaction->rollBack();
+       }
++      if (isset($session) && $session->isInTransaction() && $session_started) {
++        $session->abortTransaction();
++      }
+       Error::logException($this->logger, $e);
+       throw $e;
+     }
+diff --git a/core/modules/workspaces/src/WorkspacesAliasRepository.php b/core/modules/workspaces/src/WorkspacesAliasRepository.php
+index 8ed057f6bd58243c6cfc069809f543623f546f13..f42c952687061b3cb797e299537b5d8738fcac06 100644
+--- a/core/modules/workspaces/src/WorkspacesAliasRepository.php
++++ b/core/modules/workspaces/src/WorkspacesAliasRepository.php
+@@ -41,11 +41,34 @@ protected function getBaseQuery() {
+     $active_workspace = $this->workspaceManager->getActiveWorkspace();
+ 
+     $query = $this->connection->select('path_alias', 'original_base_table');
+-    $wa_join = $query->leftJoin('workspace_association', NULL, "[%alias].[target_entity_type_id] = 'path_alias' AND [%alias].[target_entity_id] = [original_base_table].[id] AND [%alias].[workspace] = :active_workspace_id", [
+-      ':active_workspace_id' => $active_workspace->id(),
+-    ]);
+-    $query->innerJoin('path_alias_revision', 'base_table', "[%alias].[revision_id] = COALESCE([$wa_join].[target_entity_revision_id], [original_base_table].[revision_id])");
+-    $query->condition('base_table.status', 1);
++    if ($this->connection->driver() == 'mongodb') {
++      $query->leftJoin('workspace_association', 'wa',
++        $query->joinCondition()
++          ->condition("%alias.target_entity_type_id", 'path_alias')
++          ->compare("%alias.target_entity_id", "original_base_table.id")
++          ->condition("%alias.workspace", $active_workspace->id())
++      );
++
++      $coalesce_field = [
++        '$ifNull' => [
++          '$' . $this->connection->escapeField('wa.target_entity_revision_id'),
++          '$' . $this->connection->escapeField('original_base_table.revision_id'),
++        ],
++      ];
++
++      $query->innerJoin('path_alias', 'base_table',
++        $query->joinCondition()
++          ->compare("base_table.path_alias_current_revision.revision_id", serialize($coalesce_field))
++          ->condition('base_table.path_alias_current_revision.status', TRUE),
++      );
++    }
++    else {
++      $wa_join = $query->leftJoin('workspace_association', NULL, "[%alias].[target_entity_type_id] = 'path_alias' AND [%alias].[target_entity_id] = [original_base_table].[id] AND [%alias].[workspace] = :active_workspace_id", [
++        ':active_workspace_id' => $active_workspace->id(),
++      ]);
++      $query->innerJoin('path_alias_revision', 'base_table', "[%alias].[revision_id] = COALESCE([$wa_join].[target_entity_revision_id], [original_base_table].[revision_id])");
++      $query->condition('base_table.status', 1);
++    }
+ 
+     return $query;
+   }
+diff --git a/core/modules/workspaces/workspaces.install b/core/modules/workspaces/workspaces.install
+index 6dfb3312fb895554f6a528c4b9f2ef867320abb9..c0b3343bfd34c26dbc294921c5a018beaf144f69 100644
+--- a/core/modules/workspaces/workspaces.install
++++ b/core/modules/workspaces/workspaces.install
+@@ -41,7 +41,7 @@ function workspaces_install(): void {
+     $query = \Drupal::entityTypeManager()->getStorage('user')->getQuery()
+       ->accessCheck(FALSE)
+       ->condition('roles', $admin_roles, 'IN')
+-      ->condition('status', 1)
++      ->condition('status', TRUE)
+       ->sort('uid', 'ASC')
+       ->range(0, 1);
+     $result = $query->execute();
diff --git a/readme.MD b/readme.MD
index c49da0784a67f42fc3ea4f813193a16eeb830524..63d32a8364e9331adb0deeb266e3106f184c9b0d 100644
--- a/readme.MD
+++ b/readme.MD
@@ -83,7 +83,7 @@ When entity instances are revisionable, translatable and/or have field data atta
 
 
 
-## Install Drupal on MongoDB (for Drupal Core [11.1.2](https://www.drupal.org/project/drupal/releases/11.1.2))
+## Install Drupal on MongoDB (for Drupal Core [11.1.3](https://www.drupal.org/project/drupal/releases/11.1.3))
 
 This install guide uses the DDEV development environment.
 
@@ -97,7 +97,7 @@ This install guide is based on the following DDEV [guide](https://ddev.readthedo
 
 ```ddev start```
 
-```ddev composer create drupal/recommended-project:11.1.2```
+```ddev composer create drupal/recommended-project:11.1.3```
 
 ```ddev config --update```
 
@@ -115,7 +115,7 @@ This install guide is based on the following DDEV [guide](https://ddev.readthedo
 ### 5. Drupal Core needs to be patched to make it all work.
 ```cd web```
 
-```git apply -v modules/contrib/mongodb/patches/drupal-core-11.1.2.patch```
+```git apply -v modules/contrib/mongodb/patches/drupal-core-11.1.3.patch```
 
 ```cd ..```
 
diff --git a/src/modules/paragraphs/ParagraphStorageSchema.php b/src/modules/paragraphs/ParagraphStorageSchema.php
index b82f3536dea9a4a1cd084aac6f0ca45576a7be6a..3d4b2f1eadc889d73be75ccd5e1ce40aa12af1cd 100644
--- a/src/modules/paragraphs/ParagraphStorageSchema.php
+++ b/src/modules/paragraphs/ParagraphStorageSchema.php
@@ -28,4 +28,3 @@ class ParagraphStorageSchema extends SqlContentEntityStorageSchema {
   }
 
 }
-