diff --git a/2929120-2.patch b/2929120-2.patch
new file mode 100644
index 0000000000..5a86d59e2c
--- /dev/null
+++ b/2929120-2.patch
@@ -0,0 +1,508 @@
+diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+index 7cb585f5fc..e354600aa3 100644
+--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
++++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+@@ -2,6 +2,7 @@
+ 
+ namespace Drupal\Core\Entity\Sql;
+ 
++use Drupal\Component\Utility\NestedArray;
+ use Drupal\Core\Database\Connection;
+ use Drupal\Core\Database\DatabaseExceptionWrapper;
+ use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+@@ -713,7 +714,9 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
+           elseif ($table_mapping->allowsSharedTableStorage($this->fieldStorageDefinitions[$field_name])) {
+             $column_names = $table_mapping->getColumnNames($field_name);
+             $storage_definition = $this->fieldStorageDefinitions[$field_name];
+-            $schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names));
++            // Preserve integer keys when merging the schema so that composite
++            // primary keys are not merged.
++            $schema[$table_name] = NestedArray::mergeDeepArray([$schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names)], TRUE);
+           }
+         }
+       }
+@@ -1046,15 +1049,12 @@ protected function initializeBaseTable(ContentEntityTypeInterface $entity_type)
+ 
+     $schema = [
+       'description' => "The base table for $entity_type_id entities.",
+-      'primary key' => [$entity_type->getKey('id')],
+       'indexes' => [],
+       'foreign keys' => [],
+     ];
+ 
+     if ($entity_type->hasKey('revision')) {
+       $revision_key = $entity_type->getKey('revision');
+-      $key_name = $this->getEntityIndexName($entity_type, $revision_key);
+-      $schema['unique keys'][$key_name] = [$revision_key];
+       $schema['foreign keys'][$entity_type_id . '__revision'] = [
+         'table' => $this->storage->getRevisionTable(),
+         'columns' => [$revision_key => $revision_key],
+@@ -1078,11 +1078,9 @@ protected function initializeBaseTable(ContentEntityTypeInterface $entity_type)
+   protected function initializeRevisionTable(ContentEntityTypeInterface $entity_type) {
+     $entity_type_id = $entity_type->id();
+     $id_key = $entity_type->getKey('id');
+-    $revision_key = $entity_type->getKey('revision');
+ 
+     $schema = [
+       'description' => "The revision table for $entity_type_id entities.",
+-      'primary key' => [$revision_key],
+       'indexes' => [],
+       'foreign keys' => [
+         $entity_type_id . '__revisioned' => [
+@@ -1092,8 +1090,6 @@ protected function initializeRevisionTable(ContentEntityTypeInterface $entity_ty
+       ],
+     ];
+ 
+-    $schema['indexes'][$this->getEntityIndexName($entity_type, $id_key)] = [$id_key];
+-
+     $this->addTableDefaults($schema);
+ 
+     return $schema;
+@@ -1114,10 +1110,6 @@ protected function initializeDataTable(ContentEntityTypeInterface $entity_type)
+ 
+     $schema = [
+       'description' => "The data table for $entity_type_id entities.",
+-      'primary key' => [$id_key, $entity_type->getKey('langcode')],
+-      'indexes' => [
+-        $entity_type_id . '__id__default_langcode__langcode' => [$id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')],
+-      ],
+       'foreign keys' => [
+         $entity_type_id => [
+           'table' => $this->storage->getBaseTable(),
+@@ -1126,11 +1118,6 @@ protected function initializeDataTable(ContentEntityTypeInterface $entity_type)
+       ],
+     ];
+ 
+-    if ($entity_type->hasKey('revision')) {
+-      $key = $entity_type->getKey('revision');
+-      $schema['indexes'][$this->getEntityIndexName($entity_type, $key)] = [$key];
+-    }
+-
+     $this->addTableDefaults($schema);
+ 
+     return $schema;
+@@ -1152,10 +1139,6 @@ protected function initializeRevisionDataTable(ContentEntityTypeInterface $entit
+ 
+     $schema = [
+       'description' => "The revision data table for $entity_type_id entities.",
+-      'primary key' => [$revision_key, $entity_type->getKey('langcode')],
+-      'indexes' => [
+-        $entity_type_id . '__id__default_langcode__langcode' => [$id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')],
+-      ],
+       'foreign keys' => [
+         $entity_type_id => [
+           'table' => $this->storage->getBaseTable(),
+@@ -1282,6 +1265,8 @@ protected function processFieldStorageSchema(array &$field_storage_schema) {
+         unset($field_storage_schema[$table_name]['fields'][$key]['initial']);
+         unset($field_storage_schema[$table_name]['fields'][$key]['initial_from_field']);
+       }
++
++      $this->addTableDefaults($field_storage_schema[$table_name]);
+     }
+   }
+
+@@ -1894,6 +1925,53 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
+       $schema['foreign keys'] = $this->getFieldForeignKeys($field_name, $field_schema, $column_mapping);
+     }
+ 
++    // Add primary keys and indexes for key fields.
++    if ($table_name === $this->storage->getBaseTable()) {
++      if ($field_name === $this->entityType->getKey('id')) {
++        $schema['primary key'] = [$this->entityType->getKey('id')];
++      }
++      elseif ($field_name === $this->entityType->getKey('revision')) {
++        $key_name = $this->getEntityIndexName($this->entityType, $this->entityType->getKey('revision'));
++        $schema['unique keys'][$key_name] = [$this->entityType->getKey('revision')];
++      }
++    }
++    elseif ($table_name === $this->storage->getRevisionTable()) {
++      if ($field_name === $this->entityType->getKey('id')) {
++        $schema['indexes'][$this->getEntityIndexName($this->entityType, $this->entityType->getKey('id'))] = [$field_name];
++      }
++      elseif ($field_name === $this->entityType->getKey('revision')) {
++        $schema['primary key'] = [$this->entityType->getKey('revision')];
++      }
++    }
++    elseif ($table_name === $this->storage->getDataTable()) {
++      if (in_array($field_name, [$this->entityType->getKey('id'), $this->entityType->getKey('langcode')], TRUE)) {
++        $schema['primary key'] = [
++          $this->entityType->getKey('id'),
++          $this->entityType->getKey('langcode'),
++        ];
++      }
++      elseif ($field_name === $this->entityType->getKey('revision')) {
++        $key_name = $this->getEntityIndexName($this->entityType, $this->entityType->getKey('revision'));
++        $schema['indexes'][$key_name] = [$this->entityType->getKey('revision')];
++      }
++    }
++    elseif ($table_name === $this->storage->getRevisionDataTable()) {
++      if (in_array($field_name, [$this->entityType->getKey('revision'), $this->entityType->getKey('langcode')], TRUE)) {
++        $schema['primary key'] = [$this->entityType->getKey('revision'), $this->entityType->getKey('langcode')];
++      }
++    }
++    if (in_array($table_name, [$this->storage->getDataTable(), $this->storage->getRevisionDataTable()], TRUE)) {
++      if (in_array($field_name, [$this->entityType->getKey('id'), $this->entityType->getKey('default_langcode'), $this->entityType->getKey('langcode')], TRUE)) {
++        $schema['indexes'][$this->entityType->id() . '__id__default_langcode__langcode'] = [
++          $this->entityType->getKey('id'),
++          $this->entityType->getKey('default_langcode'),
++          $this->entityType->getKey('langcode'),
++        ];
++      }
++    }
++
++    $this->addTableDefaults($schema);
++
+     return $schema;
+   }
+ 
+@@ -2145,6 +2223,34 @@ protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $stor
+   }
+ 
+   /**
++   * Merges schema with the field schema for a given column.
++   *
++   * This looks up the field that the column belongs to and merges the field
++   * schema of that field into the passed-in schema.
++   *
++   * @param array $schema
++   *   The schema to merge the other field schema to.
++   * @param \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping
++   *   The table mapping.
++   * @param string $table_name
++   *   The table name.
++   * @param string $column_name
++   *   The column name to merge the field schema for.
++   */
++  protected function mergeFieldSchema(&$schema, TableMappingInterface $table_mapping, $table_name, $column_name) {
++    foreach ($table_mapping->getFieldNames($table_name) as $search_field_name) {
++      $search_column_names = $table_mapping->getColumnNames($search_field_name);
++      foreach ($search_column_names as $search_column_name) {
++        if ($search_column_name === $column_name) {
++          $other_storage_definition = $this->fieldStorageDefinitions[$search_field_name];
++          $other_schema = $this->getSharedTableFieldSchema($other_storage_definition, $table_name, $search_column_names);
++          $schema = NestedArray::mergeDeepArray([$schema, $other_schema], TRUE);
++        }
++      }
++    }
++  }
++
++  /**
+    * Gets the name to be used for the given entity index.
+    *
+    * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+diff --git a/core/modules/node/node.install b/core/modules/node/node.install
+index 6469245c49..024cc0ccb6 100644
+--- a/core/modules/node/node.install
++++ b/core/modules/node/node.install
+@@ -255,3 +255,24 @@ function node_update_8400() {
+   $schema['fields']['realm']['description'] = 'The realm in which the user must possess the grant ID. Modules can define one or more realms by implementing hook_node_grants().';
+   Database::getConnection()->schema()->changeField('node_access', 'realm', 'realm', $schema['fields']['realm']);
+ }
++
++/**
++ * Update the stored schema data of various node fields.
++ */
++function node_update_8501() {
++  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
++
++  $field_names = [
++    'changed',
++    'created',
++    'promote',
++    'status',
++    'sticky',
++    'title',
++    'type',
++  ];
++  foreach ($field_names as $field_name) {
++    $field_storage_definition = $definition_update_manager->getFieldStorageDefinition($field_name, 'node');
++    $definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
++  }
++}
+diff --git a/core/modules/node/src/NodeStorageSchema.php b/core/modules/node/src/NodeStorageSchema.php
+index ac45bb5d1e..b0c4f17b14 100644
+--- a/core/modules/node/src/NodeStorageSchema.php
++++ b/core/modules/node/src/NodeStorageSchema.php
+@@ -2,7 +2,6 @@
+ 
+ namespace Drupal\node;
+ 
+-use Drupal\Core\Entity\ContentEntityTypeInterface;
+ use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+ use Drupal\Core\Field\FieldStorageDefinitionInterface;
+ 
+@@ -14,20 +13,6 @@ class NodeStorageSchema extends SqlContentEntityStorageSchema {
+   /**
+    * {@inheritdoc}
+    */
+-  protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
+-    $schema = parent::getEntitySchema($entity_type, $reset);
+-
+-    $schema['node_field_data']['indexes'] += [
+-      'node__frontpage' => ['promote', 'status', 'sticky', 'created'],
+-      'node__title_type' => ['title', ['type', 4]],
+-    ];
+-
+-    return $schema;
+-  }
+-
+-  /**
+-   * {@inheritdoc}
+-   */
+   protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+     $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+     $field_name = $storage_definition->getName();
+@@ -45,22 +30,17 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
+     }
+ 
+     if ($table_name == 'node_field_data') {
+-      switch ($field_name) {
+-        case 'promote':
+-        case 'status':
+-        case 'sticky':
+-        case 'title':
+-          // Improves the performance of the indexes defined
+-          // in getEntitySchema().
+-          $schema['fields'][$field_name]['not null'] = TRUE;
+-          break;
+-
+-        case 'changed':
+-        case 'created':
+-          // @todo Revisit index definitions:
+-          //   https://www.drupal.org/node/2015277.
+-          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+-          break;
++      if (in_array($field_name, ['changed', 'created'], TRUE)) {
++        $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
++      }
++      if (in_array($field_name, ['promote', 'status', 'sticky', 'title'], TRUE)) {
++        $schema['fields'][$field_name]['not null'] = TRUE;
++      }
++      if (in_array($field_name, ['promote', 'status', 'sticky', 'created'], TRUE)) {
++        $schema['node_field_data']['indexes']['node__frontpage'] = ['promote', 'status', 'sticky', 'created'];
++      }
++      if (in_array($field_name, ['title', 'type'], TRUE)) {
++        $schema['node_field_data']['indexes']['node__title_type'] = ['title', ['type', 4]];
+       }
+     }
+ 
+diff --git a/core/modules/system/system.install b/core/modules/system/system.install
+index ef6f124f51..73ebe436a5 100644
+--- a/core/modules/system/system.install
++++ b/core/modules/system/system.install
+@@ -2056,3 +2056,33 @@ function system_update_8403() {
+     }
+   }
+ }
++
++/**
++ * Update the stored schema data of ID and language fields.
++ */
++function system_update_8501() {
++  $entity_type_manager = \Drupal::entityTypeManager();
++  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
++  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_respository */
++  $entity_last_installed_schema_respository = \Drupal::service('entity.last_installed_schema.repository');
++
++  foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) {
++    $original_entity_type = $entity_last_installed_schema_respository->getLastInstalledDefinition($entity_type_id);
++    if (!$original_entity_type) {
++      continue;
++    }
++    $field_storage_definitions = $entity_last_installed_schema_respository->getLastInstalledFieldStorageDefinitions($entity_type_id);
++
++    $field_names = array_filter([
++      $original_entity_type->getKey('id'),
++      $original_entity_type->getKey('revision'),
++      $original_entity_type->getKey('langcode'),
++      $original_entity_type->getKey('default_langcode'),
++    ]);
++    foreach ($field_names as $field_name) {
++      if (isset($field_storage_definitions[$field_name])) {
++        $definition_update_manager->updateFieldStorageDefinition($field_storage_definitions[$field_name]);
++      }
++    }
++  }
++}
+diff --git a/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php b/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php
+index 625ee190ed..4b7fccbeb5 100644
+--- a/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php
++++ b/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php
+@@ -220,6 +220,53 @@ public function testMakeRevisionableErrorHandling() {
+     foreach ($new_storage_definitions as $storage_definition) {
+       $new_field_schema_data[$storage_definition->getName()] = $this->installedStorageSchema->get('entity_test_update.field_schema_data.' . $storage_definition->getName(), []);
+     }
++    // Before https://www.drupal.org/project/drupal/issues/2928906 the field
++    // schema data of integer identifier fields was incorrectly stored as 'int'
++    // instead of 'serial'. So we explicitly make that change before checking
++    // equality.
++    /* @see \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::hasColumnChanges() */
++    $this->assertSame($original_field_schema_data['id']['entity_test_update']['fields']['id']['type'], 'int');
++    $original_field_schema_data['id']['entity_test_update']['fields']['id']['type'] = 'serial';
++    // Also add keys that are stored in the field schema as of
++    // https://www.drupal.org/project/drupal/issues/2928906.
++    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update']['primary key']));
++    $original_field_schema_data['id']['entity_test_update']['primary key'] = ['id'];
++    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update_data']['primary key']));
++    $original_field_schema_data['id']['entity_test_update_data']['primary key'] = ['id', 'langcode'];
++    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode']));
++    $original_field_schema_data['id']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode'] = ['id', 'default_langcode', 'langcode'];
++    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update_data']['primary key']));
++    $original_field_schema_data['langcode']['entity_test_update_data']['primary key'] = ['id', 'langcode'];
++    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode']));
++    $original_field_schema_data['langcode']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode'] = ['id', 'default_langcode', 'langcode'];
++    $this->assertFalse(isset($original_field_schema_data['default_langcode']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode']));
++    $original_field_schema_data['default_langcode']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode'] = ['id', 'default_langcode', 'langcode'];
++    // Also account for the fact that the field schema now contains default
++    // values for DX.
++    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update']['unique keys']));
++    $original_field_schema_data['id']['entity_test_update']['unique keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update']['indexes']));
++    $original_field_schema_data['id']['entity_test_update']['indexes'] = [];
++    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update']['foreign keys']));
++    $original_field_schema_data['id']['entity_test_update']['foreign keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update_data']['unique keys']));
++    $original_field_schema_data['id']['entity_test_update_data']['unique keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update_data']['foreign keys']));
++    $original_field_schema_data['id']['entity_test_update_data']['foreign keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update']['unique keys']));
++    $original_field_schema_data['langcode']['entity_test_update']['unique keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update']['indexes']));
++    $original_field_schema_data['langcode']['entity_test_update']['indexes'] = [];
++    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update']['foreign keys']));
++    $original_field_schema_data['langcode']['entity_test_update']['foreign keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update_data']['unique keys']));
++    $original_field_schema_data['langcode']['entity_test_update_data']['unique keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update_data']['foreign keys']));
++    $original_field_schema_data['langcode']['entity_test_update_data']['foreign keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['default_langcode']['entity_test_update_data']['unique keys']));
++    $original_field_schema_data['default_langcode']['entity_test_update_data']['unique keys'] = [];
++    $this->assertFalse(isset($original_field_schema_data['default_langcode']['entity_test_update_data']['foreign keys']));
++    $original_field_schema_data['default_langcode']['entity_test_update_data']['foreign keys'] = [];
+     $this->assertEqual($original_field_schema_data, $new_field_schema_data);
+ 
+     // Check that temporary tables have been removed.
+diff --git a/core/modules/user/src/UserStorageSchema.php b/core/modules/user/src/UserStorageSchema.php
+index 5e590b8958..fae75fbb79 100644
+--- a/core/modules/user/src/UserStorageSchema.php
++++ b/core/modules/user/src/UserStorageSchema.php
+@@ -2,7 +2,6 @@
+ 
+ namespace Drupal\user;
+ 
+-use Drupal\Core\Entity\ContentEntityTypeInterface;
+ use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+ use Drupal\Core\Field\FieldStorageDefinitionInterface;
+ 
+@@ -14,19 +13,6 @@ class UserStorageSchema extends SqlContentEntityStorageSchema {
+   /**
+    * {@inheritdoc}
+    */
+-  protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
+-    $schema = parent::getEntitySchema($entity_type, $reset);
+-
+-    $schema['users_field_data']['unique keys'] += [
+-      'user__name' => ['name', 'langcode'],
+-    ];
+-
+-    return $schema;
+-  }
+-
+-  /**
+-   * {@inheritdoc}
+-   */
+   protected function processIdentifierSchema(&$schema, $key) {
+     // The "users" table does not use serial identifiers.
+     if ($key != $this->entityType->getKey('id')) {
+@@ -42,24 +28,22 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
+     $field_name = $storage_definition->getName();
+ 
+     if ($table_name == 'users_field_data') {
+-      switch ($field_name) {
+-        case 'name':
+-          // Improves the performance of the user__name index defined
+-          // in getEntitySchema().
+-          $schema['fields'][$field_name]['not null'] = TRUE;
+-          // Make sure the field is no longer than 191 characters so we can
+-          // add a unique constraint in MySQL.
+-          $schema['fields'][$field_name]['length'] = USERNAME_MAX_LENGTH;
+-          break;
+-
+-        case 'mail':
+-          $this->addSharedTableFieldIndex($storage_definition, $schema);
+-          break;
+-
+-        case 'access':
+-        case 'created':
+-          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+-          break;
++      if ($field_name === 'name') {
++        // Improves the performance of the user__name index defined
++        // in getEntitySchema().
++        $schema['fields'][$field_name]['not null'] = TRUE;
++        // Make sure the field is no longer than 191 characters so we can
++        // add a unique constraint in MySQL.
++        $schema['fields'][$field_name]['length'] = UserInterface::USERNAME_MAX_LENGTH;
++      }
++      if ($field_name === 'mail') {
++        $this->addSharedTableFieldIndex($storage_definition, $schema);
++      }
++      if (in_array($field_name, ['access', 'created'], TRUE)) {
++        $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
++      }
++      if (in_array($field_name, ['name', 'langcode'], TRUE)) {
++        $schema['users_field_data']['unique keys']['user__name'] = ['name', 'langcode'];
+       }
+     }
+ 
+diff --git a/core/modules/user/user.install b/core/modules/user/user.install
+index 0af797a43f..625bca3211 100644
+--- a/core/modules/user/user.install
++++ b/core/modules/user/user.install
+@@ -98,3 +98,16 @@ function user_update_8100() {
+     $config->set('status_blocked', $mail)->save(TRUE);
+   }
+ }
++
++/**
++ * Update the stored schema data of various node fields.
++ */
++function user_update_8501() {
++  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
++
++  $field_storage_definition = $definition_update_manager->getFieldStorageDefinition('name', 'user');
++  $definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
++
++  $entity_type = $definition_update_manager->getEntityType('user');
++  $definition_update_manager->updateEntityType($entity_type);
++}
+diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
+index d16ece2097..56852869e5 100644
+--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
++++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
+@@ -397,7 +397,7 @@ public function testGetSchemaRevisionable() {
+       ],
+     ]);
+ 
+-    $this->storage->expects($this->exactly(2))
++    $this->storage->expects($this->exactly(6))
+       ->method('getRevisionTable')
+       ->will($this->returnValue('entity_test_revision'));
+ 
+@@ -604,13 +604,13 @@ public function testGetSchemaRevisionableTranslatable() {
+       ],
+     ]);
+ 
+-    $this->storage->expects($this->exactly(3))
++    $this->storage->expects($this->exactly(25))
+       ->method('getRevisionTable')
+       ->will($this->returnValue('entity_test_revision'));
+-    $this->storage->expects($this->once())
++    $this->storage->expects($this->exactly(45))
+       ->method('getDataTable')
+       ->will($this->returnValue('entity_test_field_data'));
+-    $this->storage->expects($this->once())
++    $this->storage->expects($this->exactly(37))
+       ->method('getRevisionDataTable')
+       ->will($this->returnValue('entity_test_revision_field_data'));
+ 
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index 3349ebd30d..044af8c880 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Entity\Sql;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\DatabaseExceptionWrapper;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
@@ -717,7 +718,9 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
           elseif ($table_mapping->allowsSharedTableStorage($this->fieldStorageDefinitions[$field_name])) {
             $column_names = $table_mapping->getColumnNames($field_name);
             $storage_definition = $this->fieldStorageDefinitions[$field_name];
-            $schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names));
+            // Preserve integer keys when merging the schema so that composite
+            // primary keys are not merged.
+            $schema[$table_name] = NestedArray::mergeDeepArray([$schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names)], TRUE);
           }
         }
       }
