From bcfde9e15913b6b668c3e76f06287f2df93f48ca Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Sat, 13 Dec 2014 16:03:32 +0100
Subject: [PATCH] Issue #2392209 by plach:
 DefaultTableMapping::getFieldColumName is broken for base tables

---
 .../Core/Entity/Sql/DefaultTableMapping.php   | 40 +++++----
 .../Sql/SqlContentEntityStorageException.php  | 16 ++++
 .../Core/Entity/Sql/TableMappingInterface.php |  8 +-
 .../Entity/Sql/DefaultTableMappingTest.php    | 86 ++++++++++++++++++-
 .../Sql/SqlContentEntityStorageTest.php       |  2 +-
 5 files changed, 129 insertions(+), 23 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageException.php

diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
index f992081ad581..c63ca4d82504 100644
--- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
+++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Entity\Sql;
 
+use Drupal\Component\Utility\String;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 
 /**
@@ -130,20 +131,34 @@ public function getFieldNames($table_name) {
    */
   public function getColumnNames($field_name) {
     if (!isset($this->columnMapping[$field_name])) {
-      $column_names = array_keys($this->fieldStorageDefinitions[$field_name]->getColumns());
-      if (count($column_names) == 1) {
-        $this->columnMapping[$field_name] = array(reset($column_names) => $field_name);
-      }
-      else {
-        $this->columnMapping[$field_name] = array();
-        foreach ($column_names as $column_name) {
-          $this->columnMapping[$field_name][$column_name] = $field_name . '__' . $column_name;
-        }
+      $this->columnMapping[$field_name] = array();
+      $storage_definition = $this->fieldStorageDefinitions[$field_name];
+      foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
+        $this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($storage_definition, $property_name);
       }
     }
     return $this->columnMapping[$field_name];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $property_name) {
+    $field_name = $storage_definition->getName();
+
+    if ($this->allowsSharedTableStorage($storage_definition)) {
+      $column_name = count($storage_definition->getColumns()) == 1 ? $field_name :  $field_name . '__' . $property_name;
+    }
+    elseif ($this->requiresDedicatedTableStorage($storage_definition)) {
+      $column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name;
+    }
+    else {
+      throw new SqlContentEntityStorageException(String::format('Column information not available for the "@field_name" field.', array('@field_name' => $field_name)));
+    }
+
+    return $column_name;
+  }
+
   /**
    * Adds field columns for a table to the table mapping.
    *
@@ -319,11 +334,4 @@ protected function generateFieldTableName(FieldStorageDefinitionInterface $stora
     return $table_name;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $column) {
-    return in_array($column, $this->getReservedColumns()) ? $column : $storage_definition->getName() . '_' . $column;
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageException.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageException.php
new file mode 100644
index 000000000000..61f610e407ce
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageException.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchemaException.
+ */
+
+namespace Drupal\Core\Entity\Sql;
+
+use Drupal\Core\Entity\EntityStorageException;
+
+/**
+ * Exception thrown when a SQL storage operation fails.
+ */
+class SqlContentEntityStorageException extends EntityStorageException {
+}
diff --git a/core/lib/Drupal/Core/Entity/Sql/TableMappingInterface.php b/core/lib/Drupal/Core/Entity/Sql/TableMappingInterface.php
index 9607fe4073b6..19a45b1e7af1 100644
--- a/core/lib/Drupal/Core/Entity/Sql/TableMappingInterface.php
+++ b/core/lib/Drupal/Core/Entity/Sql/TableMappingInterface.php
@@ -90,16 +90,16 @@ public function getExtraColumns($table_name);
   public function getReservedColumns();
 
   /**
-   * Generates a column name for a field.
+   * Generates a column name for a field property.
    *
    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
    *   The field storage definition.
-   * @param string $column
-   *   The name of the column.
+   * @param string $property_name
+   *   The name of the property.
    *
    * @return string
    *   A string containing a generated column name for a field data table that is
    *   unique among all other fields.
    */
-  public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $column);
+  public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $property_name);
 }
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
index 007efc35e805..883819095c2a 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Entity\Sql;
 use Drupal\Core\Entity\Sql\DefaultTableMapping;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorageException;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -234,6 +235,87 @@ public function testGetExtraColumns() {
     $this->assertSame($expected, $table_mapping->getExtraColumns('foo'));
   }
 
