diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index 110fcee..ff45a17 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -306,6 +306,14 @@ public function hasKey($key) {
   /**
    * {@inheritdoc}
    */
+  public function setKey($key, $value) {
+    $this->entity_keys[$key] = $value;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function id() {
     return $this->id;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index e655df7..b73de24 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -141,6 +141,18 @@ public function getKey($key);
   public function hasKey($key);
 
   /**
+   * Sets a specific entity key.
+   *
+   * @param string $key
+   *   The name of the entity key to return.
+   * @param string $value
+   *   The new value of the key.
+   *
+   * @return $this
+   */
+  public function setKey($key, $value);
+
+  /**
    * Indicates whether entities should be statically cached.
    *
    * @return bool
diff --git a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php
index 4f30d47..ba8ab66 100644
--- a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php
+++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php
@@ -26,7 +26,7 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface {
   /**
    * The storage field definitions for this entity type.
    *
-   * @var \Drupal\Core\Field\FieldDefinitionInterface[]
+   * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
    */
   protected $fieldStorageDefinitions;
 
diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php
index 48745d8..73b88d2 100644
--- a/core/modules/file/src/Entity/File.php
+++ b/core/modules/file/src/Entity/File.php
@@ -25,7 +25,7 @@
  *     "storage" = "Drupal\file\FileStorage",
  *     "access" = "Drupal\file\FileAccessControlHandler",
  *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
- *     "views_data" = "Drupal\file\FileViewsData",
+ *     "views_data" = "Drupal\file\FileViewsData"
  *   },
  *   base_table = "file_managed",
  *   entity_keys = {
diff --git a/core/modules/file/src/FileViewsData.php b/core/modules/file/src/FileViewsData.php
index 1c6e524..5c5fee7 100644
--- a/core/modules/file/src/FileViewsData.php
+++ b/core/modules/file/src/FileViewsData.php
@@ -6,32 +6,29 @@
  */
 
 namespace Drupal\file;
-
-use Drupal\views\EntityViewsDataInterface;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\views\EntityViewsData;
 
 /**
  * Provides views data for the file entity type.
  */
-class FileViewsData implements EntityViewsDataInterface {
+class FileViewsData extends EntityViewsData {
 
   /**
    * {@inheritdoc}
    */
   public function getViewsData() {
-    $data = array();
+    $parent_views_data = parent::getViewsData();
     // Sets 'group' index for file_managed table.
-    $data['file_managed']['table']['group']  = t('File');
 
     // Advertise this table as a possible base table.
     $data['file_managed']['table']['base'] = array(
-      'field' => 'fid',
-      'title' => t('File'),
+      // @TODO There is no corresponding information in entity metadata.
       'help' => t("Files maintained by Drupal and various modules."),
       'defaults' => array(
         'field' => 'filename'
       ),
     );
-    $data['file_managed']['table']['entity type'] = 'file';
     $data['file_managed']['table']['wizard_id'] = 'file_managed';
 
     // Describes fid field in file_managed table.
@@ -47,12 +44,6 @@ public function getViewsData() {
         'name field' => 'filename',
         'numeric' => TRUE,
       ),
-      'filter' => array(
-        'id' => 'numeric',
-      ),
-      'sort' => array(
-        'id' => 'standard',
-      ),
       'relationship' => array(
         'title' => t('File usage'),
         'help' => t('Relate file entities to their usage.'),
@@ -66,56 +57,23 @@ public function getViewsData() {
 
     // Describes filename field in file_managed table.
     $data['file_managed']['filename'] = array(
-      'title' => t('Name'),
-      'help' => t('The name of the file.'),
       'field' => array(
         'id' => 'file',
        ),
-      'sort' => array(
-        'id' => 'standard',
-      ),
-      'filter' => array(
-        'id' => 'string',
-      ),
-      'argument' => array(
-        'id' => 'string',
-      ),
     );
 
     // Describes uri field in file_managed table.
     $data['file_managed']['uri'] = array(
-      'title' => t('Path'),
-      'help' => t('The path of the file.'),
       'field' => array(
         'id' => 'file_uri',
        ),
-      'sort' => array(
-        'id' => 'standard',
-      ),
-      'filter' => array(
-        'id' => 'string',
-      ),
-      'argument' => array(
-        'id' => 'string',
-      ),
     );
 
     // Describes filemime field in file_managed table.
     $data['file_managed']['filemime'] = array(
-      'title' => t('Mime type'),
-      'help' => t('The mime type of the file.'),
       'field' => array(
         'id' => 'file_filemime',
        ),
-      'sort' => array(
-        'id' => 'standard',
-      ),
-      'filter' => array(
-        'id' => 'string',
-      ),
-      'argument' => array(
-        'id' => 'string',
-      ),
     );
 
     // Describes extension field in file_managed table.
@@ -131,73 +89,26 @@ public function getViewsData() {
 
     // Describes filesize field in file_managed table.
     $data['file_managed']['filesize'] = array(
-      'title' => t('Size'),
-      'help' => t('The size of the file.'),
       'field' => array(
         'id' => 'file_size',
        ),
-      'sort' => array(
-        'id' => 'standard',
-      ),
-      'filter' => array(
-        'id' => 'numeric',
-      ),
     );
 
     // Describes status field in file_managed table.
     $data['file_managed']['status'] = array(
-      'title' => t('Status'),
-      'help' => t('The status of the file.'),
       'field' => array(
         'id' => 'file_status',
        ),
-      'sort' => array(
-        'id' => 'standard',
-      ),
       'filter' => array(
         'id' => 'file_status',
       ),
     );
 
-    // Describes created field in file_managed table.
-    $data['file_managed']['created'] = array(
-      'title' => t('Upload date'),
-      'help' => t('The date the file was uploaded.'),
-      'field' => array(
-        'id' => 'date',
-      ),
-      'sort' => array(
-        'id' => 'date',
-      ),
-      'filter' => array(
-        'id' => 'date',
-      ),
-    );
-
-    // Describes changed field in file_managed table.
-    $data['file_managed']['changed'] = array(
-      'title' => t('Modified date'),
-      'help' => t('The date the file was last changed.'),
-      'field' => array(
-        'id' => 'date',
-      ),
-      'sort' => array(
-        'id' => 'date',
-      ),
-      'filter' => array(
-        'id' => 'date',
-      ),
-    );
-
     // Describes uid field in file_managed table.
     $data['file_managed']['uid'] = array(
-      'title' => t('User who uploaded'),
-      'help' => t('The user that uploaded the file.'),
       'relationship' => array(
         'title' => t('User who uploaded'),
         'label' => t('User who uploaded'),
-        'base' => 'users',
-        'base field' => 'uid',
       ),
     );
 
@@ -495,8 +406,7 @@ public function getViewsData() {
       ),
     );
 
-    return $data;
+    return NestedArray::mergeDeep($parent_views_data, $data);
   }
 
 }
-
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
index 2470c8c..e791497 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
@@ -29,7 +29,8 @@
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "views_data" = "Drupal\views\EntityViewsData"
  *   },
  *   base_table = "entity_test",
  *   fieldable = TRUE,
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
index a61f41b..bbdedaa 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
@@ -24,7 +24,8 @@
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "views_data" = "Drupal\views\EntityViewsData"
  *   },
  *   base_table = "entity_test_mul",
  *   data_table = "entity_test_mul_property_data",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index f4ee307..10d3730 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -23,7 +23,8 @@
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "views_data" = "Drupal\views\EntityViewsData"
  *   },
  *   base_table = "entity_test_mulrev",
  *   data_table = "entity_test_mulrev_property_data",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
index 5f74c59..2ebc858 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
@@ -23,7 +23,8 @@
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "views_data" = "Drupal\views\EntityViewsData"
  *   },
  *   base_table = "entity_test_rev",
  *   revision_table = "entity_test_rev_revision",
diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php
new file mode 100644
index 0000000..3533964
--- /dev/null
+++ b/core/modules/views/src/EntityViewsData.php
@@ -0,0 +1,318 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\EntityViewsData.
+ */
+
+namespace Drupal\views;
+
+use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
+use Drupal\Core\Entity\Sql\TableMappingInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\TypedData\TypedDataManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides generic views integration for entities.
+ */
+class EntityViewsData implements EntityControllerInterface, EntityViewsDataInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * Entity type for this views controller instance.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $entityType;
+
+  /**
+   * The storage controller used for this entity type.
+   *
+   * @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface
+   */
+  protected $storageController;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $translationManager;
+
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedDataManager;
+
+  /**
+   * The field storage definitions for all base fields of the entity type.
+   *
+   * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
+   */
+  protected $fieldStorageDefinitions;
+
+  /**
+   * Constructs an EntityViewsData object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type to provide views integration for.
+   * @param \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage_controller
+   *   The storage controller used for this entity type.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
+   *   The translation manager.
+   */
+  function __construct(EntityTypeInterface $entity_type, SqlEntityStorageInterface $storage_controller, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, TranslationInterface $translation_manager, TypedDataManager $typed_data_manager) {
+    $this->entityType = $entity_type;
+    $this->entityManager = $entity_manager;
+    $this->storageController = $storage_controller;
+    $this->moduleHandler = $module_handler;
+    $this->setStringTranslation($translation_manager);
+    $this->typedDataManager = $typed_data_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity.manager')->getController($entity_type->id(), 'storage'),
+      $container->get('entity.manager'),
+      $container->get('module_handler'),
+      $container->get('string_translation'),
+      $container->get('typed_data_manager')
+    );
+  }
+
+  /**
+   * Gets the field storage definitions.
+   *
+   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
+   */
+  protected function getFieldStorageDefinitions() {
+    if (!isset($this->fieldStorageDefinitions)) {
+      $this->fieldStorageDefinitions = $this->entityManager->getFieldStorageDefinitions($this->entityType->id());
+    }
+    return $this->fieldStorageDefinitions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getViewsData() {
+    $data = array();
+
+    // @todo In theory we should use the data table as base table, as this would
+    //   save one pointless join (and one more for every relationship).
+    $base_table = $this->entityType->getBaseTable();
+    $base_field = $this->entityType->getKey('id');
+    $data_table = $this->entityType->getDataTable();
+    $revision_table = $this->entityType->getRevisionTable();
+    $revision_data_table = $this->entityType->getRevisionDataTable();
+    $revision_field = $this->entityType->getKey('revision');
+
+    // Setup base information of the views data.
+    $data[$base_table]['table']['entity type'] = $this->entityType->id();
+    $data[$base_table]['table']['group'] = $this->entityType->getLabel();
+    $data[$base_table]['table']['base'] = array(
+      'field' => $base_field,
+      'title' => $this->entityType->getLabel(),
+    );
+
+    // Setup relations to the revisions/property data.
+    if ($data_table) {
+      $data[$data_table]['table']['join'][$base_table] = array(
+        'left_field' => $base_field,
+        'field' => $base_field
+      );
+
+    }
+    if ($revision_table) {
+      $data[$revision_table]['table']['entity_type_id'] = $this->entityType->id();
+      $data[$revision_table]['table']['group'] = $this->entityType->getLabel();
+      $data[$revision_table]['table']['base'] = array(
+        'field' => $revision_field,
+        'title' => $this->t('@entity_type revisions', array('@entity_type' => $this->entityType->getLabel())),
+      );
+      // Join the revision table to the base table.
+      $data[$revision_table]['table']['join'][$base_table] = array(
+        'left_field' => $base_field,
+        'field' => $base_field,
+      );
+
+      if ($revision_data_table) {
+        $data[$revision_data_table]['table']['join'][$revision_table] = array(
+          'left_field' => $revision_field,
+          'field' => $revision_field,
+        );
+      }
+    }
+
+    // Load all typed data definitions of all fields. This should cover each of
+    // the entity base, revision, data tables.
+    $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id());
+    if ($table_mapping = $this->storageController->getTableMapping()) {
+      // 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 manager.
+      // @todo We should better just rely on information coming from the entity
+      //   storage.
+      foreach ($table_mapping->getTableNames() as $table) {
+        foreach ($table_mapping->getFieldNames($table) as $field_name) {
+          $this->mapFieldDefinition($field_name, $field_definitions[$field_name], $table_mapping, $data[$table]);
+        }
+      }
+    }
+
+    return $data;
+  }
+
+  protected function mapFieldDefinition($field_name, FieldDefinitionInterface $field_definition, TableMappingInterface $table_mapping, &$views_table_data) {
+    // Create a dummy instance to retrieve property definitions.
+    $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.
+    $first = TRUE;
+    foreach ($field_column_mapping as $field_column_name => $schema_field_name) {
+      $schema = $field_schema['columns'][$field_column_name];
+      if ($first) {
+        $first = FALSE;
+        $views_table_data[$field_name] = $this->mapSingleFieldViewsData($field_definition_type, $schema_field_name, $field_definition);
+      }
+      else {
+        $views_table_data["$field_name.$field_column_name"] = $this->mapSingleFieldViewsData($schema['type'], $schema_field_name, $field_definition);
+      }
+    }
+  }
+
+  /**
+   * Provides a mapping from typed data plugin types to view plugin types.
+   *
+   * @return array
+   *   The modified views data field definition.
+   */
+  protected function mapSingleFieldViewsData($data_type, $schema_field_name, FieldDefinitionInterface $field_definition) {
+    $views_field = array();
+
+    $views_field['title'] = $field_definition->getLabel() . " ($schema_field_name)";
+
+    if ($description = $field_definition->getDescription()) {
+      $views_field['help'] = $description;
+    }
+
+    // @todo Allow field types to customize this.
+    switch ($data_type) {
+      case 'int':
+      case 'integer':
+      case 'smallint':
+      case 'tinyint':
+      case 'mediumint':
+      case 'float':
+      case 'double':
+      case 'decimal':
+        $views_field['field']['id'] = 'numeric';
+        $views_field['argument']['id'] = 'numeric';
+        $views_field['filter']['id'] = 'numeric';
+        $views_field['sort']['id'] = 'standard';
+        break;
+      case 'char':
+      case 'string':
+      case 'varchar':
+      case 'tinytext':
+      case 'text':
+      case 'mediumtext':
+      case 'longtext':
+        $views_field['field']['id'] = 'standard';
+        $views_field['argument']['id'] = 'string';
+        $views_field['filter']['id'] = 'string';
+        $views_field['sort']['id'] = 'standard';
+        break;
+      case 'boolean':
+        $views_field['field']['id'] = 'boolean';
+        $views_field['argument']['id'] = 'numeric';
+        $views_field['filter']['id'] = 'boolean';
+        $views_field['sort']['id'] = 'standard';
+        break;
+      case 'uuid':
+        $views_field['field']['id'] = 'standard';
+        $views_field['argument']['id'] = 'string';
+        $views_field['filter']['id'] = 'string';
+        $views_field['sort']['id'] = 'standard';
+        break;
+      case 'language':
+        $views_field['field']['id'] = 'language';
+        $views_field['argument']['id'] = 'language';
+        $views_field['filter']['id'] = 'language';
+        $views_field['sort']['id'] = 'standard';
+        break;
+      case 'created':
+      case 'changed':
+      $views_field['field']['id'] = 'date';
+      $views_field['argument']['id'] = 'date';
+      $views_field['filter']['id'] = 'date';
+      $views_field['sort']['id'] = 'date';
+        break;
+      case 'entity_reference':
+        // @todo No idea to determine how to find out whether the field is a number/string ID.
+        // @todo Should the actual field handler respect that this is just renders a number
+        // @todo Create an optional entity field handler, that can render the
+        //   entity.
+        $views_field['field']['id'] = 'numeric';
+        $views_field['argument']['id'] = 'numeric';
+        $views_field['filter']['id'] = 'numeric';
+        $views_field['sort']['id'] = 'standard';
+        if ($entity_type_id = $field_definition->getItemDefinition()->getSetting('target_type')) {
+          $entity_type = $this->entityManager->getDefinition($entity_type_id);
+          $views_field['relationship'] = array(
+            'base' => $this->getViewsTableForEntityType($entity_type),
+            'base field' => $entity_type->getKey('id'),
+            'label' => $entity_type->getLabel(),
+            'title' => $entity_type->getLabel(),
+            'id' => 'standard',
+          );
+        }
+        break;
+      case 'uri':
+        $views_field['field']['id'] = 'standard';
+        $views_field['argument']['id'] = 'string';
+        $views_field['filter']['id'] = 'string';
+        $views_field['sort']['id'] = 'standard';
+        break;
+      default:
+        $views_field['field']['id'] = 'standard';
+        $views_field['argument']['id'] = 'standard';
+        $views_field['filter']['id'] = 'standard';
+        $views_field['sort']['id'] = 'standard';
+    }
+
+    return $views_field;
+  }
+
+  public function getViewsTableForEntityType(EntityTypeInterface $entity_type) {
+    return $entity_type->getBaseTable();
+  }
+
+}
diff --git a/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php b/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php
new file mode 100644
index 0000000..5f0a8c7
--- /dev/null
+++ b/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php
@@ -0,0 +1,567 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Tests\EntityViewsDataTest.
+ */
+
+namespace Drupal\views\Tests {
+
+use Drupal\Core\Entity\ContentEntityType;
+use Drupal\Core\Entity\EntityType;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
+use Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem;
+use Drupal\Core\Field\Plugin\Field\FieldType\LanguageItem;
+use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
+use Drupal\Core\Field\Plugin\Field\FieldType\UuidItem;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\entity_test\Entity\EntityTestMul;
+use Drupal\entity_test\Entity\EntityTestMulRev;
+use Drupal\Tests\UnitTestCase;
+use Drupal\views\EntityViewsData;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+  /**
+ * @coversDefaultClass \Drupal\views\EntityViewsData
+ * @group Views
+ */
+class EntityViewsDataTest extends UnitTestCase {
+
+  /**
+   * Entity info to use in this test.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $baseEntityType;
+
+  /**
+   * The mocked entity storage.
+   *
+   * @var \Drupal\Core\Entity\ContentEntityDatabaseStorage|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityStorage;
+
+  /**
+   * The mocked entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityManager;
+
+  /**
+   * The mocked module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The mocked translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $translationManager;
+
+  /**
+   * The tested entity views controller.
+   *
+   * @var \Drupal\views\Tests\TestEntityViewsData
+   */
+  protected $viewsData;
+  protected $typedDataManager;
+
+  protected function setUp() {
+    $this->entityStorage = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityDatabaseStorage')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+
+    $this->baseEntityType = new EntityType([
+      'base_table' => 'entity_test',
+      'id' => 'entity_test',
+      'label' => 'Entity test',
+      'entity_keys' => ['id' => 'id'],
+    ]);
+
+    $this->translationManager = $this->getStringTranslationStub();
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->typedDataManager = $this->getMockBuilder('Drupal\Core\TypedData\TypedDataManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->viewsData = new TestEntityViewsData($this->baseEntityType, $this->entityStorage, $this->entityManager, $this->moduleHandler, $this->translationManager, $this->typedDataManager);
+
+    $field_type_manager = $this->getMockBuilder('Drupal\Core\Field\FieldTypePluginManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $field_type_manager->expects($this->any())
+      ->method('getDefaultSettings')
+      ->willReturn([]);
+    $field_type_manager->expects($this->any())
+      ->method('getDefaultInstanceSettings')
+      ->willReturn([]);
+
+    $container = new ContainerBuilder();
+    $container->set('plugin.manager.field.field_type', $field_type_manager);
+    $container->set('entity.manager', $this->entityManager);
+    \Drupal::setContainer($container);
+  }
+
+  /**
+   * Tests base tables.
+   */
+  public function testBaseTables() {
+    $data = $this->viewsData->getViewsData();
+
+    $this->assertEquals('entity_test', $data['entity_test']['table']['entity type']);
+    $this->assertEquals('Entity test', $data['entity_test']['table']['group']);
+
+    $this->assertEquals('id', $data['entity_test']['table']['base']['field']);
+    $this->assertEquals('Entity test', $data['entity_test']['table']['base']['title']);
+
+    $this->assertFalse(isset($data['entity_test_mul_property_data']));
+    $this->assertFalse(isset($data['revision_table']));
+    $this->assertFalse(isset($data['revision_data_table']));
+  }
+
+
+  /**
+   * Tests data_table support.
+   */
+  public function testDataTable() {
+    $entity_type = $this->baseEntityType->set('data_table', 'entity_test_mul_property_data')
+      ->set('id', 'entity_test_mul');
+
+    $this->viewsData->setEntityType($entity_type);
+
+    // Tests the join definition between the base and the data table.
+    $data = $this->viewsData->getViewsData();
+    $field_views_data = $data['entity_test_mul_property_data'];
+
+    // Ensure the join information is set up properly.
+    $this->assertCount(1, $field_views_data['table']['join']);
+    $this->assertEquals(['entity_test' => ['left_field' => 'id', 'field' => 'id']], $field_views_data['table']['join']);
+    $this->assertFalse(isset($data['revision_table']));
+    $this->assertFalse(isset($data['revision_data_table']));
+  }
+
+  /**
+   * Tests revision table support.
+   */
+  public function testRevisionTable() {
+    $entity_type = $this->baseEntityType
+      ->set('revision_table', 'entity_test_mulrev_revision')
+      ->set('revision_data_table', 'entity_test_mulrev_property_revision')
+      ->set('id', 'entity_test_mulrev')
+      ->setKey('revision', 'revision_id')
+    ;
+    $this->viewsData->setEntityType($entity_type);
+
+    $data = $this->viewsData->getViewsData();
+
+    // Ensure the join information is set up properly.
+    // Tests the join definition between the base and the revision table.
+    $revision_data = $data['entity_test_mulrev_revision'];
+    $this->assertCount(1, $revision_data['table']['join']);
+    $this->assertEquals(['entity_test' => ['left_field' => 'id', 'field' => 'id']], $revision_data['table']['join']);
+    $revision_data = $data['entity_test_mulrev_property_revision'];
+    $this->assertCount(1, $revision_data['table']['join']);
+    $this->assertEquals(['entity_test_mulrev_revision' => ['left_field' => 'revision_id', 'field' => 'revision_id']], $revision_data['table']['join']);
+    $this->assertFalse(isset($data['data_table']));
+  }
+
+  /**
+   * Helper method to mock all store definitions.
+   */
+  protected function setupFieldStorageDefinition() {
+    $id_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $id_field_storage_definition->expects($this->any())
+      ->method('getSchema')
+      ->willReturn(IntegerItem::schema($id_field_storage_definition));
+    $uuid_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $uuid_field_storage_definition->expects($this->any())
+      ->method('getSchema')
+      ->willReturn(UuidItem::schema($uuid_field_storage_definition));
+    $type_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $type_field_storage_definition->expects($this->any())
+      ->method('getSchema')
+      ->willReturn(StringItem::schema($type_field_storage_definition));
+    $langcode_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $langcode_field_storage_definition->expects($this->any())
+      ->method('getSchema')
+      ->willReturn(LanguageItem::schema($langcode_field_storage_definition));
+    $name_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $name_field_storage_definition->expects($this->any())
+      ->method('getSchema')
+      ->willReturn(StringItem::schema($name_field_storage_definition));
+
+    // Setup the user_id entity reference field.
+    $this->entityManager->expects($this->any())
+      ->method('getDefinition')
+      ->willReturnMap([
+          ['user', TRUE, static::userEntityInfo()],
+        ]
+      );
+    $user_id_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $user_id_field_storage_definition->expects($this->any())
+      ->method('getSetting')
+      ->with('target_type')
+      ->willReturn('user');
+    $user_id_field_storage_definition->expects($this->any())
+      ->method('getSchema')
+      ->willReturn(EntityReferenceItem::schema($user_id_field_storage_definition));
+
+    $revision_id_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $revision_id_field_storage_definition->expects($this->any())
+      ->method('getSchema')
+      ->willReturn(IntegerItem::schema($revision_id_field_storage_definition));
+
+    $this->entityManager->expects($this->any())
+      ->method('getFieldStorageDefinitions')
+      ->willReturn([
+        'id' => $id_field_storage_definition,
+        'uuid' => $uuid_field_storage_definition,
+        'type' => $type_field_storage_definition,
+        'langcode' => $langcode_field_storage_definition,
+        'name' => $name_field_storage_definition,
+        'user_id' => $user_id_field_storage_definition,
+        'revision_id' => $revision_id_field_storage_definition,
+      ]);
+  }
+
+  /**
+   * Tests fields on the base table.
+   */
+  public function testBaseTableFields() {
+    $base_field_definitions = EntityTest::baseFieldDefinitions($this->baseEntityType);
+    $this->entityManager->expects($this->once())
+      ->method('getBaseFieldDefinitions')
+      ->with('entity_test')
+      ->willReturn($base_field_definitions);
+
+    // Setup the table mapping.
+    $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface');
+    $table_mapping->expects($this->any())
+      ->method('getTableNames')
+      ->willReturn(['entity_test']);
+    $table_mapping->expects($this->any())
+      ->method('getColumnNames')
+      ->willReturnMap([
+        ['id', ['value' => 'id']],
+        ['uuid', ['value' => 'uuid']],
+        ['type', ['value' => 'type']],
+        ['langcode', ['value' => 'langcode']],
+        ['name', ['value' => 'name']],
+        ['user_id', ['target_id' => 'user_id']],
+      ]);
+    $table_mapping->expects($this->any())
+      ->method('getFieldNames')
+      ->willReturnMap([
+        ['entity_test', ['id', 'uuid', 'type', 'langcode', 'name', 'user_id']]
+      ]);
+
+    $this->entityStorage->expects($this->once())
+      ->method('getTableMapping')
+      ->willReturn($table_mapping);
+
+    $this->setupFieldStorageDefinition();
+
+    $this->viewsData->setSchemaFields(['entity_test' => ['id', 'uuid', 'type', 'langcode', 'name', 'user_id']]);
+    $data = $this->viewsData->getViewsData();
+
+    $this->assertNumericField($data['entity_test']['id']);
+    $this->assertUuidField($data['entity_test']['uuid']);
+    $this->assertStringField($data['entity_test']['type']);
+    $this->assertLanguageField($data['entity_test']['langcode']);
+    $this->assertStringField($data['entity_test']['name']);
+
+    $this->assertEntityReferenceField($data['entity_test']['user_id']);
+    $relationship = $data['entity_test']['user_id']['relationship'];
+    $this->assertEquals('users', $relationship['base']);
+    $this->assertEquals('uid', $relationship['base field']);
+  }
+
+  /**
+   * Tests fields on the data table.
+   */
+  public function testDataTableFields() {
+    $entity_type = $this->baseEntityType
+      ->set('data_table', 'entity_test_mul_property_data')
+      ->set('base_table', 'entity_test_mul')
+      ->set('id', 'entity_test_mul');
+    $base_field_definitions = EntityTestMul::baseFieldDefinitions($this->baseEntityType);
+    $this->entityManager->expects($this->once())
+      ->method('getBaseFieldDefinitions')
+      ->with('entity_test_mul')
+      ->willReturn($base_field_definitions);
+
+    $this->viewsData->setSchemaFields([
+      'entity_test_mul' => ['id', 'uuid', 'type', 'langcode'],
+      'entity_test_mul_property_data' => ['id', 'langcode', 'name', 'user_id'],
+    ]);
+
+    ;
+    $this->viewsData->setEntityType($entity_type);
+
+     // Setup the table mapping.
+    $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface');
+    $table_mapping->expects($this->any())
+      ->method('getTableNames')
+      ->willReturn(['entity_test_mul', 'entity_test_mul_property_data']);
+    $table_mapping->expects($this->any())
+      ->method('getColumnNames')
+      ->willReturnMap([
+        ['id', ['value' => 'id']],
+        ['uuid', ['value' => 'uuid']],
+        ['type', ['value' => 'type']],
+        ['langcode', ['value' => 'langcode']],
+        ['name', ['value' => 'name']],
+        ['user_id', ['target_id' => 'user_id']],
+      ]);
+    $table_mapping->expects($this->any())
+      ->method('getFieldNames')
+      ->willReturnMap([
+        ['entity_test_mul', ['id', 'uuid', 'type', 'langcode']],
+        ['entity_test_mul_property_data', ['id', 'langcode', 'name', 'user_id']],
+      ]);
+
+    $this->entityStorage->expects($this->once())
+      ->method('getTableMapping')
+      ->willReturn($table_mapping);
+
+    $this->setupFieldStorageDefinition();
+
+    $data = $this->viewsData->getViewsData();
+
+    // Check the base fields.
+    $this->assertNumericField($data['entity_test_mul']['id']);
+    $this->assertUuidField($data['entity_test_mul']['uuid']);
+    $this->assertStringField($data['entity_test_mul']['type']);
+    $this->assertLanguageField($data['entity_test_mul']['langcode']);
+    // Also ensure that field_data only fields don't appear on the base table.
+    $this->assertFalse(isset($data['entity_test_mul']['name']));
+    $this->assertFalse(isset($data['entity_test_mul']['user_id']));
+
+    // Check the data fields.
+    $this->assertNumericField($data['entity_test_mul_property_data']['id']);
+    $this->assertLanguageField($data['entity_test_mul_property_data']['langcode']);
+    $this->assertStringField($data['entity_test_mul_property_data']['name']);
+
+    $this->assertEntityReferenceField($data['entity_test_mul_property_data']['user_id']);
+    $relationship = $data['entity_test_mul_property_data']['user_id']['relationship'];
+    $this->assertEquals('users', $relationship['base']);
+    $this->assertEquals('uid', $relationship['base field']);
+  }
+
+  /**
+   * Tests fields on the revision table.
+   */
+  public function testRevisionTableFields() {
+    $entity_type = $this->baseEntityType
+      ->set('base_table', 'entity_test_mulrev')
+      ->set('revision_table', 'entity_test_mulrev_revision')
+      ->set('data_table', 'entity_test_mulrev_property_data')
+      ->set('revision_data_table', 'entity_test_mulrev_property_revision')
+      ->set('id', 'entity_test_mulrev');
+    $base_field_definitions = EntityTestMulRev::baseFieldDefinitions($this->baseEntityType);
+    $this->entityManager->expects($this->once())
+      ->method('getBaseFieldDefinitions')
+      ->with('entity_test_mulrev')
+      ->willReturn($base_field_definitions);
+
+    $this->viewsData->setSchemaFields([
+      'entity_test_mulrev' => ['id', 'revision_id', 'uuid', 'type'],
+      'entity_test_mulrev_revision' => ['id', 'revision_id', 'langcode'],
+      'entity_test_mulrev_property_data' => ['id', 'revision_id', 'langcode', 'name', 'user_id'],
+      'entity_test_mulrev_property_revision' => ['id', 'revision_id', 'langcode', 'name', 'user_id'],
+    ]);
+
+    ;
+    $this->viewsData->setEntityType($entity_type);
+
+     // Setup the table mapping.
+    $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface');
+    $table_mapping->expects($this->any())
+      ->method('getTableNames')
+      ->willReturn(['entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision']);
+    $table_mapping->expects($this->any())
+      ->method('getColumnNames')
+      ->willReturnMap([
+        ['id', ['value' => 'id']],
+        ['uuid', ['value' => 'uuid']],
+        ['type', ['value' => 'type']],
+        ['langcode', ['value' => 'langcode']],
+        ['name', ['value' => 'name']],
+        ['user_id', ['target_id' => 'user_id']],
+        ['revision_id', ['value' => 'id']],
+      ]);
+    $table_mapping->expects($this->any())
+      ->method('getFieldNames')
+      ->willReturnMap([
+        ['entity_test_mulrev', ['id', 'revision_id', 'uuid', 'type']],
+        ['entity_test_mulrev_revision', ['id', 'revision_id', 'langcode']],
+        ['entity_test_mulrev_property_data', ['id', 'revision_id', 'langcode', 'name', 'user_id']],
+        ['entity_test_mulrev_property_revision', ['id', 'revision_id', 'langcode', 'name', 'user_id']],
+      ]);
+
+    $this->entityStorage->expects($this->once())
+      ->method('getTableMapping')
+      ->willReturn($table_mapping);
+
+    $this->setupFieldStorageDefinition();
+
+    $data = $this->viewsData->getViewsData();
+
+    // Check the base fields.
+    $this->assertNumericField($data['entity_test_mulrev']['id']);
+    $this->assertNumericField($data['entity_test_mulrev']['revision_id']);
+    $this->assertUuidField($data['entity_test_mulrev']['uuid']);
+    $this->assertStringField($data['entity_test_mulrev']['type']);
+
+    // Also ensure that field_data only fields don't appear on the base table.
+    $this->assertFalse(isset($data['entity_test_mulrev']['name']));
+    $this->assertFalse(isset($data['entity_test_mulrev']['langcode']));
+    $this->assertFalse(isset($data['entity_test_mulrev']['user_id']));
+
+    // Check the revision fields.
+    $this->assertNumericField($data['entity_test_mulrev_revision']['id']);
+    $this->assertNumericField($data['entity_test_mulrev_revision']['revision_id']);
+    $this->assertLanguageField($data['entity_test_mulrev_revision']['langcode']);
+    // Also ensure that field_data only fields don't appear on the revision table.
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['name']));
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['user_id']));
+
+    // Check the data fields.
+    $this->assertNumericField($data['entity_test_mulrev_property_data']['id']);
+    $this->assertLanguageField($data['entity_test_mulrev_property_data']['langcode']);
+    $this->assertStringField($data['entity_test_mulrev_property_data']['name']);
+
+    $this->assertEntityReferenceField($data['entity_test_mulrev_property_data']['user_id']);
+    $relationship = $data['entity_test_mulrev_property_data']['user_id']['relationship'];
+    $this->assertEquals('users', $relationship['base']);
+    $this->assertEquals('uid', $relationship['base field']);
+
+    // Check the property data fields.
+    $this->assertNumericField($data['entity_test_mulrev_property_revision']['id']);
+    $this->assertLanguageField($data['entity_test_mulrev_property_revision']['langcode']);
+    $this->assertStringField($data['entity_test_mulrev_property_revision']['name']);
+
+    $this->assertEntityReferenceField($data['entity_test_mulrev_property_revision']['user_id']);
+    $relationship = $data['entity_test_mulrev_property_revision']['user_id']['relationship'];
+    $this->assertEquals('users', $relationship['base']);
+    $this->assertEquals('uid', $relationship['base field']);
+  }
+
+  /**
+   * Tests views data for a string field.
+   *
+   * @param $data
+   *   The views data to check.
+   */
+  public function assertStringField($data) {
+    $this->assertEquals('standard', $data['field']['id']);
+    $this->assertEquals('string', $data['filter']['id']);
+    $this->assertEquals('string', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for a UUID field.
+   *
+   * @param array $data
+   *   The views data to check.
+   */
+  public function assertUuidField($data) {
+    // @todo Can we provide additional support for UUIDs in views?
+    $this->assertEquals('standard', $data['field']['id']);
+    $this->assertEquals('string', $data['filter']['id']);
+    $this->assertEquals('string', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for a numeric field.
+   *
+   * @param array $data
+   *   The views data to check.
+   */
+  public function assertNumericField($data) {
+    $this->assertEquals('numeric', $data['field']['id']);
+    $this->assertEquals('numeric', $data['filter']['id']);
+    $this->assertEquals('numeric', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for a language field.
+   *
+   * @param array $data
+   *   The views data to check.
+   */
+  public function assertLanguageField($data) {
+    $this->assertEquals('language', $data['field']['id']);
+    $this->assertEquals('language', $data['filter']['id']);
+    $this->assertEquals('language', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  public function assertEntityReferenceField($data) {
+    $this->assertEquals('numeric', $data['field']['id']);
+    $this->assertEquals('numeric', $data['filter']['id']);
+    $this->assertEquals('numeric', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Returns entity info for the user entity.
+   *
+   * @return array
+   */
+  public static function userEntityInfo() {
+    return new ContentEntityType([
+      'id' => 'user',
+      'class' => 'Drupal\user\Entity\User',
+      'label' => 'User',
+      'base_table' => 'users',
+      'entity_keys' => [
+        'id' => 'uid',
+        'uuid' => 'uuid',
+      ],
+    ]);
+  }
+
+}
+
+class TestEntityViewsData extends EntityViewsData {
+
+  protected $schemaFields = [];
+
+  public function setSchemaFields($fields) {
+    $this->schemaFields = $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function drupalSchemaFieldsSql($table) {
+    return isset($this->schemaFields[$table]) ? $this->schemaFields[$table] : [];
+  }
+
+  public function setEntityType(EntityTypeInterface $entity_type) {
+    $this->entityType = $entity_type;
+  }
+
+}
+
+}
+
+namespace {
+  use Drupal\Component\Utility\String;
+
+  if (!function_exists('t')) {
+    function t($string, array $args = []) {
+      return String::format($string, $args);
+    }
+  }
+}