@@ -1050,15 +1053,12 @@ protected function initializeBaseTable(ContentEntityTypeInterface $entity_type)
 
     $schema = [
       'description' => "The base table for $entity_type_id entities.",
-      'primary key' => [$entity_type->getKey('id')],
       'indexes' => [],
       'foreign keys' => [],
     ];
 
     if ($entity_type->hasKey('revision')) {
       $revision_key = $entity_type->getKey('revision');
-      $key_name = $this->getEntityIndexName($entity_type, $revision_key);
-      $schema['unique keys'][$key_name] = [$revision_key];
       $schema['foreign keys'][$entity_type_id . '__revision'] = [
         'table' => $this->storage->getRevisionTable(),
         'columns' => [$revision_key => $revision_key],
@@ -1082,11 +1082,9 @@ protected function initializeBaseTable(ContentEntityTypeInterface $entity_type)
   protected function initializeRevisionTable(ContentEntityTypeInterface $entity_type) {
     $entity_type_id = $entity_type->id();
     $id_key = $entity_type->getKey('id');
-    $revision_key = $entity_type->getKey('revision');
 
     $schema = [
       'description' => "The revision table for $entity_type_id entities.",
-      'primary key' => [$revision_key],
       'indexes' => [],
       'foreign keys' => [
         $entity_type_id . '__revisioned' => [
@@ -1096,8 +1094,6 @@ protected function initializeRevisionTable(ContentEntityTypeInterface $entity_ty
       ],
     ];
 
-    $schema['indexes'][$this->getEntityIndexName($entity_type, $id_key)] = [$id_key];
-
     $this->addTableDefaults($schema);
 
     return $schema;
@@ -1118,10 +1114,6 @@ protected function initializeDataTable(ContentEntityTypeInterface $entity_type)
 
     $schema = [
       'description' => "The data table for $entity_type_id entities.",
-      'primary key' => [$id_key, $entity_type->getKey('langcode')],
-      'indexes' => [
-        $entity_type_id . '__id__default_langcode__langcode' => [$id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')],
-      ],
       'foreign keys' => [
         $entity_type_id => [
           'table' => $this->storage->getBaseTable(),
@@ -1130,11 +1122,6 @@ protected function initializeDataTable(ContentEntityTypeInterface $entity_type)
       ],
     ];
 
-    if ($entity_type->hasKey('revision')) {
-      $key = $entity_type->getKey('revision');
-      $schema['indexes'][$this->getEntityIndexName($entity_type, $key)] = [$key];
-    }
-
     $this->addTableDefaults($schema);
 
     return $schema;
@@ -1156,10 +1143,6 @@ protected function initializeRevisionDataTable(ContentEntityTypeInterface $entit
 
     $schema = [
       'description' => "The revision data table for $entity_type_id entities.",
-      'primary key' => [$revision_key, $entity_type->getKey('langcode')],
-      'indexes' => [
-        $entity_type_id . '__id__default_langcode__langcode' => [$id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')],
-      ],
       'foreign keys' => [
         $entity_type_id => [
           'table' => $this->storage->getBaseTable(),
@@ -1204,7 +1187,23 @@ protected function addTableDefaults(&$schema) {
    *   A partial schema array for the base table.
    */
   protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) {
-    $this->processIdentifierSchema($schema, $entity_type->getKey('id'));
+    // Account for a missing ID key. In theory this should not be necessary, but
+    // we support this for backwards-compatibility. Entity types can be declared
+    // without an ID field (and, thus, also without an ID key) if they are not
+    // stored (for example if they use ContentEntityNullStorage as the storage
+    // handler). Contact messages are an example of this. We then support other
+    // modules adding an ID field, for example via
+    // hook_entity_base_field_info(). Such a module is responsible for adding
+    // ID key to the entity type in its hook_install() implementation. (See
+    // Contact Storage Test for an example of this.) The ID field, however, will
+    // already have been installed at this point by the module installer itself.
+    // And because the last installed entity type is used when building the
+    // entity schema during field installation the entity type will not (yet)
+    // have an ID key when the ID field is being installed.
+    // @todo Remove this in https://www.drupal.org/project/drupal/issues/2929120
+    if ($entity_type->hasKey('id')) {
+      $this->processIdentifierSchema($schema, $entity_type->getKey('id'));
+    }
   }
 
   /**
@@ -1286,6 +1285,8 @@ protected function processFieldStorageSchema(array &$field_storage_schema) {
         unset($field_storage_schema[$table_name]['fields'][$key]['initial']);
         unset($field_storage_schema[$table_name]['fields'][$key]['initial_from_field']);
       }
+
+      $this->addTableDefaults($field_storage_schema[$table_name]);
     }
   }
 
@@ -1358,23 +1359,43 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor
           // Create field columns.
           $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
           if (!$only_save) {
+            // The entity schema needs to be checked because the field schema is
+            // potentially incomplete.
             foreach ($schema[$table_name]['fields'] as $name => $specifier) {
+              // Check if the field is part of the primary keys and pass along
+              // this information when adding the field.
+              // @see \Drupal\Core\Database\Schema::addField()
+              $new_keys = [];
+              if (!empty($schema[$table_name]['primary key']) && in_array($name, $schema[$table_name]['primary key'], TRUE)) {
+                $new_keys = ['primary key' => $schema[$table_name]['primary key']];
+              }
+
               // Check if the field exists because it might already have been
               // created as part of the earlier entity type update event.
               if (!$schema_handler->fieldExists($table_name, $name)) {
-                $schema_handler->addField($table_name, $name, $specifier);
+                $schema_handler->addField($table_name, $name, $specifier, $new_keys);
               }
             }
             if (!empty($schema[$table_name]['indexes'])) {
               foreach ($schema[$table_name]['indexes'] as $name => $specifier) {
+                // When creating a composite index, the field schema of all
+                // participating fields must be passed to the schema handler.
+                $index_schema = $schema[$table_name];
+                foreach ($specifier as $index_key => $index_field) {
+                  // Get the name of the field from the index specification.
+                  $other_column_name = is_array($index_field) ? $index_field[0] : $index_field;
+                  if (!isset($index_schema['fields'][$other_column_name])) {
+                    $this->mergeFieldSchema($index_schema, $table_mapping, $table_name, $other_column_name);
+                  }
+                }
                 // Check if the index exists because it might already have been
                 // created as part of the earlier entity type update event.
-                $this->addIndex($table_name, $name, $specifier, $schema[$table_name]);
+                $this->addIndex($table_name, $name, $specifier, $index_schema);
               }
             }
             if (!empty($schema[$table_name]['unique keys'])) {
               foreach ($schema[$table_name]['unique keys'] as $name => $specifier) {
-                $schema_handler->addUniqueKey($table_name, $name, $specifier);
+                $this->addUniqueKey($table_name, $name, $specifier);
               }
             }
           }
@@ -1439,7 +1460,9 @@ protected function deleteSharedTableSchema(FieldStorageDefinitionInterface $stor
         if ($field_name == $deleted_field_name) {
           $schema = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
 
-          // Drop indexes and unique keys first.
+          // Drop the primary key, indexes and unique keys first.
+          $this->dropPrimaryKey($table_mapping, $table_name, $schema, $column_names);
+
           if (!empty($schema['indexes'])) {
             foreach ($schema['indexes'] as $name => $specifier) {
               $schema_handler->dropIndex($table_name, $name);
@@ -1625,7 +1648,9 @@ protected function updateSharedTableSchema(FieldStorageDefinitionInterface $stor
               }
             }
 
-            // Drop original indexes and unique keys.
+            // Drop the original primary key, indexes and unique keys first.
+            $this->dropPrimaryKey($table_mapping, $table_name, $schema[$table_name], $column_names);
+
             if (!empty($original_schema[$table_name]['indexes'])) {
               foreach ($original_schema[$table_name]['indexes'] as $name => $specifier) {
                 $schema_handler->dropIndex($table_name, $name);
@@ -1636,18 +1661,40 @@ protected function updateSharedTableSchema(FieldStorageDefinitionInterface $stor
                 $schema_handler->dropUniqueKey($table_name, $name);
               }
             }
-            // Create new indexes and unique keys.
+            // Create new primary key, indexes and unique keys.
+            // The entity schema needs to be checked because the field schema is
+            // potentially incomplete.
+            // @todo Remove this in
+            //   https://www.drupal.org/project/drupal/issues/2929120
+            $entity_schema = $this->getEntitySchema($this->entityType);
+            foreach ($schema[$table_name]['fields'] as $name => $specifier) {
+              // Check if the field is part of the primary keys.
+              if (isset($entity_schema[$table_name]['primary key']) && array_intersect($column_names, $entity_schema[$table_name]['primary key'])) {
+                $schema_handler->addPrimaryKey($table_name, $entity_schema[$table_name]['primary key']);
+                break;
+              }
+            }
             if (!empty($schema[$table_name]['indexes'])) {
               foreach ($schema[$table_name]['indexes'] as $name => $specifier) {
+                // When creating a composite index, the field schema of all
+                // participating fields must be passed to the schema handler.
+                $index_schema = $schema[$table_name];
+                foreach ($specifier as $index_key => $index_field) {
+                  // Get the name of the field from the index specification.
+                  $other_column_name = is_array($index_field) ? $index_field[0] : $index_field;
+                  if (!isset($index_schema['fields'][$other_column_name])) {
+                    $this->mergeFieldSchema($index_schema, $table_mapping, $table_name, $other_column_name);
+                  }
+                }
                 // Check if the index exists because it might already have been
                 // created as part of the earlier entity type update event.
-                $this->addIndex($table_name, $name, $specifier, $schema[$table_name]);
+                $this->addIndex($table_name, $name, $specifier, $index_schema);
 
               }
             }
             if (!empty($schema[$table_name]['unique keys'])) {
               foreach ($schema[$table_name]['unique keys'] as $name => $specifier) {
-                $schema_handler->addUniqueKey($table_name, $name, $specifier);
+                $this->addUniqueKey($table_name, $name, $specifier);
               }
             }
             // After deleting the field schema skip to the next table.
@@ -1898,6 +1945,53 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
       $schema['foreign keys'] = $this->getFieldForeignKeys($field_name, $field_schema, $column_mapping);
     }
 
+    // Add primary keys and indexes for key fields.
+    if ($table_name === $this->storage->getBaseTable()) {
+      if ($field_name === $this->entityType->getKey('id')) {
+        $schema['primary key'] = [$this->entityType->getKey('id')];
+      }
+      elseif ($field_name === $this->entityType->getKey('revision')) {
+        $key_name = $this->getEntityIndexName($this->entityType, $this->entityType->getKey('revision'));
+        $schema['unique keys'][$key_name] = [$this->entityType->getKey('revision')];
+      }
+    }
+    elseif ($table_name === $this->storage->getRevisionTable()) {
+      if ($field_name === $this->entityType->getKey('id')) {
+        $schema['indexes'][$this->getEntityIndexName($this->entityType, $this->entityType->getKey('id'))] = [$field_name];
+      }
+      elseif ($field_name === $this->entityType->getKey('revision')) {
+        $schema['primary key'] = [$this->entityType->getKey('revision')];
+      }
+    }
+    elseif ($table_name === $this->storage->getDataTable()) {
+      if (in_array($field_name, [$this->entityType->getKey('id'), $this->entityType->getKey('langcode')], TRUE)) {
+        $schema['primary key'] = [
+          $this->entityType->getKey('id'),
+          $this->entityType->getKey('langcode'),
+        ];
+      }
+      elseif ($field_name === $this->entityType->getKey('revision')) {
+        $key_name = $this->getEntityIndexName($this->entityType, $this->entityType->getKey('revision'));
+        $schema['indexes'][$key_name] = [$this->entityType->getKey('revision')];
+      }
+    }
+    elseif ($table_name === $this->storage->getRevisionDataTable()) {
+      if (in_array($field_name, [$this->entityType->getKey('revision'), $this->entityType->getKey('langcode')], TRUE)) {
+        $schema['primary key'] = [$this->entityType->getKey('revision'), $this->entityType->getKey('langcode')];
+      }
+    }
+    if (in_array($table_name, [$this->storage->getDataTable(), $this->storage->getRevisionDataTable()], TRUE)) {
+      if (in_array($field_name, [$this->entityType->getKey('id'), $this->entityType->getKey('default_langcode'), $this->entityType->getKey('langcode')], TRUE)) {
+        $schema['indexes'][$this->entityType->id() . '__id__default_langcode__langcode'] = [
+          $this->entityType->getKey('id'),
+          $this->entityType->getKey('default_langcode'),
+          $this->entityType->getKey('langcode'),
+        ];
+      }
+    }
+
+    $this->addTableDefaults($schema);
+
     return $schema;
   }
 
@@ -2148,6 +2242,34 @@ protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $stor
     return $dedicated_table_schema;
   }
 
+  /**
+   * Merges schema with the field schema for a given column.
+   *
+   * This looks up the field that the column belongs to and merges the field
+   * schema of that field into the passed-in schema.
+   *
+   * @param array $schema
+   *   The schema to merge the other field schema to.
+   * @param \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping
+   *   The table mapping.
+   * @param string $table_name
+   *   The table name.
+   * @param string $column_name
+   *   The column name to merge the field schema for.
+   */
+  protected function mergeFieldSchema(&$schema, TableMappingInterface $table_mapping, $table_name, $column_name) {
+    foreach ($table_mapping->getFieldNames($table_name) as $search_field_name) {
+      $search_column_names = $table_mapping->getColumnNames($search_field_name);
+      foreach ($search_column_names as $search_column_name) {
+        if ($search_column_name === $column_name) {
+          $other_storage_definition = $this->fieldStorageDefinitions[$search_field_name];
+          $other_schema = $this->getSharedTableFieldSchema($other_storage_definition, $table_name, $search_column_names);
+          $schema = NestedArray::mergeDeepArray([$schema, $other_schema], TRUE);
+        }
+      }
+    }
+  }
+
   /**
    * Gets the name to be used for the given entity index.
    *
@@ -2286,4 +2408,43 @@ protected function addUniqueKey($table, $name, array $specifier) {
     $schema_handler->addUniqueKey($table, $name, $specifier);
   }
 
+  /**
+   * Removes the primary key of a table if the passed columns are part of it.
+   *
+   * @param \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping
+   *   The table mapping.
+   * @param string $table_name
+   *   The name of the table whose primary key is to be deleted.
+   * @param array $schema
+   *   The field schema for this table.
+   * @param string[] $column_names
+   *   The list of columns to check whether they are part of the primary key.
+   *
+   * @internal
+   */
+  private function dropPrimaryKey(TableMappingInterface $table_mapping, $table_name, array $schema, $column_names) {
+    $schema_handler = $this->database->schema();
+    // The entity schema needs to be checked because the field schema is
+    // potentially incomplete.
+    if (array_intersect($column_names, $schema['primary key'])) {
+      // Dropping a primary key must not leave a serial key, so we need to
+      // change any serial field to integer first.
+      foreach ($schema['primary key'] as $column_name) {
+        if ($schema['fields'][$column_name]['type'] === 'serial') {
+          foreach ($table_mapping->getFieldNames($table_name) as $search_field_name) {
+            $search_column_names = $table_mapping->getColumnNames($search_field_name);
+            foreach ($search_column_names as $search_column_name) {
+              if ($search_column_name === $column_name) {
+                $other_storage_definition = $this->fieldStorageDefinitions[$search_field_name];
+                $other_schema = $this->getSharedTableFieldSchema($other_storage_definition, $table_name, $search_column_names);
+                $other_schema['type'] = 'int';
+                $schema_handler->changeField($table_name, $column_name, $column_name, $other_schema);              }
+            }
+          }
+        }
+      }
+      $schema_handler->dropPrimaryKey($table_name);
+    }
+  }
+
 }
diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index 6469245c49..651928462d 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -255,3 +255,24 @@ function node_update_8400() {
   $schema['fields']['realm']['description'] = 'The realm in which the user must possess the grant ID. Modules can define one or more realms by implementing hook_node_grants().';
   Database::getConnection()->schema()->changeField('node_access', 'realm', 'realm', $schema['fields']['realm']);
 }
+
+/**
+ * Update the stored schema data of various node fields.
+ */
+function node_update_8601() {
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+
+  $field_names = [
+    'changed',
+    'created',
+    'promote',
+    'status',
+    'sticky',
+    'title',
+    'type',
+  ];
+  foreach ($field_names as $field_name) {
+    $field_storage_definition = $definition_update_manager->getFieldStorageDefinition($field_name, 'node');
+    $definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
+  }
+}
diff --git a/core/modules/node/src/NodeStorageSchema.php b/core/modules/node/src/NodeStorageSchema.php
index ac45bb5d1e..b0c4f17b14 100644
--- a/core/modules/node/src/NodeStorageSchema.php
+++ b/core/modules/node/src/NodeStorageSchema.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\node;
 
-use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 
@@ -11,20 +10,6 @@
  */
 class NodeStorageSchema extends SqlContentEntityStorageSchema {
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
-    $schema = parent::getEntitySchema($entity_type, $reset);
-
-    $schema['node_field_data']['indexes'] += [
-      'node__frontpage' => ['promote', 'status', 'sticky', 'created'],
-      'node__title_type' => ['title', ['type', 4]],
-    ];
-
-    return $schema;
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -45,22 +30,17 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
     }
 
     if ($table_name == 'node_field_data') {
-      switch ($field_name) {
-        case 'promote':
-        case 'status':
-        case 'sticky':
-        case 'title':
-          // Improves the performance of the indexes defined
-          // in getEntitySchema().
-          $schema['fields'][$field_name]['not null'] = TRUE;
-          break;
-
-        case 'changed':
-        case 'created':
-          // @todo Revisit index definitions:
-          //   https://www.drupal.org/node/2015277.
-          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
-          break;
+      if (in_array($field_name, ['changed', 'created'], TRUE)) {
+        $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+      }
+      if (in_array($field_name, ['promote', 'status', 'sticky', 'title'], TRUE)) {
+        $schema['fields'][$field_name]['not null'] = TRUE;
+      }
+      if (in_array($field_name, ['promote', 'status', 'sticky', 'created'], TRUE)) {
+        $schema['node_field_data']['indexes']['node__frontpage'] = ['promote', 'status', 'sticky', 'created'];
+      }
+      if (in_array($field_name, ['title', 'type'], TRUE)) {
+        $schema['node_field_data']['indexes']['node__title_type'] = ['title', ['type', 4]];
       }
     }
 
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 1838326451..b38c2437c8 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -2135,3 +2135,33 @@ function system_update_8501() {
     }
   }
 }
+
+/**
+ * Update the stored schema data of ID and language fields.
+ */
+function system_update_8601() {
+  $entity_type_manager = \Drupal::entityTypeManager();
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_respository */
+  $entity_last_installed_schema_respository = \Drupal::service('entity.last_installed_schema.repository');
+
+  foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) {
+    $original_entity_type = $entity_last_installed_schema_respository->getLastInstalledDefinition($entity_type_id);
+    if (!$original_entity_type) {
+      continue;
+    }
+    $field_storage_definitions = $entity_last_installed_schema_respository->getLastInstalledFieldStorageDefinitions($entity_type_id);
+
+    $field_names = array_filter([
+      $original_entity_type->getKey('id'),
+      $original_entity_type->getKey('revision'),
+      $original_entity_type->getKey('langcode'),
+      $original_entity_type->getKey('default_langcode'),
+    ]);
+    foreach ($field_names as $field_name) {
+      if (isset($field_storage_definitions[$field_name])) {
+        $definition_update_manager->updateFieldStorageDefinition($field_storage_definitions[$field_name]);
+      }
+    }
+  }
+}
diff --git a/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php b/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php
index 625ee190ed..4b7fccbeb5 100644
--- a/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php
+++ b/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php
@@ -220,6 +220,53 @@ public function testMakeRevisionableErrorHandling() {
     foreach ($new_storage_definitions as $storage_definition) {
       $new_field_schema_data[$storage_definition->getName()] = $this->installedStorageSchema->get('entity_test_update.field_schema_data.' . $storage_definition->getName(), []);
     }
+    // Before https://www.drupal.org/project/drupal/issues/2928906 the field
+    // schema data of integer identifier fields was incorrectly stored as 'int'
+    // instead of 'serial'. So we explicitly make that change before checking
+    // equality.
+    /* @see \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::hasColumnChanges() */
+    $this->assertSame($original_field_schema_data['id']['entity_test_update']['fields']['id']['type'], 'int');
+    $original_field_schema_data['id']['entity_test_update']['fields']['id']['type'] = 'serial';
+    // Also add keys that are stored in the field schema as of
+    // https://www.drupal.org/project/drupal/issues/2928906.
+    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update']['primary key']));
+    $original_field_schema_data['id']['entity_test_update']['primary key'] = ['id'];
+    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update_data']['primary key']));
+    $original_field_schema_data['id']['entity_test_update_data']['primary key'] = ['id', 'langcode'];
+    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode']));
+    $original_field_schema_data['id']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode'] = ['id', 'default_langcode', 'langcode'];
+    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update_data']['primary key']));
+    $original_field_schema_data['langcode']['entity_test_update_data']['primary key'] = ['id', 'langcode'];
+    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode']));
+    $original_field_schema_data['langcode']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode'] = ['id', 'default_langcode', 'langcode'];
+    $this->assertFalse(isset($original_field_schema_data['default_langcode']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode']));
+    $original_field_schema_data['default_langcode']['entity_test_update_data']['indexes']['entity_test_update__id__default_langcode__langcode'] = ['id', 'default_langcode', 'langcode'];
+    // Also account for the fact that the field schema now contains default
+    // values for DX.
+    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update']['unique keys']));
+    $original_field_schema_data['id']['entity_test_update']['unique keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update']['indexes']));
+    $original_field_schema_data['id']['entity_test_update']['indexes'] = [];
+    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update']['foreign keys']));
+    $original_field_schema_data['id']['entity_test_update']['foreign keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update_data']['unique keys']));
+    $original_field_schema_data['id']['entity_test_update_data']['unique keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['id']['entity_test_update_data']['foreign keys']));
+    $original_field_schema_data['id']['entity_test_update_data']['foreign keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update']['unique keys']));
+    $original_field_schema_data['langcode']['entity_test_update']['unique keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update']['indexes']));
+    $original_field_schema_data['langcode']['entity_test_update']['indexes'] = [];
+    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update']['foreign keys']));
+    $original_field_schema_data['langcode']['entity_test_update']['foreign keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update_data']['unique keys']));
+    $original_field_schema_data['langcode']['entity_test_update_data']['unique keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['langcode']['entity_test_update_data']['foreign keys']));
+    $original_field_schema_data['langcode']['entity_test_update_data']['foreign keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['default_langcode']['entity_test_update_data']['unique keys']));
+    $original_field_schema_data['default_langcode']['entity_test_update_data']['unique keys'] = [];
+    $this->assertFalse(isset($original_field_schema_data['default_langcode']['entity_test_update_data']['foreign keys']));
+    $original_field_schema_data['default_langcode']['entity_test_update_data']['foreign keys'] = [];
     $this->assertEqual($original_field_schema_data, $new_field_schema_data);
 
     // Check that temporary tables have been removed.
diff --git a/core/modules/user/src/UserStorageSchema.php b/core/modules/user/src/UserStorageSchema.php
index 5e590b8958..fae75fbb79 100644
--- a/core/modules/user/src/UserStorageSchema.php
+++ b/core/modules/user/src/UserStorageSchema.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\user;
 
-use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 
@@ -11,19 +10,6 @@
  */
 class UserStorageSchema extends SqlContentEntityStorageSchema {
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
-    $schema = parent::getEntitySchema($entity_type, $reset);
-
-    $schema['users_field_data']['unique keys'] += [
-      'user__name' => ['name', 'langcode'],
-    ];
-
-    return $schema;
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -42,24 +28,22 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
     $field_name = $storage_definition->getName();
 
     if ($table_name == 'users_field_data') {
-      switch ($field_name) {
-        case 'name':
-          // Improves the performance of the user__name index defined
-          // in getEntitySchema().
-          $schema['fields'][$field_name]['not null'] = TRUE;
-          // Make sure the field is no longer than 191 characters so we can
-          // add a unique constraint in MySQL.
-          $schema['fields'][$field_name]['length'] = USERNAME_MAX_LENGTH;
-          break;
-
-        case 'mail':
-          $this->addSharedTableFieldIndex($storage_definition, $schema);
-          break;
-
-        case 'access':
-        case 'created':
-          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
-          break;
+      if ($field_name === 'name') {
+        // Improves the performance of the user__name index defined
+        // in getEntitySchema().
+        $schema['fields'][$field_name]['not null'] = TRUE;
+        // Make sure the field is no longer than 191 characters so we can
+        // add a unique constraint in MySQL.
+        $schema['fields'][$field_name]['length'] = UserInterface::USERNAME_MAX_LENGTH;
+      }
+      if ($field_name === 'mail') {
+        $this->addSharedTableFieldIndex($storage_definition, $schema);
+      }
+      if (in_array($field_name, ['access', 'created'], TRUE)) {
+        $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+      }
+      if (in_array($field_name, ['name', 'langcode'], TRUE)) {
+        $schema['users_field_data']['unique keys']['user__name'] = ['name', 'langcode'];
       }
     }
 
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index 0af797a43f..aa0a2a4be6 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -98,3 +98,16 @@ function user_update_8100() {
     $config->set('status_blocked', $mail)->save(TRUE);
   }
 }
+
+/**
+ * Update the stored schema data of various node fields.
+ */
+function user_update_8601() {
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+
+  $field_storage_definition = $definition_update_manager->getFieldStorageDefinition('name', 'user');
+  $definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
+
+  $entity_type = $definition_update_manager->getEntityType('user');
+  $definition_update_manager->updateEntityType($entity_type);
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Database/AssertSchemaTrait.php b/core/tests/Drupal/KernelTests/Core/Database/AssertSchemaTrait.php
new file mode 100644
index 0000000000..8ba2564707
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Database/AssertSchemaTrait.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Database;
+
+use Drupal\Core\Database\Database;
+
+/**
+ * Provides a trait for schema-related assertions in tests.
+ */
+trait AssertSchemaTrait {
+
+  /**
+   * Tests the primary keys of a table.
+   *
+   * @param string $table_name
+   *   The name of the table to check.
+   * @param array $primary_key
+   *   The expected key column specifier for a table's primary key.
+   */
+  protected function assertPrimaryKeyColumns($table_name, array $primary_key = []) {
+    $connection = Database::getConnection();
+    $db_type = $connection->databaseType();
+
+    switch ($db_type) {
+      case 'mysql':
+        $result = $connection->query("SHOW KEYS FROM {" . $table_name . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
+        $this->assertSame($primary_key, array_keys($result));
+
+        break;
+      case 'pgsql':
+        $result = $connection->query("SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type
+          FROM pg_index i
+          JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
+          WHERE i.indrelid = '{" . $table_name . "}'::regclass AND i.indisprimary")
+          ->fetchAllAssoc('attname');
+        $this->assertSame($primary_key, array_keys($result));
+
+        break;
+      case 'sqlite':
+        // For SQLite we need access to the protected
+        // \Drupal\Core\Database\Driver\sqlite\Schema::introspectSchema() method
+        // because we have no other way of getting the table prefixes needed for
+        // running a straight PRAGMA query.
+        $schema_object = $connection->schema();
+        $reflection = new \ReflectionMethod($schema_object, 'introspectSchema');
+        $reflection->setAccessible(TRUE);
+
+        $table_info = $reflection->invoke($schema_object, $table_name);
+        $this->assertSame($primary_key, $table_info['primary key']);
+
+        break;
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
index e239098715..608c748606 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
@@ -16,6 +16,8 @@
  */
 class SchemaTest extends KernelTestBase {
 
+  use AssertSchemaTrait;
+
   /**
    * A global counter for table and field creation.
    */
@@ -826,46 +828,4 @@ public function testFindTables() {
     Database::setActiveConnection('default');
   }
 
-  /**
-   * Tests the primary keys of a table.
-   *
-   * @param string $table_name
-   *   The name of the table to check.
-   * @param array $primary_key
-   *   The expected key column specifier for a table's primary key.
-   */
-  protected function assertPrimaryKeyColumns($table_name, array $primary_key = []) {
-    $db_type = Database::getConnection()->databaseType();
-
-    switch ($db_type) {
-      case 'mysql':
-        $result = Database::getConnection()->query("SHOW KEYS FROM {" . $table_name . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
-        $this->assertSame($primary_key, array_keys($result));
-
-        break;
-      case 'pgsql':
-        $result = Database::getConnection()->query("SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type
-          FROM pg_index i
-          JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
-          WHERE i.indrelid = '{" . $table_name . "}'::regclass AND i.indisprimary")
-          ->fetchAllAssoc('attname');
-        $this->assertSame($primary_key, array_keys($result));
-
-        break;
-      case 'sqlite':
-        // For SQLite we need access to the protected
-        // \Drupal\Core\Database\Driver\sqlite\Schema::introspectSchema() method
-        // because we have no other way of getting the table prefixes needed for
-        // running a straight PRAGMA query.
-        $schema_object = Database::getConnection()->schema();
-        $reflection = new \ReflectionMethod($schema_object, 'introspectSchema');
-        $reflection->setAccessible(TRUE);
-
-        $table_info = $reflection->invoke($schema_object, $table_name);
-        $this->assertSame($primary_key, $table_info['primary key']);
-
-        break;
-    }
-  }
-
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntitySchemaTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntitySchemaTest.php
index 3e285ff69b..a31228eff8 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntitySchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntitySchemaTest.php
@@ -3,14 +3,19 @@
 namespace Drupal\KernelTests\Core\Entity;
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\KernelTests\Core\Database\AssertSchemaTrait;
 
 /**
- * Tests adding a custom bundle field.
+ * Tests the default entity storage schema handler.
  *
- * @group system
+ * @group Entity
  */
 class EntitySchemaTest extends EntityKernelTestBase {
 
+  use AssertSchemaTrait;
+
   /**
    * The database connection used.
    *
@@ -109,6 +114,163 @@ public function testEntitySchemaUpdate() {
     $this->assertTrue($schema_handler->tableExists($dedicated_tables[0]), SafeMarkup::format('Field schema correct for the @table table.', ['@table' => $table]));
   }
 
+  /**
+   * Tests deleting and creating a field that is part of a primary key.
+   *
+   * @param string $entity_type_id
+   *   The ID of the entity type whose schema is being tested.
+   * @param string $field_name
+   *   The name of the field that is being re-installed.
+   *
+   * @dataProvider providerTestPrimaryKeyUpdate
+   */
+  public function testPrimaryKeyUpdate($entity_type_id, $field_name) {
+    // EntityKernelTestBase::setUp() already installs the schema for the
+    // 'entity_test' entity type.
+    if ($entity_type_id !== 'entity_test') {
+      $this->installEntitySchema($entity_type_id);
+    }
+
+    /* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $update_manager */
+    $update_manager = $this->container->get('entity.definition_update_manager');
+    $entity_type = $update_manager->getEntityType($entity_type_id);
+
+    /* @see \Drupal\Core\Entity\ContentEntityBase::baseFieldDefinitions() */
+    switch ($field_name) {
+      case 'id':
+        $field = BaseFieldDefinition::create('integer')
+          ->setLabel('ID')
+          ->setReadOnly(TRUE)
+          ->setSetting('unsigned', TRUE);
+        break;
+
+      case 'revision_id':
+        $field = BaseFieldDefinition::create('integer')
+          ->setLabel('Revision ID')
+          ->setReadOnly(TRUE)
+          ->setSetting('unsigned', TRUE);
+        break;
+
+      case 'langcode':
+        $field = BaseFieldDefinition::create('language')
+          ->setLabel('Language');
+        if ($entity_type->isRevisionable()) {
+          $field->setRevisionable(TRUE);
+        }
+        if ($entity_type->isTranslatable()) {
+          $field->setTranslatable(TRUE);
+        }
+        break;
+    }
+
+    $field
+      ->setName($field_name)
+      ->setTargetEntityTypeId($entity_type_id)
+      ->setProvider($entity_type->getProvider());
+
+    // First test explicitly deleting and re-installing a field.
+    // Make sure all primary keys are there to start with.
+    $this->assertPrimaryKeys($entity_type);
+    // Then uninstall the field and make sure all primary keys that the field
+    // is part of have been removed.
+    $update_manager->uninstallFieldStorageDefinition($field);
+    $this->assertPrimaryKeys($entity_type, $field_name);
+    // Finally reinstall the field and make sure the primary keys have been
+    // recreated.
+    $update_manager->installFieldStorageDefinition($field->getName(), $entity_type_id, $field->getProvider(), $field);
+    $this->assertPrimaryKeys($entity_type);
+
+    // Now test updating a field without data. This will end up deleting
+    // and re-creating the field, similar to the code above.
+    $update_manager->updateFieldStorageDefinition($field);
+    $this->assertPrimaryKeys($entity_type);
+
+    // Now test updating a field with data.
+    /* @var \Drupal\Core\Entity\FieldableEntityStorageInterface $storage */
+    $storage = $this->entityManager->getStorage($entity_type_id);
+    // The schema of ID fields is incorrectly recreated as 'int' instead of
+    // 'serial', so we manually have to specify an ID.
+    // @todo Remove this in https://www.drupal.org/project/drupal/issues/2928906
+    $storage->create(['id' => 1, 'revision_id' => 1])->save();
+    $this->assertTrue($storage->countFieldData($field, TRUE));
+    $update_manager->updateFieldStorageDefinition($field);
+    $this->assertPrimaryKeys($entity_type);
+    $this->assertTrue($storage->countFieldData($field, TRUE));
+  }
+
+  /**
+   * Asserts that the primary keys are as expected for a given entity type.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type whose primary keys are being checked.
+   * @param string $removed_field
+   *   (optional) The name of a field that has been removed. If passed, any
+   *   primary key that would otherwise have contained this field will be
+   *   checked to have been dropped.
+   */
+  protected function assertPrimaryKeys(EntityTypeInterface $entity_type, $removed_field = NULL) {
+    $base_table = $entity_type->getBaseTable();
+    $revision_table = $entity_type->getRevisionTable();
+    $data_table = $entity_type->getDataTable();
+    $revision_data_table = $entity_type->getRevisionDataTable();
+
+    $id_key = $entity_type->getKey('id');
+    $revision_key = $entity_type->getKey('revision');
+    $langcode_key = $entity_type->getKey('langcode');
+
+    // Build up a map of primary keys depending on the entity type
+    // configuration.
+    $key_map = [];
+    $key_map[$base_table] = [$id_key];
+    if ($entity_type->isRevisionable()) {
+      $key_map[$revision_table] = [$revision_key];
+    }
+    if ($entity_type->isTranslatable()) {
+      $key_map[$data_table] = [$id_key, $langcode_key];
+    }
+    if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
+      $key_map[$revision_data_table] = [$revision_key, $langcode_key];
+    }
+
+    foreach ($key_map as $table => $primary_key) {
+      // Assert that the primary key has been dropped if a field name was passed
+      // and the given primary key contains this field.
+      if (!$removed_field || !in_array($removed_field, $primary_key, TRUE)) {
+        $this->assertPrimaryKeyColumns($table, $primary_key);
+      }
+      else {
+        $this->assertPrimaryKeyColumns($table, []);
+      }
+    }
+  }
+
+  /**
+   * Provides test cases for EntitySchemaTest::testPrimaryKeyUpdate()
+   *
+   * @return array
+   *   An array of test cases consisting of an entity type ID and a field name.
+   */
+  public function providerTestPrimaryKeyUpdate() {
+    // Build up test cases for all possible entity type configurations.
+    // For each entity type we test reinstalling each field that is part of
+    // any table's primary key.
+    $tests = [];
+
+    $tests['entity_test:id'] = ['entity_test', 'id'];
+
+    $tests['entity_test_rev:id'] = ['entity_test_rev', 'id'];
+    $tests['entity_test_rev:revision_id'] = ['entity_test_rev', 'revision_id'];
+
+    $tests['entity_test_mul:id'] = ['entity_test_mul', 'id'];
+    $tests['entity_test_mul:langcode'] = ['entity_test_mul', 'langcode'];
+
+    $tests['entity_test_mulrev:id'] = ['entity_test_mulrev', 'id'];
+    $tests['entity_test_mulrev:revision_id'] = ['entity_test_mulrev', 'revision_id'];
+    $tests['entity_test_mulrev:langcode'] = ['entity_test_mulrev', 'langcode'];
+
+    return $tests;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
index d16ece2097..56852869e5 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
@@ -397,7 +397,7 @@ public function testGetSchemaRevisionable() {
       ],
     ]);
 
-    $this->storage->expects($this->exactly(2))
+    $this->storage->expects($this->exactly(6))
       ->method('getRevisionTable')
       ->will($this->returnValue('entity_test_revision'));
 
@@ -604,13 +604,13 @@ public function testGetSchemaRevisionableTranslatable() {
       ],
     ]);
 
-    $this->storage->expects($this->exactly(3))
+    $this->storage->expects($this->exactly(25))
       ->method('getRevisionTable')
       ->will($this->returnValue('entity_test_revision'));
-    $this->storage->expects($this->once())
+    $this->storage->expects($this->exactly(45))
       ->method('getDataTable')
       ->will($this->returnValue('entity_test_field_data'));
-    $this->storage->expects($this->once())
+    $this->storage->expects($this->exactly(37))
       ->method('getRevisionDataTable')
       ->will($this->returnValue('entity_test_revision_field_data'));
 
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
index bf3fd46eb6..d7e23a1d87 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
@@ -327,6 +327,14 @@ public function testOnEntityTypeCreate() {
     $this->entityType->expects($this->once())
       ->method('getKeys')
       ->will($this->returnValue(['id' => 'id']));
+    $this->entityType->expects($this->any())
+      ->method('hasKey')
+      ->will($this->returnValueMap([
+        // SqlContentEntityStorageSchema::initializeBaseTable()
+        ['revision', FALSE],
+        // SqlContentEntityStorageSchema::processBaseTable()
+        ['id', TRUE],
+      ]));
     $this->entityType->expects($this->any())
       ->method('getKey')
       ->will($this->returnValueMap([
diff --git a/test.php b/test.php
new file mode 100644
index 0000000000..740c90403a
--- /dev/null
+++ b/test.php
@@ -0,0 +1,13 @@
+<?php
+
+$aggregate_sum = \Drupal::entityQueryAggregate('commerce_product_variation')
+  ->aggregate('price.number', 'SUM', NULL)
+  ->execute();
+
+drush_print_r($aggregate_sum);
+
+$query = \Drupal::database()->select('commerce_product_variation_field_data');
+$query->addExpression('price__number + :summand', NULL, [':summand' => 0.1]);
+$sum = $query->execute()->fetchAll();
+
+drush_print_r($sum);
\ No newline at end of file
