From f1dac228c27bb08a9cf7d6b85dd979ca657df300 Mon Sep 17 00:00:00 2001
From: David Bekker <david.bekker@finalist.nl>
Date: Wed, 12 Mar 2025 09:07:57 +0100
Subject: [PATCH] update for Drupal 11.1.4

---
 database-settings-example2.png   |   Bin 0 -> 109005 bytes
 patches/drupal-core-11.1.4.patch | 16723 +++++++++++++++++++++++++++++
 src/Plugin/views/field/Date.php  |     2 +-
 src/Plugin/views/filter/Date.php |     6 +-
 4 files changed, 16727 insertions(+), 4 deletions(-)
 create mode 100644 database-settings-example2.png
 create mode 100644 patches/drupal-core-11.1.4.patch

diff --git a/database-settings-example2.png b/database-settings-example2.png
new file mode 100644
index 0000000000000000000000000000000000000000..443600948bd0df1037e2aa92bf3ac233d2abd021
GIT binary patch
literal 109005
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
Mp00i_>zopr0K$9B!vFvP

literal 0
HcmV?d00001

diff --git a/patches/drupal-core-11.1.4.patch b/patches/drupal-core-11.1.4.patch
new file mode 100644
index 0000000..fe080e2
--- /dev/null
+++ b/patches/drupal-core-11.1.4.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/src/Plugin/views/field/Date.php b/src/Plugin/views/field/Date.php
index 6725ef7..3659b3f 100644
--- a/src/Plugin/views/field/Date.php
+++ b/src/Plugin/views/field/Date.php
@@ -35,7 +35,7 @@ class Date extends CoreDate {
       $timezone = !empty($this->options['timezone']) ? $this->options['timezone'] : NULL;
       // Will be positive for a datetime in the past (ago), and negative for a
       // datetime in the future (hence).
-      $time_diff = \Drupal::time()->getRequestMicroTime() - $value;
+      $time_diff = intval(\Drupal::time()->getRequestMicroTime() - $value);
       switch ($format) {
         case 'raw time ago':
           return $this->dateFormatter->formatTimeDiffSince($value, ['granularity' => is_numeric($custom_format) ? $custom_format : 2]);
diff --git a/src/Plugin/views/filter/Date.php b/src/Plugin/views/filter/Date.php
index 9ceac91..54ce9ae 100644
--- a/src/Plugin/views/filter/Date.php
+++ b/src/Plugin/views/filter/Date.php
@@ -21,8 +21,8 @@ class Date extends CoreDate {
 
     if (!empty($this->value['type']) && $this->value['type'] == 'offset') {
       $time = \Drupal::time()->getRequestMicroTime();
-      $a = new UTCDateTime(($time - $a) * 1000);
-      $b = new UTCDateTime(($time + $b) * 1000);
+      $a = new UTCDateTime(intval(($time - $a) * 1000));
+      $b = new UTCDateTime(intval(($time + $b) * 1000));
     }
     else {
       $a = new UTCDateTime($a * 1000);
@@ -48,7 +48,7 @@ class Date extends CoreDate {
 
     if (!empty($this->value['type']) && $this->value['type'] == 'offset') {
       $time = \Drupal::time()->getRequestMicroTime();
-      $value = new UTCDateTime(($time + $value) * 1000);
+      $value = new UTCDateTime(intval(($time + $value) * 1000));
     }
     else {
       $value = new UTCDateTime($value * 1000);
-- 
GitLab