diff --git a/includes/entity.inc b/includes/entity.inc index 9ee7889cfb7ca439e77cfdb44282b68e19584a0a..f363c31137248e7f413e96ebe6970d8bd7d83885 100644 --- a/includes/entity.inc +++ b/includes/entity.inc @@ -457,6 +457,21 @@ class EntityFieldQuery { */ public $fieldConditions = array(); + /** + * List of field meta conditions (language and delta). + * + * Field conditions operate on columns specified by hook_field_schema(), + * the meta conditions operate on columns added by the system: delta + * and language. These can not be mixed with the field conditions because + * field columns can have any name including delta and language. + * + * @var array + * + * @see EntityFieldQuery::fieldLanguageCondition() + * @see EntityFieldQuery::fielDeltaCondition() + */ + public $fieldMetaConditions = array(); + /** * List of property conditions. * @@ -613,6 +628,90 @@ public function entityCondition($name, $value, $operator = NULL) { /** * Adds a condition on field values. * + * @param $type + * The condition array the given conditions should be added to. + * @param $field + * Either a field name or a field array. + * @param $column + * The column that should hold the value to be matched. + * @param $value + * The value to test the column value against. + * @param $operator + * The operator to be used to test the given value. + * @param $delta_group + * An arbitrary identifier: conditions in the same group must have the same + * $delta_group. + * @param $language_group + * An arbitrary identifier: conditions in the same group must have the same + * $language_group. + * + * @return EntityFieldQuery + * The called object. + * + * @see EntityFieldQuery::addFieldCondition + * @see EntityFieldQuery::deleted + */ + public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + return $this->addFieldCondition($this->fieldConditions, $field, $column, $value, $operator, $delta_group, $language_group); + } + + /** + * Adds a condition on the field language column. + * + * @param $field + * Either a field name or a field array. + * @param $value + * The value to test the column value against. + * @param $operator + * The operator to be used to test the given value. + * @param $delta_group + * An arbitrary identifier: conditions in the same group must have the same + * $delta_group. + * @param $language_group + * An arbitrary identifier: conditions in the same group must have the same + * $language_group. + * + * @return EntityFieldQuery + * The called object. + * + * @see EntityFieldQuery::addFieldCondition + * @see EntityFieldQuery::deleted + */ + public function fieldLanguageCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + return $this->addFieldCondition($this->fieldMetaConditions, $field, 'language', $value, $operator, $delta_group, $language_group); + } + + /** + * Adds a condition on the field delta column. + * + * @param $field + * Either a field name or a field array. + * @param $value + * The value to test the column value against. + * @param $operator + * The operator to be used to test the given value. + * @param $delta_group + * An arbitrary identifier: conditions in the same group must have the same + * $delta_group. + * @param $language_group + * An arbitrary identifier: conditions in the same group must have the same + * $language_group. + * + * @return EntityFieldQuery + * The called object. + * + * @see EntityFieldQuery::addFieldCondition + * @see EntityFieldQuery::deleted + */ + public function fieldDeltaCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + return $this->addFieldCondition($this->fieldMetaConditions, $field, 'delta', $value, $operator, $delta_group, $language_group); + } + + /** + * Adds the given condition to the proper condition array. + * + * @param $conditions + * A reference to an array of conditions. * @param $field * Either a field name or a field array. * @param $column @@ -649,7 +748,7 @@ public function entityCondition($name, $value, $operator = NULL) { * @return EntityFieldQuery * The called object. */ - public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + protected function addFieldCondition(&$conditions, $field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { if (is_scalar($field)) { $field_definition = field_info_field($field); if (empty($field_definition)) { @@ -657,11 +756,11 @@ public function fieldCondition($field, $column = NULL, $value = NULL, $operator } $field = $field_definition; } - // Ensure the same index is used for fieldConditions as for fields. + // Ensure the same index is used for field conditions as for fields. $index = count($this->fields); $this->fields[$index] = $field; if (isset($column)) { - $this->fieldConditions[$index] = array( + $conditions[$index] = array( 'field' => $field, 'column' => $column, 'value' => $value, diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module index 6f49167ec0061129207803a05dbff567354c03b4..89aeca66f2fc73f72c39d33b4dd38f9a8241fd8f 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -468,7 +468,6 @@ function field_sql_storage_field_storage_purge($entity_type, $entity, $field, $i * Implements hook_field_storage_query(). */ function field_sql_storage_field_storage_query(EntityFieldQuery $query) { - $groups = array(); if ($query->age == FIELD_LOAD_CURRENT) { $tablename_function = '_field_sql_storage_tablename'; $id_key = 'entity_id'; @@ -499,26 +498,12 @@ function field_sql_storage_field_storage_query(EntityFieldQuery $query) { } } - // Add field conditions. - foreach ($query->fieldConditions as $key => $condition) { - $table_alias = $table_aliases[$key]; - $field = $condition['field']; - // Add the specified condition. - $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $condition['column']); - $query->addCondition($select_query, $sql_field, $condition); - // Add delta / language group conditions. - foreach (array('delta', 'language') as $column) { - if (isset($condition[$column . '_group'])) { - $group_name = $condition[$column . '_group']; - if (!isset($groups[$column][$group_name])) { - $groups[$column][$group_name] = $table_alias; - } - else { - $select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column"); - } - } - } - } + // Add field conditions. We need a fresh grouping cache. + drupal_static_reset('_field_sql_storage_query_field_conditions'); + _field_sql_storage_query_field_conditions($query, $select_query, $query->fieldConditions, $table_aliases, '_field_sql_storage_columnname'); + + // Add field meta conditions. + _field_sql_storage_query_field_conditions($query, $select_query, $query->fieldMetaConditions, $table_aliases, function ($field_name, $column) { return $column; }); if (isset($query->deleted)) { $select_query->condition("$field_base_table.deleted", (int) $query->deleted); @@ -591,6 +576,44 @@ function _field_sql_storage_query_join_entity(SelectQuery $select_query, $entity return $entity_base_table; } +/** + * Adds field (meta) conditions to the given query objects respecting groupings. + * + * @param EntityFieldQuery $query + * The field query object to be processed. + * @param SelectQuery $select_query + * The SelectQuery that should get grouping conditions. + * @param condtions + * The conditions to be added. + * @param $table_aliases + * An associative array of table aliases keyed by field index. + * @param $column_callback + * A callback that should return the column name to be used for the field + * conditions. Accepts a field name and a field column name as parameters. + */ +function _field_sql_storage_query_field_conditions(EntityFieldQuery $query, SelectQuery $select_query, $conditions, $table_aliases, $column_callback) { + $groups = &drupal_static(__FUNCTION__, array()); + foreach ($conditions as $key => $condition) { + $table_alias = $table_aliases[$key]; + $field = $condition['field']; + // Add the specified condition. + $sql_field = "$table_alias." . $column_callback($field['field_name'], $condition['column']); + $query->addCondition($select_query, $sql_field, $condition); + // Add delta / language group conditions. + foreach (array('delta', 'language') as $column) { + if (isset($condition[$column . '_group'])) { + $group_name = $condition[$column . '_group']; + if (!isset($groups[$column][$group_name])) { + $groups[$column][$group_name] = $table_alias; + } + else { + $select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column"); + } + } + } + } +} + /** * Implements hook_field_storage_delete_revision(). * diff --git a/modules/simpletest/tests/entity_query.test b/modules/simpletest/tests/entity_query.test index d28d5a35c9361fb88ddb92f88462ec90d064f026..0fe8106ef6c141d1425ecb86d65f94c605732667 100644 --- a/modules/simpletest/tests/entity_query.test +++ b/modules/simpletest/tests/entity_query.test @@ -1049,6 +1049,133 @@ class EntityFieldQueryTestCase extends DrupalWebTestCase { $this->assertTrue($pass, t("Can't query the universe.")); } + /** + * Tests field meta conditions. + */ + function testEntityFieldQueryMetaConditions() { + // Make a test field translatable. + $this->fields[0]['translatable'] = TRUE; + field_update_field($this->fields[0]); + field_test_entity_info_translatable('test_entity', TRUE); + drupal_static_reset('field_available_languages'); + + // Create more items with different languages. + $entity = new stdClass(); + $entity->ftid = 1; + $entity->ftvid = 1; + $entity->fttype = 'test_bundle'; + $j = 0; + + foreach (array(LANGUAGE_NONE, 'en') as $langcode) { + for ($i = 0; $i < 4; $i++) { + $entity->{$this->field_names[0]}[$langcode][$i]['value'] = $i + $j; + } + $j += 4; + } + + field_attach_update('test_entity', $entity); + + // Test delta field meta condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldDeltaCondition($this->fields[0], 0, '>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a delta meta condition.')); + + // Test language field meta condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '!='); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a language meta condition.')); + + // Test delta grouping. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'group') + ->fieldDeltaCondition($this->fields[0], 1, '<', 'group'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a grouped delta meta condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'group') + ->fieldDeltaCondition($this->fields[0], 1, '>=', 'group'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta meta condition (empty result set).')); + + // Test language grouping. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', NULL, 'group') + ->fieldLanguageCondition($this->fields[0], 'en', '!=', NULL, 'group'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a grouped language meta condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', NULL, 'group') + ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '!=', NULL, 'group'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped language meta condition (empty result set).')); + + // Test delta and language grouping. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[0], 1, '<', 'delta', 'language') + ->fieldLanguageCondition($this->fields[0], 'en', '!=', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a grouped delta + language meta condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[0], 1, '>=', 'delta', 'language') + ->fieldLanguageCondition($this->fields[0], 'en', '!=', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, delta condition unsatisifed).')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[0], 1, '<', 'delta', 'language') + ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '!=', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, language condition unsatisifed).')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[0], 1, '>=', 'delta', 'language') + ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '!=', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, both conditions unsatisifed).')); + + // Test grouping with another field to ensure that grouping cache is reset + // properly. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle', '=') + ->fieldCondition($this->fields[1], 'shape', 'circle', '=', 'delta', 'language') + ->fieldCondition($this->fields[1], 'color', 'blue', '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[1], 1, '=', 'delta', 'language') + ->fieldLanguageCondition($this->fields[1], LANGUAGE_NONE, '=', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 5), + ), t('Test grouping cache.')); + } + /** * Tests the routing feature of EntityFieldQuery. */