+  /**
+   * Tests DefaultTableMapping::getFieldColumnName() with valid parameters.
+   *
+   * @param bool $base_field
+   *   Flag indicating whether the field should be treated as a base or bundle
+   *   field.
+   * @param string[] $columns
+   *   An array of available field column names.
+   * @param string $column
+   *   The name of the column to be processed.
+   * @param string $expected
+   *   The expected result.
+   *
+   * @covers ::getFieldColumnName
+   *
+   * @dataProvider providerTestGetFieldColumnName
+   */
+  public function testGetFieldColumnName($base_field, $columns, $column, $expected) {
+    $definitions['test'] = $this->setUpDefinition('test', $columns, $base_field);
+    $table_mapping = new DefaultTableMapping($definitions);
+    $result = $table_mapping->getFieldColumnName($definitions['test'], $column);
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * Tests DefaultTableMapping::getFieldColumnName() with invalid parameters.
+   *
+   * @param bool $base_field
+   *   Flag indicating whether the field should be treated as a base or bundle
+   *   field.
+   * @param string[] $columns
+   *   An array of available field column names.
+   * @param string $column
+   *   The name of the column to be processed.
+   *
+   * @expectedException \Drupal\Core\Entity\Sql\SqlContentEntityStorageException
+   * @expectedExceptionMessage Column information not available for the "test" field.
+   *
+   * @covers ::getFieldColumnName
+   *
+   * @dataProvider providerTestGetFieldColumnName
+   */
+  public function testGetFieldColumnNameInvalid($base_field, $columns, $column) {
+    $definitions['test'] = $this->setUpDefinition('test', $columns, $base_field);
+
+    // Mark field storage definition as custom storage.
+    $definitions['test']->expects($this->any())
+      ->method('hasCustomStorage')
+      ->willReturn(TRUE);
+
+    $table_mapping = new DefaultTableMapping($definitions);
+    $table_mapping->getFieldColumnName($definitions['test'], $column);
+  }
+
+  /**
+   * Provides test data for testGetFieldColumnName().
+   *
+   * @return array[]
+   *   An nested array where each inner array has the following values: test
+   *   field name, base field status, list of field columns, name of the column
+   *   to be retrieved, expected result, whether an exception is expected.
+   */
+  public function providerTestGetFieldColumnName() {
+    $data = [];
+    // Base field with single column.
+    $data[] = [TRUE, ['foo'], 'foo', 'test'];
+
+    // Base field with multiple columns.
+    $data[] = [TRUE, ['foo', 'bar'], 'foo', 'test__foo'];
+    $data[] = [TRUE, ['foo', 'bar'], 'bar', 'test__bar'];
+    // Bundle field with single column.
+    $data[] = [FALSE, ['foo'], 'foo', 'test_foo'];
+    // Bundle field with multiple columns.
+    $data[] = [FALSE, ['foo', 'bar'], 'foo', 'test_foo'];
+    $data[] = [FALSE, ['foo', 'bar'], 'bar', 'test_bar'];
+    // Bundle field with reserved column.
+    $data[] = [FALSE, ['foo', 'bar'], 'deleted', 'deleted'];
+
+    return $data;
+  }
+
   /**
    * Sets up a field storage definition for the test.
    *
@@ -244,11 +326,11 @@ public function testGetExtraColumns() {
    *
    * @return \Drupal\Core\Field\FieldStorageDefinitionInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected function setUpDefinition($name, array $column_names) {
+  protected function setUpDefinition($name, array $column_names, $base_field = TRUE) {
     $definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
     $definition->expects($this->any())
       ->method('isBaseField')
-      ->will($this->returnValue(TRUE));
+      ->willReturn($base_field);
     $definition->expects($this->any())
       ->method('getName')
       ->will($this->returnValue($name));
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
index 3a37f909b4a1..cfa5cd9061e1 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
@@ -281,7 +281,7 @@ public function testOnEntityTypeCreate() {
     );
 
     $this->fieldDefinitions = $this->mockFieldDefinitions(array('id'));
-    $this->fieldDefinitions['id']->expects($this->once())
+    $this->fieldDefinitions['id']->expects($this->any())
       ->method('getColumns')
       ->will($this->returnValue($columns));
     $this->fieldDefinitions['id']->expects($this->once())
-- 
GitLab