diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc index b8e2015..7a1dbb0 100644 --- a/core/modules/field/field.crud.inc +++ b/core/modules/field/field.crud.inc @@ -887,7 +887,7 @@ function field_purge_batch($batch_size) { $field = field_info_field_by_id($instance['field_id']); // Retrieve some entities. $results = $factory->get($entity_type) - ->condition('id:' . $field['id'] . '.deleted', 1) + ->condition('id@' . $field['id'] . '.deleted', 1) ->condition($info[$entity_type]['entity_keys']['bundle'], $ids->bundle) ->range(0, $batch_size) ->execute(); diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php index 4a0b440..868961e 100644 --- a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php +++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php @@ -48,27 +48,14 @@ public function execute() { $entity_type = $this->entityType; $entity_info = entity_get_info($entity_type); if (!isset($entity_info['base_table'])) { - throw new QueryException("No base table, nothing to query."); + throw new QueryException("No base table, invalid query."); } - $configurable_fields = array_map(function ($data) use ($entity_type) { - return isset($data['bundles'][$entity_type]); - }, field_info_field_map()); $base_table = $entity_info['base_table']; - // Assemble a list of entity tables, primarily for use in - // \Drupal\field_sql_storage\Entity\Tables::ensureEntityTable(). - $entity_tables = array(); $simple_query = TRUE; - // 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 prefered, which is why it gets added - // before the base table. if (isset($entity_info['data_table'])) { - $entity_tables[$entity_info['data_table']] = drupal_get_schema($entity_info['data_table']); $simple_query = FALSE; } - $entity_tables[$base_table] = drupal_get_schema($base_table); $sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction)); - $sqlQuery->addMetaData('configurable_fields', $configurable_fields); $sqlQuery->addMetaData('entity_type', $entity_type); // Determines the key of the column to join on. This is either the entity // id key or the revision id key, depending on whether the entity type @@ -87,10 +74,6 @@ public function execute() { $revision_field = $entity_info['entity_keys']['revision']; $fields[$revision_field] = TRUE; $sqlQuery->addField('base_table', $revision_field); - // Now revision id is column 0 and the value column is 1. - if ($this->age == FIELD_LOAD_CURRENT) { - $id_key = 'revision'; - } } // Now add the value column for fetchAllKeyed(). This is always the // entity id. @@ -116,14 +99,7 @@ public function execute() { } // This now contains first the table containing entity properties and // last the entity base table. They might be the same. - $sqlQuery->addMetaData('entity_tables', $entity_tables); $sqlQuery->addMetaData('age', $this->age); - // This contains the relevant SQL field to be used when joining entity - // tables. - $sqlQuery->addMetaData('entity_id_field', $entity_info['entity_keys'][$id_key]); - // This contains the relevant SQL field to be used when joining field - // tables. - $sqlQuery->addMetaData('field_id_field', $id_key == 'id' ? 'entity_id' : 'revision_id'); $sqlQuery->addMetaData('simple_query', $simple_query); $this->condition->compile($sqlQuery); if ($this->count) { diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php index 7ba89b3..6fb71f0 100644 --- a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php +++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php @@ -59,19 +59,69 @@ function __construct(SelectInterface $sql_query) { * of this in a query for a condition or sort. */ function addField($field, $type, $langcode) { - $parts = explode('.', $field); - $property = $parts[0]; - $configurable_fields = $this->sqlQuery->getMetaData('configurable_fields'); - if (!empty($configurable_fields[$property]) || substr($property, 0, 3) == 'id:') { - $field_name = $property; - $table = $this->ensureFieldTable($field_name, $type, $langcode); - // Default to .value. - $column = isset($parts[1]) ? $parts[1] : 'value'; - $sql_column = _field_sql_storage_columnname($field_name, $column); - } - else { - $sql_column = $property; - $table = $this->ensureEntityTable($property, $type, $langcode); + $age = $this->sqlQuery->getMetaData('age'); + $specifiers = explode(':', $field); + $path = ''; + foreach ($specifiers as $key => $specifier) { + if ($key) { + $definitions = entity_get_controller($entity_type)->getFieldDefinitions(array()); + $entity_type = $definitions[$property]['settings']['entity type']; + } + else { + $entity_type = $this->sqlQuery->getMetaData('entity_type'); + } + $entity_info = entity_get_info($entity_type); + if (!isset($entity_info['base_table'])) { + throw new QueryException("No base table, invalid query."); + } + if (!empty($entity_info['entity_keys']['revision']) && $age == FIELD_LOAD_CURRENT) { + // This contains the relevant SQL field to be used when joining entity + // tables. + $entity_id_field = $entity_info['entity_keys']['revision']; + // This contains the relevant SQL field to be used when joining field + // tables. + $field_id_field = 'revision_id'; + } + else { + $entity_id_field = $entity_info['entity_keys']['id']; + $field_id_field = 'entity_id'; + } + $configurable_fields = array_map(function ($data) use ($entity_type) { + return isset($data['bundles'][$entity_type]); + }, field_info_field_map()); + if ($key) { + $join_condition= '%alias.' . $entity_info['entity_keys']['id'] . " = $table.$sql_column"; + $base_table = $this->sqlQuery->leftJoin($entity_info['base_table'], NULL, $join_condition); + } + else { + $base_table = 'base_table'; + } + $parts = explode('.', $specifier); + $property = $parts[0]; + if (!empty($configurable_fields[$property]) || substr($property, 0, 3) == 'id@') { + $field_name = $property; + $table = $this->ensureFieldTable($path, $field_name, $type, $langcode, $base_table, $entity_id_field, $field_id_field); + // Default to .value. + $column = isset($parts[1]) ? $parts[1] : 'value'; + $sql_column = _field_sql_storage_columnname($field_name, $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 prefered, which is why + // it gets added before the base table. + $entity_tables = array(); + if (isset($entity_info['data_table'])) { + $this->sqlQuery->addMetaData('simple_query', FALSE); + $entity_tables[$entity_info['data_table']] = drupal_get_schema($entity_info['data_table']); + } + $entity_tables[$entity_info['base_table']] = drupal_get_schema($entity_info['base_table']); + $sql_column = $property; + $table = $this->ensureEntityTable($path, $property, $type, $langcode, $base_table, $entity_id_field, $entity_tables); + } + // This is used to keep track which tables have been joined for + // grouping. + $path .= "$specifier."; } return "$table.$sql_column"; } @@ -83,18 +133,13 @@ function addField($field, $type, $langcode) { * @return string * @throws \Drupal\Core\Entity\Query\QueryException */ - protected function ensureEntityTable($property, $type, $langcode) { - $entity_tables = $this->sqlQuery->getMetaData('entity_tables'); - if (!$entity_tables) { - throw new QueryException('Can not query entity properties without entity tables.'); - } + protected function ensureEntityTable($path, $property, $type, $langcode, $base_table, $id_field, $entity_tables) { foreach ($entity_tables as $table => $schema) { if (isset($schema['fields'][$property])) { - if (!isset($this->entityTables[$table])) { - $id_field = $this->sqlQuery->getMetaData('entity_id_field'); - $this->entityTables[$table] = $this->addJoin($type, $table, "%alias.$id_field = base_table.$id_field", $langcode); + if (!isset($this->entityTables[$path . $table])) { + $this->entityTables[$path . $table] = $this->addJoin($type, $table, "%alias.$id_field = $base_table.$id_field", $langcode); } - return $this->entityTables[$table]; + return $this->entityTables[$path . $table]; } } throw new QueryException(format_string('@property not found', array('@property' => $property))); @@ -108,10 +153,10 @@ protected function ensureEntityTable($property, $type, $langcode) { * @return string * @throws \Drupal\Core\Entity\Query\QueryException */ - protected function ensureFieldTable(&$field_name, $type, $langcode) { - if (!isset($this->fieldTables[$field_name])) { + protected function ensureFieldTable($path, &$field_name, $type, $langcode, $base_table, $entity_id_field, $field_id_field) { + if (!isset($this->fieldTables[$path . $field_name])) { // This is field_purge_batch() passing in a field id. - if (substr($field_name, 0, 3) == 'id:') { + if (substr($field_name, 0, 3) == 'id@') { $field = field_info_field_by_id(substr($field_name, 3)); } else { @@ -120,19 +165,17 @@ protected function ensureFieldTable(&$field_name, $type, $langcode) { if (!$field) { throw new QueryException(format_string('field @field_name not found', array('@field_name' => $field_name))); } - // This is really necessary only for the id: case but it can't be run + // This is really necessary only for the id@ case but it can't be run // before throwing the exception. $field_name = $field['field_name']; $table = $this->sqlQuery->getMetaData('age') == FIELD_LOAD_CURRENT ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); - $field_id_field = $this->sqlQuery->getMetaData('field_id_field'); - $entity_id_field = $this->sqlQuery->getMetaData('entity_id_field'); if ($field['cardinality'] != 1) { $this->sqlQuery->addMetaData('simple_query', FALSE); } $entity_type = $this->sqlQuery->getMetaData('entity_type'); - $this->fieldTables[$field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = base_table.$entity_id_field AND %alias.entity_type = '$entity_type'", $langcode); + $this->fieldTables[$path . $field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = $base_table.$entity_id_field AND %alias.entity_type = '$entity_type'", $langcode); } - return $this->fieldTables[$field_name]; + return $this->fieldTables[$path . $field_name]; } protected function addJoin($type, $table, $join_condition, $langcode) { diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryRelationshipTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryRelationshipTest.php new file mode 100644 index 0000000..a304319 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryRelationshipTest.php @@ -0,0 +1,82 @@ + 'Entity Query relationship', + 'description' => 'Tests the Entity Query relationship API', + 'group' => 'Entity API', + ); + } + + /** + * Overrides \Drupal\simpletest\WebTestBase::setUp(). + */ + protected function setUp() { + parent::setUp(); + for ($i = 0; $i <= 1; $i++) { + $this->accounts[] = $this->drupalCreateUser(); + } + for ($i = 0; $i <= 2; $i++) { + $entity = entity_create('entity_test', array()); + $entity->name->value = $this->randomName(); + $entity->user_id->value = $this->accounts[$i ? 1 : 0]->uid; + $entity->save(); + $this->entities[] = $entity; + } + $this->factory = drupal_container()->get('entity.query'); + } + + /** + * Tests querying. + */ + public function testQuery() { + $results = $this->factory->get('entity_test') + ->condition("user_id:name", $this->accounts[0]->name) + ->execute(); + $this->assertEqual(count($results), 1); + $id = $this->entities[0]->id(); + $this->assertEqual($results, array($id => $id)); + $results = $this->factory->get('entity_test') + ->condition("user_id:name", $this->accounts[0]->name, '<>') + ->execute(); + $this->assertEqual(count($results), 2); + $this->assertFalse(isset($results[$id])); + for ($i = 1; $i <=2 ; $i++) { + $id = $this->entities[$i]->id(); + $this->assertEqual($results[$id], $id); + } + $results = $this->factory->get('entity_test') + ->exists("user_id:name") + ->execute(); + $this->assertEqual(count($results), 3); + for ($i = 0; $i <=2 ; $i++) { + $id = $this->entities[$i]->id(); + $this->assertEqual($results[$id], $id); + } + $results = $this->factory->get('entity_test') + ->notExists("user_id:name") + ->execute(); + $this->assertEqual(count($results), 0); + } +}