/** * {@inheritdoc} */ public function addField($field, $type, $langcode) { $entity_type_id = $this->sqlQuery->getMetaData('entity_type'); $age = $this->sqlQuery->getMetaData('age'); // This variable ensures grouping works correctly. For example: // ->condition('tags', 2, '>') // ->condition('tags', 20, '<') // ->condition('node_reference.nid.entity.tags', 2) // The first two should use the same table but the last one needs to be a // new table. So for the first two, the table array index will be 'tags' // while the third will be 'node_reference.nid.tags'. $index_prefix = ''; $specifiers = explode('.', $field); $base_table = 'base_table'; $count = count($specifiers) - 1; // This will contain the definitions of the last specifier seen by the // system. $propertyDefinitions = array(); $entity_type = $this->entityManager->getDefinition($entity_type_id); $field_storage_definitions = array(); // @todo Needed for menu links, make this implementation content entity // specific after https://drupal.org/node/2256521. if ($entity_type instanceof ContentEntityTypeInterface) { $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); } for ($key = 0; $key <= $count; $key++) { // If there is revision support and only the current revision is being // queried then use the revision id. Otherwise, the entity id will do. if (($revision_key = $entity_type->getKey('revision')) && $age == EntityStorageInterface::FIELD_LOAD_CURRENT) { // This contains the relevant SQL field to be used when joining entity // tables. $entity_id_field = $revision_key; // This contains the relevant SQL field to be used when joining field // tables. $field_id_field = 'revision_id'; } else { $entity_id_field = $entity_type->getKey('id'); $field_id_field = 'entity_id'; } // This can either be the name of an entity base field or a configurable // field. $specifier = $specifiers[$key]; if (isset($field_storage_definitions[$specifier])) { $field_storage = $field_storage_definitions[$specifier]; } else { $field_storage = FALSE; } // If we managed to retrieve a configurable field, process it. if ($field_storage instanceof FieldStorageConfigInterface) { // Find the field column. $column = $field_storage->getMainPropertyName(); if ($key < $count) { $next = $specifiers[$key + 1]; // Is this a field column? $columns = $field_storage->getColumns(); if (isset($columns[$next]) || in_array($next, FieldStorageConfig::getReservedColumns())) { // Use it. $column = $next; // Do not process it again. $key++; } // If there are more specifiers, the next one must be a // relationship. Either the field name followed by a relationship // specifier, for example $node->field_image->entity. Or a field // column followed by a relationship specifier, for example // $node->field_image->fid->entity. In both cases, prepare the // property definitions for the relationship. In the first case, // also use the property definitions for column. if ($key < $count) { $relationship_specifier = $specifiers[$key + 1]; $propertyDefinitions = $field_storage->getPropertyDefinitions(); // Prepare the next index prefix. $next_index_prefix = "{$relationship_specifier}.{$column}"; } } $table = $this->ensureFieldTable($index_prefix, $field_storage, $type, $langcode, $base_table, $entity_id_field, $field_id_field); $sql_column = ContentEntityDatabaseStorage::_fieldColumnName($field_storage, $column); } else { // ensureEntityTable() decides whether an entity property will be // queried from the data table or the base table based on where it // finds the property first. The data table is preferred, which is why // it gets added before the base table. $entity_tables = array(); if ($data_table = $entity_type->getDataTable()) { $this->sqlQuery->addMetaData('simple_query', FALSE); $entity_tables[$data_table] = $this->getTableMapping($data_table, $entity_type_id); } $entity_base_table = $entity_type->getBaseTable(); $entity_tables[$entity_base_table] = $this->getTableMapping($entity_base_table, $entity_type_id); $sql_column = $specifier; $table = $this->ensureEntityTable($index_prefix, $specifier, $type, $langcode, $base_table, $entity_id_field, $entity_tables); } // If there are more specifiers to come, it's a relationship. if ($field_storage && $key < $count) { // Computed fields have prepared their property definition already, do // it for properties as well. if (!$propertyDefinitions) { $propertyDefinitions = $field_storage->getPropertyDefinitions(); $relationship_specifier = $specifiers[$key + 1]; $next_index_prefix = $relationship_specifier; } // Check for a valid relationship. if (isset($propertyDefinitions[$relationship_specifier]) && $field_storage->getPropertyDefinition('entity')->getDataType() == 'entity_reference') { // If it is, use the entity type. $entity_type_id = $propertyDefinitions[$relationship_specifier]->getTargetDefinition()->getEntityTypeId(); $entity_type = $this->entityManager->getDefinition($entity_type_id); $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); // Add the new entity base table using the table and sql column. $join_condition = '%alias.' . $entity_type->getKey('id') . " = {$table}.{$sql_column}"; $base_table = $this->sqlQuery->leftJoin($entity_type->getBaseTable(), NULL, $join_condition); $propertyDefinitions = array(); $key++; $index_prefix .= "{$next_index_prefix}."; } else { throw new QueryException(format_string('Invalid specifier @next.', array('@next' => $relationship_specifier))); } } } return "{$table}.{$sql_column}"; }
/** * Called to determine what to tell the clicksorter. */ public function clickSort($order) { // No column selected, can't continue. if (empty($this->options['click_sort_column'])) { return; } $this->ensureMyTable(); $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->definition['entity_type']); $field_storage = $field_storage_definitions[$this->definition['field_name']]; $column = ContentEntityDatabaseStorage::_fieldColumnName($field_storage, $this->options['click_sort_column']); if (!isset($this->aliases[$column])) { // Column is not in query; add a sort on it (without adding the column). $this->aliases[$column] = $this->tableAlias . '.' . $column; } $this->query->addOrderBy(NULL, NULL, $order, $this->aliases[$column]); }
/** * Test foreign key support. */ function testFieldSqlStorageForeignKeys() { // Create a 'shape' field, with a configurable foreign key (see // field_test_field_schema()). $field_name = 'testfield'; $foreign_key_name = 'shape'; $field_storage = entity_create('field_storage_config', array('name' => $field_name, 'entity_type' => 'entity_test', 'type' => 'shape', 'settings' => array('foreign_key_name' => $foreign_key_name))); $field_storage->save(); // Get the field schema. $schema = $field_storage->getSchema(); // Retrieve the field definition and check that the foreign key is in place. $this->assertEqual($schema['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name preserved through CRUD'); $this->assertEqual($schema['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name preserved through CRUD'); // Update the field settings, it should update the foreign key definition too. $foreign_key_name = 'color'; $field_storage->settings['foreign_key_name'] = $foreign_key_name; $field_storage->save(); // Reload the field schema after the update. $schema = $field_storage->getSchema(); // Retrieve the field definition and check that the foreign key is in place. $field_storage = FieldStorageConfig::loadByName('entity_test', $field_name); $this->assertEqual($schema['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name modified after update'); $this->assertEqual($schema['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name modified after update'); // Verify the SQL schema. $schemas = ContentEntityDatabaseStorage::_fieldSqlSchema($field_storage); $schema = $schemas[ContentEntityDatabaseStorage::_fieldTableName($field_storage)]; $this->assertEqual(count($schema['foreign keys']), 1, 'There is 1 foreign key in the schema'); $foreign_key = reset($schema['foreign keys']); $foreign_key_column = ContentEntityDatabaseStorage::_fieldColumnName($field_storage, $foreign_key_name); $this->assertEqual($foreign_key['table'], $foreign_key_name, 'Foreign key table name preserved in the schema'); $this->assertEqual($foreign_key['columns'][$foreign_key_column], 'id', 'Foreign key column name preserved in the schema'); }
/** * Verify that deleting an instance leaves the field data items in * the database and that the appropriate Field API functions can * operate on the deleted data and instance. * * This tests how EntityFieldQuery interacts with field instance deletion and * could be moved to FieldCrudTestCase, but depends on this class's setUp(). */ function testDeleteFieldInstance() { $bundle = reset($this->bundles); $field_storage = reset($this->fieldStorages); $field_name = $field_storage->name; $factory = \Drupal::service('entity.query'); // There are 10 entities of this bundle. $found = $factory->get('entity_test')->condition('type', $bundle)->execute(); $this->assertEqual(count($found), 10, 'Correct number of entities found before deleting'); // Delete the instance. $instance = FieldInstanceConfig::loadByName($this->entity_type, $bundle, $field_storage->name); $instance->delete(); // The instance still exists, deleted. $instances = entity_load_multiple_by_properties('field_instance_config', array('field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE)); $this->assertEqual(count($instances), 1, 'There is one deleted instance'); $instance = $instances[$instance->uuid()]; $this->assertEqual($instance->bundle, $bundle, 'The deleted instance is for the correct bundle'); // Check that the actual stored content did not change during delete. $schema = ContentEntityDatabaseStorage::_fieldSqlSchema($field_storage); $table = ContentEntityDatabaseStorage::_fieldTableName($field_storage); $column = ContentEntityDatabaseStorage::_fieldColumnName($field_storage, 'value'); $result = db_select($table, 't')->fields('t', array_keys($schema[$table]['fields']))->execute(); foreach ($result as $row) { $this->assertEqual($this->entities[$row->entity_id]->{$field_storage->name}->value, $row->{$column}); } // There are 0 entities of this bundle with non-deleted data. $found = $factory->get('entity_test')->condition('type', $bundle)->condition("{$field_name}.deleted", 0)->execute(); $this->assertFalse($found, 'No entities found after deleting'); // There are 10 entities of this bundle when deleted fields are allowed, and // their values are correct. $found = $factory->get('entity_test')->condition('type', $bundle)->condition("{$field_name}.deleted", 1)->sort('id')->execute(); $this->assertEqual(count($found), 10, 'Correct number of entities found after deleting'); $this->assertFalse(array_diff($found, array_keys($this->entities))); }