summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2010-06-14 15:41:03 +0000
committerDries Buytaert <dries@buytaert.net>2010-06-14 15:41:03 +0000
commit9cf21be9949ad70b4fb41eed0d4b8204afead2f8 (patch)
treec01d1e3cc750c26a541f81a293ea9bcbd64d73a8
parentf6d56f96c306a9e9ec3202c087321edc39d65b03 (diff)
downloadbrdo-9cf21be9949ad70b4fb41eed0d4b8204afead2f8.tar.gz
brdo-9cf21be9949ad70b4fb41eed0d4b8204afead2f8.tar.bz2
- Patch #780154 by chx, noahb, dhthwy, pwolanin, aspilicious, jhodgdon, dereine, bjaspan: listing API for field API.
-rw-r--r--includes/entity.inc659
-rw-r--r--modules/field/field.api.php58
-rw-r--r--modules/field/field.attach.inc135
-rw-r--r--modules/field/field.crud.inc31
-rw-r--r--modules/field/field.module30
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.module203
-rw-r--r--modules/field/modules/list/list.module2
-rw-r--r--modules/field/tests/field.test237
-rw-r--r--modules/field/tests/field_test.entity.inc25
-rw-r--r--modules/field/tests/field_test.field.inc48
-rw-r--r--modules/field/tests/field_test.install31
-rw-r--r--modules/file/file.module7
-rw-r--r--modules/simpletest/simpletest.info1
-rw-r--r--modules/simpletest/tests/entity_query.test849
-rw-r--r--modules/system/system.api.php19
15 files changed, 1780 insertions, 555 deletions
diff --git a/includes/entity.inc b/includes/entity.inc
index 59dfc5bff..231d36055 100644
--- a/includes/entity.inc
+++ b/includes/entity.inc
@@ -290,3 +290,662 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
$this->entityCache += $entities;
}
}
+
+/**
+ * Exception thrown by EntityFieldQuery() on unsupported query syntax.
+ *
+ * Some storage modules might not support the full range of the syntax for
+ * conditions, and will raise an EntityFieldQueryException when an unsupported
+ * condition was specified.
+ */
+class EntityFieldQueryException extends Exception {}
+
+/**
+ * Retrieves entities matching a given set of conditions.
+ *
+ * This class allows finding entities based on entity properties (for example,
+ * node->changed), field values, and generic entity meta data (bundle,
+ * entity type, entity id, and revision ID). It is not possible to query across
+ * multiple entity types. For example, there is no facility to find published
+ * nodes written by users created in the last hour, as this would require
+ * querying both node->status and user->created.
+ *
+ * Normally we would not want to have public properties on the object, as that
+ * allows the object's state to become inconsistent too easily. However, this
+ * class's standard use case involves primarily code that does need to have
+ * direct access to the collected properties in order to handle alternate
+ * execution routines. We therefore use public properties for simplicity. Note
+ * that code that is simply creating and running a field query should still use
+ * the appropriate methods add conditions on the query.
+ *
+ * Storage engines are not required to support every type of query. By default,
+ * an EntityFieldQueryException will be raised if an unsupported condition is
+ * specified or if the query has field conditions or sorts that are stored in
+ * different field storage engines. However, this logic can be overridden in
+ * hook_entity_query().
+ */
+class EntityFieldQuery {
+ /**
+ * Indicates that both deleted and non-deleted fields should be returned.
+ *
+ * @see EntityFieldQuery::deleted()
+ */
+ const RETURN_ALL = NULL;
+
+ /**
+ * Associative array of entity-generic metadata conditions.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::entityCondition()
+ */
+ public $entityConditions = array();
+
+ /**
+ * List of field conditions.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::fieldCondition()
+ */
+ public $fieldConditions = array();
+
+ /**
+ * List of property conditions.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::propertyCondition()
+ */
+ public $propertyConditions = array();
+
+ /**
+ * List of order clauses for entity-generic metadata.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::entityOrderBy()
+ */
+ public $entityOrder = array();
+
+ /**
+ * List of order clauses for fields.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::fieldOrderBy()
+ */
+ public $fieldOrder = array();
+
+ /**
+ * List of order clauses for entities.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::entityOrderBy()
+ */
+ public $propertyOrder = array();
+
+ /**
+ * The query range.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::range()
+ */
+ public $range = array();
+
+ /**
+ * Query behavior for deleted data.
+ *
+ * TRUE to return only deleted data, FALSE to return only non-deleted data,
+ * EntityFieldQuery::RETURN_ALL to return everything.
+ *
+ * @see EntityFieldQuery::deleted()
+ */
+ public $deleted = FALSE;
+
+ /**
+ * A list of field arrays used.
+ *
+ * Field names passed to EntityFieldQuery::fieldCondition() and
+ * EntityFieldQuery::fieldOrderBy() are run through field_info_field() before
+ * stored in this array. This way, the elements of this array are field
+ * arrays.
+ *
+ * @var array
+ */
+ public $fields = array();
+
+ /**
+ * TRUE if this is a count query, FALSE if it isn't.
+ *
+ * @var boolean
+ */
+ public $count = FALSE;
+
+ /**
+ * Flag indicating whether this is querying current or all revisions.
+ *
+ * @var int
+ *
+ * @see EntityFieldQuery::age()
+ */
+ public $age = FIELD_LOAD_CURRENT;
+
+ /**
+ * The ordered results.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::execute().
+ */
+ public $orderedResults = array();
+
+ /**
+ * The method executing the query, if it is overriding the default.
+ *
+ * @var string
+ *
+ * @see EntityFieldQuery::execute().
+ */
+ public $executeCallback = '';
+
+ /**
+ * Adds a condition on entity-generic metadata.
+ *
+ * If the overall query contains only entity conditions or ordering, or if
+ * there are property conditions, then specifying the entity type is
+ * mandatory. If there are field conditions or ordering but no property
+ * conditions or ordering, then specifying an entity type is optional. While
+ * the field storage engine might support field conditions on more than one
+ * entity type, there is no way to query across multiple entity base tables by
+ * default. To specify the entity type, pass in 'entity_type' for $name,
+ * the type as a string for $value, and no $operator (it's disregarded).
+ *
+ * 'bundle', 'revision_id' and 'entity_id' have no such restrictions.
+ *
+ * @param $name
+ * 'entity_type', 'bundle', 'revision_id' or 'entity_id'.
+ * @param $value
+ * The value for $name. In most cases, this is a scalar. For more complex
+ * options, it is an array. The meaning of each element in the array is
+ * dependent on $operator.
+ * @param $operator
+ * Possible values:
+ * - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These
+ * operators expect $value to be a literal of the same type as the
+ * column.
+ * - 'IN', 'NOT IN': These operators expect $value to be an array of
+ * literals of the same type as the column.
+ * - 'BETWEEN': This operator expects $value to be an array of two literals
+ * of the same type as the column.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function entityCondition($name, $value, $operator = NULL) {
+ $this->entityConditions[$name] = array(
+ 'value' => $value,
+ 'operator' => $operator,
+ );
+ return $this;
+ }
+
+ /**
+ * Adds a condition on field values.
+ *
+ * @param $field
+ * Either a field name or a field array.
+ * @param $column
+ * A column defined in the hook_field_schema() of this field. If this is
+ * omitted then the query will find only entities that have data in this
+ * field, using the entity and property conditions if there are any.
+ * @param $value
+ * The value to test the column value against. In most cases, this is a
+ * scalar. For more complex options, it is an array. The meaning of each
+ * element in the array is dependent on $operator.
+ * @param $operator
+ * Possible values:
+ * - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These
+ * operators expect $value to be a literal of the same type as the
+ * column.
+ * - 'IN', 'NOT IN': These operators expect $value to be an array of
+ * literals of the same type as the column.
+ * - 'BETWEEN': This operator expects $value to be an array of two literals
+ * of the same type as the column.
+ * @param $delta_group
+ * An arbitrary identifier: conditions in the same group must have the same
+ * $delta_group. For example, let's presume a multivalue field which has
+ * two columns, 'color' and 'shape', and for entity id 1, there are two
+ * values: red/square and blue/circle. Entity ID 1 does not have values
+ * corresponding to 'red circle', however if you pass 'red' and 'circle' as
+ * conditions, it will appear in the results - by default queries will run
+ * against any combination of deltas. By passing the conditions with the
+ * same $delta_group it will ensure that only values attached to the same
+ * delta are matched, and entity 1 would then be excluded from the results.
+ * @param $language_group
+ * An arbitrary identifier: conditions in the same group must have the same
+ * $language_group.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) {
+ if (is_scalar($field)) {
+ $field = field_info_field($field);
+ }
+ // Ensure the same index is used for fieldConditions as for fields.
+ $index = count($this->fields);
+ $this->fields[$index] = $field;
+ if (isset($column)) {
+ $this->fieldConditions[$index] = array(
+ 'field' => $field,
+ 'column' => $column,
+ 'value' => $value,
+ 'operator' => $operator,
+ 'delta_group' => $delta_group,
+ 'language_group' => $language_group,
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Adds a condition on an entity-specific property.
+ *
+ * An $entity_type must be specified by calling
+ * EntityFieldCondition::entityCondition('entity_type', $entity_type) before
+ * executing the query. Also, by default only entities stored in SQL are
+ * supported; however, EntityFieldQuery::executeCallback can be set to handle
+ * different entity storage.
+ *
+ * @param $column
+ * A column defined in the hook_schema() of the base table of the entity.
+ * @param $value
+ * The value to test the field against. In most cases, this is a scalar. For
+ * more complex options, it is an array. The meaning of each element in the
+ * array is dependent on $operator.
+ * @param $operator
+ * Possible values:
+ * - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These
+ * operators expect $value to be a literal of the same type as the
+ * column.
+ * - 'IN', 'NOT IN': These operators expect $value to be an array of
+ * literals of the same type as the column.
+ * - 'BETWEEN': This operator expects $value to be an array of two literals
+ * of the same type as the column.
+ * The operator can be omitted, and will default to 'IN' if the value is an
+ * array, or to '=' otherwise.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function propertyCondition($column, $value, $operator = NULL) {
+ $this->propertyConditions[] = array(
+ 'column' => $column,
+ 'value' => $value,
+ 'operator' => $operator,
+ );
+ return $this;
+ }
+
+ /**
+ * Orders the result set by entity-generic metadata.
+ *
+ * If called multiple times, the query will order by each specified column in
+ * the order this method is called.
+ *
+ * @param $name
+ * 'entity_type', 'bundle', 'revision_id' or 'entity_id'.
+ * @param $direction
+ * The direction to sort. Legal values are "ASC" and "DESC".
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function entityOrderBy($name, $direction) {
+ $this->entityOrder[$name] = $direction;
+ return $this;
+ }
+
+ /**
+ * Orders the result set by a given field column.
+ *
+ * If called multiple times, the query will order by each specified column in
+ * the order this method is called.
+ *
+ * @param $field
+ * Either a field name or a field array.
+ * @param $column
+ * A column defined in the hook_field_schema() of this field. entity_id and
+ * bundle can also be used.
+ * @param $direction
+ * The direction to sort. Legal values are "ASC" and "DESC".
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function fieldOrderBy($field, $column, $direction) {
+ if (is_scalar($field)) {
+ $field = field_info_field($field);
+ }
+ // Ensure the same index is used for fieldOrder as for fields.
+ $index = count($this->fields);
+ $this->fields[$index] = $field;
+ $this->fieldOrder[$index] = array(
+ 'field' => $field,
+ 'column' => $column,
+ 'direction' => $direction,
+ );
+ return $this;
+ }
+
+ /**
+ * Orders the result set by an entity-specific property.
+ *
+ * An $entity_type must be specified by calling
+ * EntityFieldCondition::entityCondition('entity_type', $entity_type) before
+ * executing the query.
+ *
+ * If called multiple times, the query will order by each specified column in
+ * the order this method is called.
+ *
+ * @param $column
+ * The column on which to order.
+ * @param $direction
+ * The direction to sort. Legal values are "ASC" and "DESC".
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function propertyOrderBy($column, $direction) {
+ $this->propertyOrder[] = array(
+ 'column' => $column,
+ 'direction' => $direction,
+ );
+ return $this;
+ }
+
+ /**
+ * Sets the query to be a count query only.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function count() {
+ $this->count = TRUE;
+ return $this;
+ }
+
+ /**
+ * Restricts a query to a given range in the result set.
+ *
+ * @param $start
+ * The first entity from the result set to return. If NULL, removes any
+ * range directives that are set.
+ * @param $length
+ * The number of entities to return from the result set.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function range($start = NULL, $length = NULL) {
+ $this->range = array(
+ 'start' => $start,
+ 'length' => $length,
+ );
+ return $this;
+ }
+
+ /**
+ * Filters on the data being deleted.
+ *
+ * @param $deleted
+ * TRUE to only return deleted data, FALSE to return non-deleted data,
+ * EntityFieldQuery::RETURN_ALL to return everything. Defaults to FALSE.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function deleted($deleted = TRUE) {
+ $this->deleted = $deleted;
+ return $this;
+ }
+
+ /**
+ * Queries the current or every revision.
+ *
+ * Note that this only affects field conditions. Property conditions always
+ * apply to the current revision.
+ * @TODO: Once revision tables have been cleaned up, revisit this.
+ *
+ * @param $age
+ * - FIELD_LOAD_CURRENT (default): Query the most recent revisions for all
+ * entities. The results will be keyed by entity type and entity ID.
+ * - FIELD_LOAD_REVISION: Query all revisions. The results will be keyed by
+ * entity type and entity revision ID.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ */
+ public function age($age) {
+ $this->age = $age;
+ return $this;
+ }
+
+ /**
+ * Executes the query.
+ *
+ * After executing the query, $this->orderedResults will contain a list of
+ * the same stub entities in the order returned by the query. This is only
+ * relevant if there are multiple entity types in the returned value and
+ * a field ordering was requested. In every other case, the returned value
+ * contains everything necessary for processing.
+ *
+ * @return
+ * Either a number if count() was called or an array of associative
+ * arrays of stub entities. The outer array keys are entity types, and the
+ * inner array keys are the relevant ID. (In most this cases this will be
+ * the entity ID. The only exception is when age=FIELD_LOAD_REVISION is used
+ * and field conditions or sorts are present -- in this case, the key will
+ * be the revision ID.) The inner array values are always stub entities, as
+ * returned by entity_create_stub_entity(). To traverse the returned array:
+ * @code
+ * foreach ($query->execute() as $entity_type => $entities) {
+ * foreach ($entities as $entity_id => $entity) {
+ * @endcode
+ * Note if the entity type is known, then the following snippet will load
+ * the entities found:
+ * @code
+ * $result = $query->execute;
+ * $entities = entity_load($my_type, array_keys($result[$my_type]));
+ * @endcode
+ */
+ public function execute() {
+ drupal_alter('entity_query', $this);
+ if (function_exists($this->executeCallback)) {
+ return $this->executeCallback($this);
+ }
+ // If there are no field conditions and sorts, and no execute callback
+ // then we default to querying entity tables in SQL.
+ if (empty($this->fields)) {
+ return $this->propertyQuery();
+ }
+ // If no override, find the storage engine to be used.
+ foreach ($this->fields as $field) {
+ if (!isset($storage)) {
+ $storage = $field['storage']['module'];
+ }
+ elseif ($storage != $field['storage']['module']) {
+ throw new EntityFieldQueryException(t("Can't handle more than one field storage engine"));
+ }
+ }
+ if (empty($storage)) {
+ throw new EntityFieldQueryException(t("Field storage engine not found."));
+ }
+ $function = $storage . '_field_storage_query';
+ $result = $function($this);
+ if (!empty($this->propertyConditions)) {
+ throw new EntityFieldQueryException(t('Property query conditions were not handled in !function.', array('!function' => $function)));
+ }
+ if (!empty($this->propertyOrderBy)) {
+ throw new EntityFieldQueryException(t('Property query order by was not handled in !function.', array('!function' => $function)));
+ }
+ return $result;
+ }
+
+ /**
+ * Queries entity tables in SQL for property conditions and sorts.
+ *
+ * This method is only used if there are no field conditions and sorts.
+ *
+ * @return
+ * See EntityFieldQuery::execute().
+ */
+ protected function propertyQuery() {
+ if (empty($this->entityConditions['entity_type'])) {
+ throw new EntityFieldQueryException(t('For this query an entity type must be specified.'));
+ }
+ $entity_type = $this->entityConditions['entity_type']['value'];
+ unset($this->entityConditions['entity_type']);
+ $entity_info = entity_get_info($entity_type);
+ if (empty($entity_info['base table'])) {
+ throw new EntityFieldQueryException(t('Entity %entity has no base table.', array('%entity' => $entity_type)));
+ }
+ $base_table = $entity_info['base table'];
+ $select_query = db_select($base_table);
+ $select_query->addExpression(':entity_type', 'entity_type', array(':entity_type' => $entity_type));
+ // Process the four possible entity condition.
+ // The id field is always present in entity keys.
+ $sql_field = $entity_info['entity keys']['id'];
+ $id_map['entity_id'] = $sql_field;
+ $select_query->addField($base_table, $sql_field, 'entity_id');
+ if (isset($this->entityConditions['entity_id'])) {
+ $this->addCondition($select_query, $sql_field, $this->entityConditions['entity_id']);
+ }
+
+ // If there is a revision key defined, use it.
+ if (!empty($entity_info['entity keys']['revision'])) {
+ $sql_field = $entity_info['entity keys']['revision'];
+ $select_query->addField($base_table, $sql_field, 'revision_id');
+ if (isset($this->entityConditions['revision_id'])) {
+ $this->addCondition($select_query, $sql_field, $this->entityConditions['revision_id']);
+ }
+ }
+ else {
+ $sql_field = 'revision_id';
+ $select_query->addExpression('NULL', 'revision_id');
+ }
+ $id_map['revision_id'] = $sql_field;
+
+ // Handle bundles.
+ if (!empty($entity_info['entity keys']['bundle'])) {
+ $sql_field = $entity_info['entity keys']['bundle'];
+ $select_query->addField($base_table, $sql_field, 'bundle');
+ $having = FALSE;
+ }
+ else {
+ $sql_field = 'bundle';
+ $select_query->addExpression(':bundle', 'bundle', array(':bundle' => $entity_type));
+ $having = TRUE;
+ }
+ $id_map['bundle'] = $sql_field;
+ if (isset($this->entityConditions['bundle'])) {
+ $this->addCondition($select_query, $sql_field, $this->entityConditions['bundle'], $having);
+ }
+
+ foreach ($this->entityOrder as $key => $direction) {
+ if (isset($id_map[$key])) {
+ $select_query->orderBy($id_map[$key], $direction);
+ }
+ else {
+ throw new EntityFieldQueryException(t('Do not know how to order on @key for @entity_type', array('@key' => $key, '@entity_type' => $entity_type)));
+ }
+ }
+ $this->processProperty($select_query, $base_table);
+ return $this->finishQuery($select_query);
+ }
+
+ /**
+ * Finishes the query.
+ *
+ * Adds the range and returns the requested list.
+ *
+ * @param SelectQuery $select_query
+ * A SelectQuery which has entity_type, entity_id, revision_id and bundle
+ * fields added.
+ * @param $id_key
+ * Which field's values to use as the returned array keys.
+ *
+ * @return
+ * See EntityFieldQuery::execute().
+ */
+ function finishQuery($select_query, $id_key = 'entity_id') {
+ if ($this->range) {
+ $select_query->range($this->range['start'], $this->range['length']);
+ }
+ if ($this->count) {
+ return $select_query->countQuery()->execute()->fetchField();
+ }
+ $return = array();
+ foreach ($select_query->execute() as $partial_entity) {
+ $entity = entity_create_stub_entity($partial_entity->entity_type, array($partial_entity->entity_id, $partial_entity->revision_id, $partial_entity->bundle));
+ $return[$partial_entity->entity_type][$partial_entity->$id_key] = $entity;
+ $this->ordered_results[] = $partial_entity;
+ }
+ return $return;
+ }
+
+ /**
+ * Processes the property condition and orders.
+ *
+ * This is a helper for hook_entity_query() and hook_field_storage_query().
+ *
+ * @param SelectQuery $select_query
+ * A SelectQuery object.
+ * @param $entity_base_table
+ * The name of the entity base table. This already should be in
+ * $select_query.
+ */
+ public function processProperty(SelectQuery $select_query, $entity_base_table) {
+ foreach ($this->propertyConditions as $entity_condition) {
+ $this->addCondition($select_query, "$entity_base_table." . $entity_condition['column'], $entity_condition);
+ }
+ foreach ($this->propertyOrder as $order) {
+ $select_query->orderBy("$entity_base_table." . $order['column'], $order['direction']);
+ }
+ unset($this->propertyConditions, $this->propertyOrder);
+ }
+
+ /**
+ * Adds a condition to an already built SelectQuery (internal function).
+ *
+ * This is a helper for hook_entity_query() and hook_field_storage_query().
+ *
+ * @param SelectQuery $select_query
+ * A SelectQuery object.
+ * @param $sql_field
+ * The name of the field.
+ * @param $condition
+ * A condition as described in EntityFieldQuery::fieldCondition() and
+ * EntityFieldQuery::entityCondition().
+ * @param $having
+ * HAVING or WHERE. This is necessary because SQL can't handle WHERE
+ * conditions on aliased columns.
+ */
+ public function addCondition(SelectQuery $select_query, $sql_field, $condition, $having = FALSE) {
+ $method = $having ? 'havingCondition' : 'condition';
+ $like_prefix = '';
+ switch ($condition['operator']) {
+ case 'CONTAINS':
+ $like_prefix = '%';
+ case 'STARTS_WITH':
+ $select_query->$method($sql_field, $like_prefix . db_like($condition['value']) . '%', 'LIKE');
+ break;
+ default:
+ $select_query->$method($sql_field, $condition['value'], $condition['operator']);
+ }
+ }
+
+}
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index cba0957bd..bcbde0564 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -1470,24 +1470,20 @@ function hook_field_storage_delete_revision($entity_type, $entity, $fields) {
}
/**
- * Handle a field query.
+ * Execute an EntityFieldQuery.
*
- * This hook is invoked from field_attach_query() to ask the field storage
- * module to handle a field query.
+ * This hook is called to find the entities having certain entity and field
+ * conditions and sort them in the given field order. If the field storage
+ * engine also handles property sorts and orders, it should unset those
+ * properties in the called object to signal that those have been handled.
*
- * @param $field_name
- * The name of the field to query.
- * @param $conditions
- * See field_attach_query(). A storage module that doesn't support querying a
- * given column should raise a FieldQueryException. Incompatibilities should
- * be mentioned on the module project page.
- * @param $options
- * See field_attach_query(). All option keys are guaranteed to be specified.
+ * @param EntityFieldQuery $query
+ * An EntityFieldQuery.
*
* @return
- * See field_attach_query().
+ * See EntityFieldQuery::execute() for the return values.
*/
-function hook_field_storage_query($field_name, $conditions, $options) {
+function hook_field_storage_query($query) {
// @todo Needs function body
}
@@ -1658,34 +1654,6 @@ function hook_field_storage_pre_update($entity_type, $entity, &$skip_fields) {
}
/**
- * Act before the storage backend runs the query.
- *
- * This hook should be implemented by modules that use
- * hook_field_storage_pre_load(), hook_field_storage_pre_insert() and
- * hook_field_storage_pre_update() to bypass the regular storage engine, to
- * handle field queries.
- *
- * @param $field_name
- * The name of the field to query.
- * @param $conditions
- * See field_attach_query().
- * A storage module that doesn't support querying a given column should raise
- * a FieldQueryException. Incompatibilities should be mentioned on the module
- * project page.
- * @param $options
- * See field_attach_query(). All option keys are guaranteed to be specified.
- * @param $skip_field
- * Boolean, always coming as FALSE.
- * @return
- * See field_attach_query().
- * The $skip_field parameter should be set to TRUE if the query has been
- * handled.
- */
-function hook_field_storage_pre_query($field_name, $conditions, $options, &$skip_field) {
- // @todo Needs function body.
-}
-
-/**
* Alters the display settings of a field before it gets displayed.
*
* Note that instead of hook_field_display_alter(), which is called for all
@@ -1837,8 +1805,12 @@ function hook_field_update_forbid($field, $prior_field, $has_data) {
// Identify the keys that will be lost.
$lost_keys = array_diff(array_keys($field['settings']['allowed_values']), array_keys($prior_field['settings']['allowed_values']));
// If any data exist for those keys, forbid the update.
- $count = field_attach_query($prior_field['id'], array('value', $lost_keys, 'IN'), 1);
- if ($count > 0) {
+ $query = new EntityFieldQuery;
+ $found = $query
+ ->fieldCondition($prior_field['field_name'], 'value', $lost_keys)
+ ->range(0, 1)
+ ->execute();
+ if ($found) {
throw new FieldUpdateForbiddenException("Cannot update a list field not to include keys with existing data");
}
}
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index b1f6a57e0..d461eca46 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -31,15 +31,6 @@ class FieldValidationException extends FieldException {
}
/**
- * Exception thrown by field_attach_query() on unsupported query syntax.
- *
- * Some storage modules might not support the full range of the syntax for
- * conditions, and will raise a FieldQueryException when an usupported
- * condition was specified.
- */
-class FieldQueryException extends FieldException {}
-
-/**
* @defgroup field_storage Field Storage API
* @{
* Implement a storage engine for Field API data.
@@ -1046,132 +1037,6 @@ function field_attach_delete_revision($entity_type, $entity) {
}
/**
- * Retrieve entities matching a given set of conditions.
- *
- * Note that the query 'conditions' only apply to the stored values.
- * In a regular field_attach_load() call, field values additionally go through
- * hook_field_load() and hook_field_attach_load() invocations, which can add
- * to or affect the raw stored values. The results of field_attach_query()
- * might therefore differ from what could be expected by looking at a regular,
- * fully loaded entity.
- *
- * @param $field_id
- * The id of the field to query.
- * @param $conditions
- * An array of query conditions. Each condition is a numerically indexed
- * array, in the form: array(column, value, operator).
- * Not all storage engines are required to support queries on all column, or
- * with all operators below. A FieldQueryException will be raised if an
- * unsupported condition is specified.
- * Supported columns:
- * - any of the columns defined in hook_field_schema() for $field_name's
- * field type: condition on field value,
- * - 'type': condition on entity type (e.g. 'node', 'user'...),
- * - 'bundle': condition on entity bundle (e.g. node type),
- * - 'entity_id': condition on entity id (e.g node nid, user uid...),
- * - 'deleted': condition on whether the field's data is
- * marked deleted for the entity (defaults to FALSE if not specified)
- * The field_attach_query_revisions() function additionally supports:
- * - 'revision_id': condition on entity revision id (e.g node vid).
- * Supported operators:
- * - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH', 'ENDS_WITH',
- * 'CONTAINS': these operators expect the value as a literal of the same
- * type as the column,
- * - 'IN', 'NOT IN': this operator expects the value as an array of
- * literals of the same type as the column.
- * - 'BETWEEN': this operator expects the value as an array of two literals
- * of the same type as the column.
- * The operator can be ommitted, and will default to 'IN' if the value is
- * an array, or to '=' otherwise.
- * Example values for $conditions:
- * @code
- * array(
- * array('type', 'node'),
- * );
- * array(
- * array('bundle', array('article', 'page')),
- * array('value', 12, '>'),
- * );
- * @endcode
- * @param $options
- * An associative array of additional options:
- * - limit: The number of results that is requested. This is only a hint to
- * the storage engine(s); callers should be prepared to handle fewer or
- * more results. Specify FIELD_QUERY_NO_LIMIT to retrieve all available
- * entities. This option has a default value of 0 so callers must make an
- * explicit choice to potentially retrieve an enormous result set.
- * - cursor: A reference to an opaque cursor that allows a caller to iterate
- * through multiple result sets. On the first call, pass 0; the correct
- * value to pass on the next call will be written into the value on return.
- * When there is no more query data available, the value will be filled in
- * with FIELD_QUERY_COMPLETE. If cursor is passed as NULL, the first result
- * set is returned and no next cursor is returned.
- * - count: If TRUE, return a single count of all matching entities; limit and
- * cursor are ignored.
- * - age: Internal use only. Use field_attach_query_revisions() instead of
- * passing FIELD_LOAD_REVISION.
- * - FIELD_LOAD_CURRENT (default): query the most recent revisions for all
- * entities. The results will be keyed by entity type and entity id.
- * - FIELD_LOAD_REVISION: query all revisions. The results will be keyed by
- * entity type and entity revision id.
- * @return
- * An array keyed by entity type (e.g. 'node', 'user'...), then by entity id
- * or revision id (depending of the value of the $age parameter). The values
- * are pseudo-entities with the bundle, id, and revision id fields filled in.
- * Throws a FieldQueryException if the field's storage doesn't support the
- * specified conditions.
- */
-function field_attach_query($field_id, $conditions, $options = array()) {
- // Merge in default options.
- $default_options = array(
- 'limit' => 0,
- 'cursor' => 0,
- 'count' => FALSE,
- 'age' => FIELD_LOAD_CURRENT,
- );
- $options += $default_options;
-
- // Give a chance to 3rd party modules that bypass the storage engine to
- // handle the query.
- $skip_field = FALSE;
- foreach (module_implements('field_storage_pre_query') as $module) {
- $function = $module . '_field_storage_pre_query';
- $results = $function($field_id, $conditions, $options, $skip_field);
- // Stop as soon as a module claims it handled the query.
- if ($skip_field) {
- break;
- }
- }
- // If the request hasn't been handled, let the storage engine handle it.
- if (!$skip_field) {
- $field = field_info_field_by_id($field_id);
- $function = $field['storage']['module'] . '_field_storage_query';
- $results = $function($field_id, $conditions, $options);
- }
-
- return $results;
-}
-
-/**
- * Retrieve entity revisions matching a given set of conditions.
- *
- * See field_attach_query() for more informations.
- *
- * @param $field_id
- * The id of the field to query.
- * @param $conditions
- * See field_attach_query().
- * @param $options
- * An associative array of additional options. See field_attach_query().
- * @return
- * See field_attach_query().
- */
-function field_attach_query_revisions($field_id, $conditions, $options = array()) {
- $options['age'] = FIELD_LOAD_REVISION;
- return field_attach_query($field_id, $conditions, $options);
-}
-
-/**
* Prepare field data prior to display.
*
* This function must be called before field_attach_view(). It lets field
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index ec018faa1..9ba49e822 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -1036,25 +1036,23 @@ function field_purge_batch($batch_size) {
$instances = field_read_instances(array('deleted' => 1), array('include_deleted' => 1));
foreach ($instances as $instance) {
+ // field_purge_data() will need the field array.
$field = field_info_field_by_id($instance['field_id']);
+ // Retrieve some entities.
+ $query = new EntityFieldQuery;
+ $results = $query
+ ->fieldCondition($field)
+ ->entityCondition('bundle', $instance['bundle'])
+ ->deleted(TRUE)
+ ->range(0, $batch_size)
+ ->execute();
- // Retrieve some pseudo-entities.
- $entity_types = field_attach_query($instance['field_id'], array(array('bundle', $instance['bundle']), array('deleted', 1)), array('limit' => $batch_size));
-
- if (count($entity_types) > 0) {
- // Field data for the instance still exists.
- foreach ($entity_types as $entity_type => $entities) {
- field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1));
-
- foreach ($entities as $id => $entity) {
- // field_attach_query() may return more results than we asked for.
- // Stop when he have done our batch size.
- if ($batch_size-- <= 0) {
- return;
- }
-
+ if ($results) {
+ foreach ($results as $entity_type => $stub_entities) {
+ field_attach_load($entity_type, $stub_entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1));
+ foreach ($stub_entities as $stub_entity) {
// Purge the data for the entity.
- field_purge_data($entity_type, $entity, $field, $instance);
+ field_purge_data($entity_type, $stub_entity, $field, $instance);
}
}
}
@@ -1164,4 +1162,3 @@ function field_purge_field($field) {
/**
* @} End of "defgroup field_purge".
*/
-
diff --git a/modules/field/field.module b/modules/field/field.module
index 25b9c8265..4ef400633 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -102,28 +102,6 @@ define('FIELD_LOAD_CURRENT', 'FIELD_LOAD_CURRENT');
define('FIELD_LOAD_REVISION', 'FIELD_LOAD_REVISION');
/**
- * @name Field query flags
- * @{
- * Flags for field_attach_query().
- */
-
-/**
- * Limit argument for field_attach_query() to request all available
- * entities instead of a limited number.
- */
-define('FIELD_QUERY_NO_LIMIT', 'FIELD_QUERY_NO_LIMIT');
-
-/**
- * Cursor return value for field_attach_query() to indicate that no
- * more data is available.
- */
-define('FIELD_QUERY_COMPLETE', 'FIELD_QUERY_COMPLETE');
-
-/**
- * @} End of "Field query flags".
- */
-
-/**
* Exception class thrown by hook_field_update_forbid().
*/
class FieldUpdateForbiddenException extends FieldException {}
@@ -877,8 +855,12 @@ function field_get_items($entity_type, $entity, $field_name, $langcode = NULL) {
* TRUE if the field has data for any entity; FALSE otherwise.
*/
function field_has_data($field) {
- $results = field_attach_query($field['id'], array(), array('limit' => 1));
- return !empty($results);
+ $query = new EntityFieldQuery();
+ return (bool) $query
+ ->fieldCondition($field)
+ ->range(0, 1)
+ ->count()
+ ->execute();
}
/**
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 1c365b41f..71300d98c 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -482,127 +482,114 @@ function field_sql_storage_field_storage_purge($entity_type, $entity, $field, $i
/**
* Implements hook_field_storage_query().
*/
-function field_sql_storage_field_storage_query($field_id, $conditions, $options) {
- $load_current = $options['age'] == FIELD_LOAD_CURRENT;
-
- $field = field_info_field_by_id($field_id);
- $field_name = $field['field_name'];
- $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
- $field_columns = array_keys($field['columns']);
-
- // Build the query.
- $query = db_select($table, 't');
- $query->join('field_config_entity_type', 'e', 't.etid = e.etid');
-
- // Add conditions.
- foreach ($conditions as $condition) {
- // A condition is either a (column, value, operator) triple, or a
- // (column, value) pair with implied operator.
- @list($column, $value, $operator) = $condition;
- // Translate operator and value if needed.
- switch ($operator) {
- case 'STARTS_WITH':
- $operator = 'LIKE';
- $value = db_like($value) . '%';
- break;
-
- case 'ENDS_WITH':
- $operator = 'LIKE';
- $value = '%' . db_like($value);
- break;
-
- case 'CONTAINS':
- $operator = 'LIKE';
- $value = '%' . db_like($value) . '%';
- break;
+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';
+ }
+ else {
+ $tablename_function = '_field_sql_storage_revision_tablename';
+ $id_key = 'revision_id';
+ }
+ $table_aliases = array();
+ // Add tables for the fields used.
+ foreach ($query->fields as $key => $field) {
+ $tablename = $tablename_function($field);
+ // Every field needs a new table.
+ $table_alias = $tablename . $key;
+ $table_aliases[$key] = $table_alias;
+ if ($key) {
+ $select_query->join($tablename, $table_alias, "$table_alias.etid = $field_base_table.etid AND $table_alias.$id_key = $field_base_table.$id_key");
+ }
+ else {
+ $select_query = db_select($tablename, $table_alias);
+ $select_query->fields($table_alias, array('entity_id', 'revision_id', 'bundle'));
+ // As only a numeric ID is stored instead of the entity type add the
+ // field_config_entity_type table to resolve the etid to a more readable
+ // name.
+ $select_query->join('field_config_entity_type', 'fcet', "fcet.etid = $table_alias.etid");
+ $select_query->addField('fcet', 'type', 'entity_type');
+ $field_base_table = $table_alias;
}
- // Translate field columns into prefixed db columns.
- if (in_array($column, $field_columns)) {
- $column = _field_sql_storage_columnname($field_name, $column);
+ if ($field['cardinality'] != 1) {
+ $select_query->distinct();
}
- // Translate entity types into numeric ids. Expressing the condition on the
- // local 'etid' column rather than the JOINed 'type' column avoids a
- // filesort.
- if ($column == 'type') {
- $column = 't.etid';
- if (is_array($value)) {
- foreach (array_keys($value) as $key) {
- $value[$key] = _field_sql_storage_etid($value[$key]);
+ }
+
+ // 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");
}
}
- else {
- $value = _field_sql_storage_etid($value);
- }
- }
- // Track condition on 'deleted'.
- if ($column == 'deleted') {
- $condition_deleted = TRUE;
}
-
- $query->condition($column, $value, $operator);
}
- // Exclude deleted data unless we have a condition on it.
- if (!isset($condition_deleted)) {
- $query->condition('deleted', 0);
+ // Add field orders.
+ foreach ($query->fieldOrder as $key => $order) {
+ $table_alias = $table_aliases[$key];
+ $field = $order['field'];
+ $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $order['column']);
+ $select_query->orderBy($sql_field, $order['direction']);
}
- // For a count query, return the count now.
- if ($options['count']) {
- return $query
- ->fields('t', array('etid', 'entity_id', 'revision_id'))
- ->distinct()
- ->countQuery()
- ->execute()
- ->fetchField();
+ if (isset($query->deleted)) {
+ $select_query->condition("$field_base_table.deleted", (int) $query->deleted);
}
-
- // For a data query, add fields.
- $query
- ->fields('t', array('bundle', 'entity_id', 'revision_id'))
- ->fields('e', array('type'))
- // We need to ensure entities arrive in a consistent order for the
- // range() operation to work.
- ->orderBy('t.etid')
- ->orderBy('t.entity_id');
-
- // Initialize results array
- $return = array();
-
- // Getting $count entities possibly requires reading more than $count rows
- // since fields with multiple values span over several rows. We query for
- // batches of $count rows until we've either read $count entities or received
- // less rows than asked for.
- $entity_count = 0;
- do {
- if ($options['limit'] != FIELD_QUERY_NO_LIMIT) {
- $query->range($options['cursor'], $options['limit']);
- }
- $results = $query->execute();
-
- $row_count = 0;
- foreach ($results as $row) {
- $row_count++;
- $options['cursor']++;
- // If querying all revisions and the entity type has revisions, we need
- // to key the results by revision_ids.
- $entity_type = entity_get_info($row->type);
- $id = ($load_current || empty($entity_type['entity keys']['revision'])) ? $row->entity_id : $row->revision_id;
-
- if (!isset($return[$row->type][$id])) {
- $return[$row->type][$id] = entity_create_stub_entity($row->type, array($row->entity_id, $row->revision_id, $row->bundle));
- $entity_count++;
- }
+ if ($query->propertyConditions || $query->propertyOrder) {
+ if (empty($query->entityConditions['entity_type']['value'])) {
+ throw new EntityFieldQueryException('Property conditions and orders must have an entity type defined.');
}
- } while ($options['limit'] != FIELD_QUERY_NO_LIMIT && $row_count == $options['limit'] && $entity_count < $options['limit']);
-
- // The query is complete when the last batch returns less rows than asked
- // for.
- if ($row_count < $options['limit']) {
- $options['cursor'] = FIELD_QUERY_COMPLETE;
+ $entity_type = $query->entityConditions['entity_type']['value'];
+ $entity_base_table = _field_sql_storage_query_join_entity($select_query, $entity_type, $field_base_table);
+ $query->entityConditions['entity_type']['operator'] = '=';
+ $query->processProperty($select_query, $entity_base_table);
}
+ foreach ($query->entityConditions as $key => $condition) {
+ $sql_field = $key == 'entity_type' ? 'fcet.type' : "$field_base_table.$key";
+ $query->addCondition($select_query, $sql_field, $condition);
+ }
+ foreach ($query->entityOrder as $key => $direction) {
+ $sql_field = $key == 'entity_type' ? 'fcet.type' : "$field_base_table.$key";
+ $query->orderBy($sql_field, $direction);
+ }
+ return $query->finishQuery($select_query, $id_key);
+}
- return $return;
+/**
+ * Adds the base entity table to a field query object.
+ *
+ * @param SelectQuery $select_query
+ * A SelectQuery containing at least one table as specified by
+ * _field_sql_storage_tablename().
+ * @param $entity_type
+ * The entity type for which the base table should be joined.
+ * @param $field_base_table
+ * Name of a table in $select_query. As only INNER JOINs are used, it does
+ * not matter which.
+ *
+ * @return
+ * The name of the entity base table joined in.
+ */
+function _field_sql_storage_query_join_entity(SelectQuery $select_query, $entity_type, $field_base_table) {
+ $entity_info = entity_get_info($entity_type);
+ $entity_base_table = $entity_info['base table'];
+ $entity_field = $entity_info['entity keys']['id'];
+ $select_query->join($entity_base_table, $entity_base_table, "$entity_base_table.$entity_field = $field_base_table.entity_id");
+ return $entity_base_table;
}
/**
diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module
index cf2893712..8aa3489a2 100644
--- a/modules/field/modules/list/list.module
+++ b/modules/field/modules/list/list.module
@@ -99,7 +99,7 @@ function list_field_schema($field) {
*
* @todo: If $has_data, add a form validate function to verify that the
* new allowed values do not exclude any keys for which data already
- * exists in the databae (use field_attach_query()) to find out.
+ * exists in the field storage (use EntityFieldQuery to find out).
* Implement the validate function via hook_field_update_forbid() so
* list.module does not depend on form submission.
*/
diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test
index fa24ada88..015af6646 100644
--- a/modules/field/tests/field.test
+++ b/modules/field/tests/field.test
@@ -623,215 +623,6 @@ class FieldAttachStorageTestCase extends FieldAttachTestCase {
$this->assertFalse(field_read_instance('test_entity', $this->field_name, $this->instance['bundle']), "First field is deleted");
$this->assertFalse(field_read_instance('test_entity', $field_name, $instance['bundle']), "Second field is deleted");
}
-
- /**
- * Test field_attach_query().
- */
- function testFieldAttachQuery() {
- $cardinality = $this->field['cardinality'];
- $langcode = LANGUAGE_NONE;
-
- // Create an additional bundle with an instance of the field.
- field_test_create_bundle('test_bundle_1', 'Test Bundle 1');
- $this->instance2 = $this->instance;
- $this->instance2['bundle'] = 'test_bundle_1';
- field_create_instance($this->instance2);
-
- // Create instances of both fields on the second entity type.
- $instance = $this->instance;
- $instance['entity_type'] = 'test_cacheable_entity';
- field_create_instance($instance);
- $instance2 = $this->instance2;
- $instance2['entity_type'] = 'test_cacheable_entity';
- field_create_instance($instance2);
-
- // Unconditional count query returns 0.
- $count = field_attach_query($this->field_id, array(), array('count' => TRUE));
- $this->assertEqual($count, 0, t('With no entities, count query returns 0.'));
-
- // Create two test entities, using two different types and bundles.
- $entity_types = array(1 => 'test_entity', 2 => 'test_cacheable_entity');
- $entities = array(1 => field_test_create_stub_entity(1, 1, 'test_bundle'), 2 => field_test_create_stub_entity(2, 2, 'test_bundle_1'));
-
- // Create first test entity with random (distinct) values.
- $values = array();
- for ($delta = 0; $delta < $cardinality; $delta++) {
- do {
- $value = mt_rand(1, 127);
- } while (in_array($value, $values));
- $values[$delta] = $value;
- $entities[1]->{$this->field_name}[$langcode][$delta] = array('value' => $values[$delta]);
- }
- field_attach_insert($entity_types[1], $entities[1]);
-
- // Unconditional count query returns 1.
- $count = field_attach_query($this->field_id, array(), array('count' => TRUE));
- $this->assertEqual($count, 1, t('With one entity, count query returns @count.', array('@count' => $count)));
-
- // Create second test entity, sharing a value with the first one.
- $common_value = $values[$cardinality - 1];
- $entities[2]->{$this->field_name} = array($langcode => array(array('value' => $common_value)));
- field_attach_insert($entity_types[2], $entities[2]);
-
- // Query on the entity's values.
- for ($delta = 0; $delta < $cardinality; $delta++) {
- $conditions = array(array('value', $values[$delta]));
- $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertTrue(isset($result[$entity_types[1]][1]), t('Query on value %delta returns the entity', array('%delta' => $delta)));
-
- $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
- $this->assertEqual($count, ($values[$delta] == $common_value) ? 2 : 1, t('Count query on value %delta counts %count entities', array('%delta' => $delta, '%count' => $count)));
- }
-
- // Query on a value that is not in the entity.
- do {
- $different_value = mt_rand(1, 127);
- } while (in_array($different_value, $values));
- $conditions = array(array('value', $different_value));
- $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertFalse(isset($result[$entity_types[1]][1]), t("Query on a value that is not in the entity doesn't return the entity"));
- $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
- $this->assertEqual($count, 0, t("Count query on a value that is not in the entity doesn't count the entity"));
-
- // Query on the value shared by both entities, and discriminate using
- // additional conditions.
-
- $conditions = array(array('value', $common_value));
- $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertTrue(isset($result[$entity_types[1]][1]) && isset($result[$entity_types[2]][2]), t('Query on a value common to both entities returns both entities'));
- $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
- $this->assertEqual($count, 2, t('Count query on a value common to both entities counts both entities'));
-
- $conditions = array(array('type', $entity_types[1]), array('value', $common_value));
- $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both entities and a 'type' condition only returns the relevant entity"));
- $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
- $this->assertEqual($count, 1, t("Count query on a value common to both entities and a 'type' condition only returns the relevant entity"));
-
- $conditions = array(array('bundle', $entities[1]->fttype), array('value', $common_value));
- $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both entities and a 'bundle' condition only returns the relevant entity"));
- $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
- $this->assertEqual($count, 1, t("Count query on a value common to both entities and a 'bundle' condition only counts the relevant entity"));
-
- $conditions = array(array('entity_id', $entities[1]->ftid), array('value', $common_value));
- $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both entities and an 'entity_id' condition only returns the relevant entity"));
- $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
- $this->assertEqual($count, 1, t("Count query on a value common to both entities and an 'entity_id' condition only counts the relevant entity"));
-
- // Test result format.
- $conditions = array(array('value', $values[0]));
- $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $expected = array(
- $entity_types[1] => array(
- $entities[1]->ftid => field_test_create_stub_entity($entities[1]->ftid, $entities[1]->ftvid),
- )
- );
- $this->assertEqual($result, $expected, t('Result format is correct.'));
-
- // Now test the count/offset paging capability.
-
- // Create a new bundle with an instance of the field.
- field_test_create_bundle('offset_bundle', 'Offset Test Bundle');
- $this->instance2 = $this->instance;
- $this->instance2['bundle'] = 'offset_bundle';
- field_create_instance($this->instance2);
-
- // Create 20 test entities, using the new bundle, but with
- // non-sequential ids so we can tell we are getting the right ones
- // back. We do not need unique values since field_attach_query()
- // won't return them anyway.
- $offset_entities = array();
- $offset_id = mt_rand(1, 3);
- for ($i = 0; $i < 20; ++$i) {
- $offset_id += mt_rand(2, 5);
- $offset_entities[$offset_id] = field_test_create_stub_entity($offset_id, $offset_id, 'offset_bundle');
- $offset_entities[$offset_id]->{$this->field_name}[$langcode][0] = array('value' => $offset_id);
- field_attach_insert('test_entity', $offset_entities[$offset_id]);
- }
-
- // Query for the offset entities in batches, making sure we get
- // back the right ones.
- $cursor = 0;
- foreach (array(1 => 1, 3 => 3, 5 => 5, 8 => 8, 13 => 3) as $count => $expect) {
- $found = field_attach_query($this->field_id, array(array('bundle', 'offset_bundle')), array('limit' => $count, 'cursor' => &$cursor));
- if (isset($found['test_entity'])) {
- $this->assertEqual(count($found['test_entity']), $expect, t('Requested @count, expected @expect, got @found, cursor @cursor', array('@count' => $count, '@expect' => $expect, '@found' => count($found['test_entity']), '@cursor' => $cursor)));
- foreach ($found['test_entity'] as $id => $entity) {
- $this->assert(isset($offset_entities[$id]), "Entity $id found");
- unset($offset_entities[$id]);
- }
- }
- else {
- $this->assertEqual(0, $expect, t('Requested @count, expected @expect, got @found, cursor @cursor', array('@count' => $count, '@expect' => $expect, '@found' => 0, '@cursor' => $cursor)));
- }
- }
- $this->assertEqual(count($offset_entities), 0, "All entities found");
- $this->assertEqual($cursor, FIELD_QUERY_COMPLETE, "Cursor is FIELD_QUERY_COMPLETE");
- }
-
- /**
- * Test field_attach_query_revisions().
- */
- function testFieldAttachQueryRevisions() {
- $cardinality = $this->field['cardinality'];
-
- // Create first entity revision with random (distinct) values.
- $entity_type = 'test_entity';
- $entities = array(1 => field_test_create_stub_entity(1, 1), 2 => field_test_create_stub_entity(1, 2));
- $langcode = LANGUAGE_NONE;
- $values = array();
- for ($delta = 0; $delta < $cardinality; $delta++) {
- do {
- $value = mt_rand(1, 127);
- } while (in_array($value, $values));
- $values[$delta] = $value;
- $entities[1]->{$this->field_name}[$langcode][$delta] = array('value' => $values[$delta]);
- }
- field_attach_insert($entity_type, $entities[1]);
-
- // Create second entity revision, sharing a value with the first one.
- $common_value = $values[$cardinality - 1];
- $entities[2]->{$this->field_name}[$langcode][0] = array('value' => $common_value);
- field_attach_update($entity_type, $entities[2]);
-
- // Query on the entity values.
- for ($delta = 0; $delta < $cardinality; $delta++) {
- $conditions = array(array('value', $values[$delta]));
- $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertTrue(isset($result[$entity_type][1]), t('Query on value %delta returns the entity', array('%delta' => $delta)));
- }
-
- // Query on a value that is not in the entity.
- do {
- $different_value = mt_rand(1, 127);
- } while (in_array($different_value, $values));
- $conditions = array(array('value', $different_value));
- $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertFalse(isset($result[$entity_type][1]), t("Query on a value that is not in the entity doesn't return the entity"));
-
- // Query on the value shared by both entities, and discriminate using
- // additional conditions.
-
- $conditions = array(array('value', $common_value));
- $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertTrue(isset($result[$entity_type][1]) && isset($result[$entity_type][2]), t('Query on a value common to both entities returns both entities'));
-
- $conditions = array(array('revision_id', $entities[1]->ftvid), array('value', $common_value));
- $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $this->assertTrue(isset($result[$entity_type][1]) && !isset($result[$entity_type][2]), t("Query on a value common to both entities and a 'revision_id' condition only returns the relevant entity"));
-
- // Test FIELD_QUERY_RETURN_IDS result format.
- $conditions = array(array('value', $values[0]));
- $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
- $expected = array(
- $entity_type => array(
- $entities[1]->ftid => field_test_create_stub_entity($entities[1]->ftid, $entities[1]->ftvid),
- )
- );
- $this->assertEqual($result, $expected, t('FIELD_QUERY_RETURN_IDS result format returns the expect result'));
- }
}
/**
@@ -3026,7 +2817,7 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
* the database and that the appropriate Field API functions can
* operate on the deleted data and instance.
*
- * This tests how field_attach_query() interacts with
+ * This tests how EntityFieldQuery interacts with
* field_delete_instance() and could be moved to FieldCrudTestCase,
* but depends on this class's setUp().
*/
@@ -3035,7 +2826,11 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
$field = reset($this->fields);
// There are 10 entities of this bundle.
- $found = field_attach_query($field['id'], array(array('bundle', $bundle)), array('limit' => FIELD_QUERY_NO_LIMIT));
+ $query = new EntityFieldQuery;
+ $found = $query
+ ->fieldCondition($field)
+ ->entityCondition('bundle', $bundle)
+ ->execute();
$this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found before deleting');
// Delete the instance.
@@ -3048,12 +2843,21 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
$this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle');
// There are 0 entities of this bundle with non-deleted data.
- $found = field_attach_query($field['id'], array(array('bundle', $bundle)), array('limit' => FIELD_QUERY_NO_LIMIT));
+ $query = new EntityFieldQuery;
+ $found = $query
+ ->fieldCondition($field)
+ ->entityCondition('bundle', $bundle)
+ ->execute();
$this->assertTrue(!isset($found['test_entity']), 'No entities found after deleting');
// There are 10 entities of this bundle when deleted fields are allowed, and
// their values are correct.
- $found = field_attach_query($field['id'], array(array('bundle', $bundle), array('deleted', 1)), array('limit' => FIELD_QUERY_NO_LIMIT));
+ $query = new EntityFieldQuery;
+ $found = $query
+ ->fieldCondition($field)
+ ->entityCondition('bundle', $bundle)
+ ->deleted(TRUE)
+ ->execute();
field_attach_load($this->entity_type, $found[$this->entity_type], FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1));
$this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found after deleting');
foreach ($found['test_entity'] as $id => $entity) {
@@ -3085,7 +2889,12 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
field_purge_batch($batch_size);
// There are $count deleted entities left.
- $found = field_attach_query($field['id'], array(array('bundle', $bundle), array('deleted', 1)), array('limit' => FIELD_QUERY_NO_LIMIT));
+ $query = new EntityFieldQuery;
+ $found = $query
+ ->fieldCondition($field)
+ ->entityCondition('bundle', $bundle)
+ ->deleted(TRUE)
+ ->execute();
$this->assertEqual($count ? count($found['test_entity']) : count($found), $count, 'Correct number of entities found after purging 2');
}
diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc
index 6bf582731..b078714b9 100644
--- a/modules/field/tests/field_test.entity.inc
+++ b/modules/field/tests/field_test.entity.inc
@@ -27,6 +27,8 @@ function field_test_entity_info() {
'name' => t('Test Entity'),
'fieldable' => TRUE,
'field cache' => FALSE,
+ 'base table' => 'test_entity',
+ 'revision table' => 'test_entity_revision',
'entity keys' => array(
'id' => 'ftid',
'revision' => 'ftvid',
@@ -48,6 +50,29 @@ function field_test_entity_info() {
'bundles' => $bundles,
'view modes' => $test_entity_modes,
),
+ 'test_entity_bundle_key' => array(
+ 'name' => t('Test Entity with a bundle key.'),
+ 'base table' => 'test_entity_bundle_key',
+ 'fieldable' => TRUE,
+ 'field cache' => FALSE,
+ 'entity keys' => array(
+ 'id' => 'ftid',
+ 'bundle' => 'fttype',
+ ),
+ 'bundles' => array('bundle1' => array('label' => 'Bundle1'), 'bundle2' => array('label' => 'Bundle2')),
+ 'view modes' => $test_entity_modes,
+ ),
+ 'test_entity_bundle' => array(
+ 'name' => t('Test Entity with a specified bundle.'),
+ 'base table' => 'test_entity_bundle',
+ 'fieldable' => TRUE,
+ 'field cache' => FALSE,
+ 'entity keys' => array(
+ 'id' => 'ftid',
+ ),
+ 'bundles' => array('test_entity_2' => array('label' => 'Test entity 2')),
+ 'view modes' => $test_entity_modes,
+ ),
);
}
diff --git a/modules/field/tests/field_test.field.inc b/modules/field/tests/field_test.field.inc
index 116e94a6a..6bb8dd87b 100644
--- a/modules/field/tests/field_test.field.inc
+++ b/modules/field/tests/field_test.field.inc
@@ -26,6 +26,14 @@ function field_test_field_info() {
'default_widget' => 'test_field_widget',
'default_formatter' => 'field_test_default',
),
+ 'shape' => array(
+ 'label' => t('Shape'),
+ 'description' => t('Another dummy field type.'),
+ 'settings' => array(),
+ 'instance_settings' => array(),
+ 'default_widget' => 'test_field_widget',
+ 'default_formatter' => 'field_test_default',
+ ),
'hidden_test_field' => array(
'no_ui' => TRUE,
'label' => t('Hidden from UI test field'),
@@ -42,18 +50,36 @@ function field_test_field_info() {
* Implements hook_field_schema().
*/
function field_test_field_schema($field) {
- return array(
- 'columns' => array(
- 'value' => array(
- 'type' => 'int',
- 'size' => 'tiny',
- 'not null' => FALSE,
+ if ($field['type'] == 'test_field') {
+ return array(
+ 'columns' => array(
+ 'value' => array(
+ 'type' => 'int',
+ 'size' => 'medium',
+ 'not null' => FALSE,
+ ),
),
- ),
- 'indexes' => array(
- 'value' => array('value'),
- ),
- );
+ 'indexes' => array(
+ 'value' => array('value'),
+ ),
+ );
+ }
+ else {
+ return array(
+ 'columns' => array(
+ 'shape' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => FALSE,
+ ),
+ 'color' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => FALSE,
+ ),
+ ),
+ );
+ }
}
/**
diff --git a/modules/field/tests/field_test.install b/modules/field/tests/field_test.install
index 72a2deee6..d16d79ee8 100644
--- a/modules/field/tests/field_test.install
+++ b/modules/field/tests/field_test.install
@@ -50,6 +50,37 @@ function field_test_schema() {
),
'primary key' => array('ftid'),
);
+ $schema['test_entity_bundle_key'] = array(
+ 'description' => 'The base table for test entities with a bundle key.',
+ 'fields' => array(
+ 'ftid' => array(
+ 'description' => 'The primary indentifier for a test_entity_bundle_key.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'fttype' => array(
+ 'description' => 'The type of this test_entity.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => FALSE,
+ 'default' => '',
+ ),
+ ),
+ );
+ $schema['test_entity_bundle'] = array(
+ 'description' => 'The base table for test entities with a bundle.',
+ 'fields' => array(
+ 'ftid' => array(
+ 'description' => 'The primary indentifier for a test_entity_bundle.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ );
$schema['test_entity_revision'] = array(
'description' => 'Stores information about each saved version of a {test_entity}.',
'fields' => array(
diff --git a/modules/file/file.module b/modules/file/file.module
index 350387a86..92d0f95a0 100644
--- a/modules/file/file.module
+++ b/modules/file/file.module
@@ -969,8 +969,11 @@ function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISI
foreach ($fields as $field_name => $file_field) {
if ((empty($field_type) || $field['type'] == $field_type) && !isset($references[$field_name])) {
// Get each time this file is used within a field.
- $cursor = 0;
- $references[$field_name] = field_attach_query($file_field['id'], array(array('fid', $file->fid)), array('limit' => FIELD_QUERY_NO_LIMIT, 'cursor' => &$cursor, 'age'=> $age));
+ $query = new EntityFieldQuery;
+ $query
+ ->fieldCondition($file_field, 'fid', $file->fid)
+ ->age($age);
+ $references[$field_name] = $query->execute();
}
}
diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info
index a66b8b9cc..aa038fff4 100644
--- a/modules/simpletest/simpletest.info
+++ b/modules/simpletest/simpletest.info
@@ -19,6 +19,7 @@ files[] = tests/bootstrap.test
files[] = tests/cache.test
files[] = tests/common.test
files[] = tests/database_test.test
+files[] = tests/entity_query.test
files[] = tests/error.test
files[] = tests/file.test
files[] = tests/filetransfer.test
diff --git a/modules/simpletest/tests/entity_query.test b/modules/simpletest/tests/entity_query.test
new file mode 100644
index 000000000..2766dedf7
--- /dev/null
+++ b/modules/simpletest/tests/entity_query.test
@@ -0,0 +1,849 @@
+<?php
+
+// $Id$
+
+/**
+ * @file
+ * Unit test file for the entity API.
+ */
+
+/**
+ * Tests EntityFieldQuery.
+ */
+class EntityFieldQueryTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity query',
+ 'description' => 'Test the EntityFieldQuery class.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp(array('field_test'));
+
+ field_attach_create_bundle('bundle1', 'test_entity_bundle_key');
+ field_attach_create_bundle('bundle2', 'test_entity_bundle_key');
+ field_attach_create_bundle('test_bundle', 'test_entity');
+ field_attach_create_bundle('test_entity_bundle', 'test_entity_bundle');
+
+ $instances = array();
+ $this->fields = array();
+ $this->field_names[0] = $field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 4);
+ $field = field_create_field($field);
+ $this->fields[0] = $field;
+ $instance = array(
+ 'field_name' => $field_name,
+ 'entity_type' => '',
+ 'bundle' => '',
+ 'label' => $this->randomName() . '_label',
+ 'description' => $this->randomName() . '_description',
+ 'weight' => mt_rand(0, 127),
+ 'settings' => array(
+ 'test_instance_setting' => $this->randomName(),
+ ),
+ 'widget' => array(
+ 'type' => 'test_field_widget',
+ 'label' => 'Test Field',
+ 'settings' => array(
+ 'test_widget_setting' => $this->randomName(),
+ )
+ )
+ );
+
+ $instances[0] = $instance;
+
+ // Add an instance to that bundle.
+ $instances[0]['bundle'] = 'bundle1';
+ $instances[0]['entity_type'] = 'test_entity_bundle_key';
+ field_create_instance($instances[0]);
+ $instances[0]['bundle'] = $instances[0]['entity_type'] = 'test_entity_bundle';
+ field_create_instance($instances[0]);
+
+ $this->field_names[1] = $field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4);
+ $field = field_create_field($field);
+ $this->fields[1] = $field;
+ $instance = array(
+ 'field_name' => $field_name,
+ 'entity_type' => '',
+ 'bundle' => '',
+ 'label' => $this->randomName() . '_label',
+ 'description' => $this->randomName() . '_description',
+ 'weight' => mt_rand(0, 127),
+ 'settings' => array(
+ 'test_instance_setting' => $this->randomName(),
+ ),
+ 'widget' => array(
+ 'type' => 'test_field_widget',
+ 'label' => 'Test Field',
+ 'settings' => array(
+ 'test_widget_setting' => $this->randomName(),
+ )
+ )
+ );
+
+ $instances[1] = $instance;
+
+ // Add an instance to that bundle.
+ $instances[1]['bundle'] = 'bundle1';
+ $instances[1]['entity_type'] = 'test_entity_bundle_key';
+ field_create_instance($instances[1]);
+ $instances[1]['bundle'] = $instances[1]['entity_type'] = 'test_entity_bundle';
+ field_create_instance($instances[1]);
+
+ $this->instances = $instances;
+ // Write entity base table if there is one.
+ $entities = array();
+
+ // Create entities which have a 'bundle key' defined.
+ for ($i = 1; $i < 7; $i++) {
+ $entity = new stdClass;
+ $entity->ftid = $i;
+ $entity->fttype = ($i < 5) ? 'bundle1' : 'bundle2';
+
+ $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i;
+ drupal_write_record('test_entity_bundle_key', $entity);
+ field_attach_insert('test_entity_bundle_key', $entity);
+ }
+
+ $entity = new stdClass;
+ $entity->ftid = 5;
+ $entity->fttype = 'bundle2';
+ $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['shape'] = 'square';
+ $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['color'] = 'red';
+ $entity->{$this->field_names[1]}[LANGUAGE_NONE][1]['shape'] = 'circle';
+ $entity->{$this->field_names[1]}[LANGUAGE_NONE][1]['color'] = 'blue';
+ drupal_write_record('test_entity_bundle', $entity);
+ field_attach_insert('test_entity_bundle', $entity);
+
+ $instances[2] = $instance;
+ $instances[2]['bundle'] = 'test_bundle';
+ $instances[2]['field_name'] = $this->field_names[0];
+ $instances[2]['entity_type'] = 'test_entity';
+ field_create_instance($instances[2]);
+
+ // Create entities with support for revisions.
+ for ($i = 1; $i < 5; $i++) {
+ $entity = new stdClass;
+ $entity->ftid = $i;
+ $entity->ftvid = $i;
+ $entity->fttype = 'test_bundle';
+ $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i;
+
+ drupal_write_record('test_entity', $entity);
+ field_attach_insert('test_entity', $entity);
+ drupal_write_record('test_entity_revision', $entity);
+ }
+
+ // Add two revisions to an entity.
+ for ($i = 100; $i < 102; $i++) {
+ $entity = new stdClass;
+ $entity->ftid = 4;
+ $entity->ftvid = $i;
+ $entity->fttype = 'test_bundle';
+ $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i;
+
+ drupal_write_record('test_entity', $entity, 'ftid');
+ drupal_write_record('test_entity_revision', $entity);
+
+ db_update('test_entity')
+ ->fields(array('ftvid' => $entity->ftvid))
+ ->condition('ftid', $entity->ftid)
+ ->execute();
+
+ field_attach_update('test_entity', $entity);
+ }
+ }
+
+ /**
+ * Tests EntityFieldQuery.
+ */
+ function testEntityFieldQuery() {
+ // Test entity_type condition.
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test entity entity_type condition.'));
+
+ // Test entity_id condition.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->entityCondition('entity_id', '3');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ ), t('Test entity entity_id condition.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', '3');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ ), t('Test entity entity_id condition and entity_id property condition.'));
+
+ // Test bundle condition.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->entityCondition('bundle', 'bundle1');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ ), t('Test entity bundle condition: bundle1.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->entityCondition('bundle', 'bundle2');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test entity bundle condition: bundle2.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('fttype', 'bundle2');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test entity bundle condition and bundle property condition.'));
+
+ // Test revision_id condition.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity')
+ ->entityCondition('revision_id', '3');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 3),
+ ), t('Test entity revision_id condition.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity')
+ ->propertyCondition('ftvid', '3');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 3),
+ ), t('Test entity revision_id condition and revision_id property condition.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity')
+ ->fieldCondition($this->fields[0], 'value', 100, '>=')
+ ->age(FIELD_LOAD_REVISION);
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 100),
+ array('test_entity', 101),
+ ), t('Test revision age.'));
+
+ // Test entity sort by entity_id.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->entityOrderBy('entity_id', 'ASC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test sort entity entity_id in ascending order.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->entityOrderBy('entity_id', 'DESC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 6),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 1),
+ ), t('Test sort entity entity_id in descending order.'), TRUE);
+
+ // Test property sort by entity id.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyOrderBy('ftid', 'ASC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test sort entity entity_id property in ascending order.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyOrderBy('ftid', 'DESC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 6),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 1),
+ ), t('Test sort entity entity_id property in descending order.'), TRUE);
+
+ // Test entity sort by bundle.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->entityOrderBy('bundle', 'ASC')
+ ->propertyOrderBy('ftid', 'ASC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test sort entity bundle in ascending order.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $foo = $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->entityOrderBy('bundle', 'DESC')
+ ->propertyOrderBy('ftid', 'DESC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 6),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 1),
+ ), t('Test sort entity bundle in descending order.'), TRUE);
+
+ // Test entity sort by revision_id.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity')
+ ->entityOrderBy('revision_id', 'ASC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 1),
+ array('test_entity', 2),
+ array('test_entity', 3),
+ array('test_entity', 4),
+ ), t('Test sort entity revision_id in ascending order.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity')
+ ->entityOrderBy('revision_id', 'DESC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 4),
+ array('test_entity', 3),
+ array('test_entity', 2),
+ array('test_entity', 1),
+ ), t('Test sort entity revision_id in descending order.'), TRUE);
+
+ // Test property sort by revision_id.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity')
+ ->propertyOrderBy('ftvid', 'ASC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 1),
+ array('test_entity', 2),
+ array('test_entity', 3),
+ array('test_entity', 4),
+ ), t('Test sort entity revision_id property in ascending order.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity')
+ ->propertyOrderBy('ftvid', 'DESC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 4),
+ array('test_entity', 3),
+ array('test_entity', 2),
+ array('test_entity', 1),
+ ), t('Test sort entity revision_id property in descending order.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $query->fieldOrderBy($this->fields[0], 'value', 'ASC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ ), t('Test sort field in ascending order without field condition.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $query->fieldOrderBy($this->fields[0], 'value', 'DESC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 1),
+ ), t('Test sort field in descending order without field condition.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $query->fieldCondition($this->fields[0], 'value', 0, '>');
+ $query->fieldOrderBy($this->fields[0], 'value', 'asc');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ ), t('Test sort field in ascending order.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $query->fieldCondition($this->fields[0], 'value', 0, '>');
+ $query->fieldOrderBy($this->fields[0], 'value', 'desc');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 1),
+ ), t('Test sort field in descending order.'), TRUE);
+
+ // Test "in" operation with entity entity_type condition and entity_id
+ // property condition.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', array(1, 3, 4), 'IN');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ ), t('Test "in" operation with entity entity_type condition and entity_id property condition.'));
+
+ // Test "in" operation with entity entity_type condition and entity_id
+ // property condition. Sort in descending order by entity_id.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', array(1, 3, 4), 'IN')
+ ->propertyOrderBy('ftid', 'DESC');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 1),
+ ), t('Test "in" operation with entity entity_type condition and entity_id property condition. Sort entity_id in descending order.'), TRUE);
+
+ // Test query count
+ $query = new EntityFieldQuery();
+ $query_count = $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->count()
+ ->execute();
+ $this->assertEqual($query_count, 6, t('Test query count on entity condition.'));
+
+ $query = new EntityFieldQuery();
+ $query_count = $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', '1')
+ ->count()
+ ->execute();
+ $this->assertEqual($query_count, 1, t('Test query count on entity and property condition.'));
+
+ $query = new EntityFieldQuery();
+ $query_count = $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', '4', '>')
+ ->count()
+ ->execute();
+ $this->assertEqual($query_count, 2, t('Test query count on entity and property condition with operator.'));
+
+ $query = new EntityFieldQuery();
+ $query_count = $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->fieldCondition($this->fields[0], 'value', 3, '=')
+ ->count()
+ ->execute();
+ $this->assertEqual($query_count, 1, t('Test query count on field condition.'));
+
+ // First, test without options.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', 1, 'CONTAINS');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ ), t('Test the "contains" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 3, 'CONTAINS');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity', 3),
+ ), t('Test the "contains" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', 1, '=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ ), t('Test the "equal to" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 3, '=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity', 3),
+ ), t('Test the "equal to" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', 3, '!=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test the "not equal to" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 3, '!=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 4),
+ array('test_entity', 1),
+ array('test_entity', 2),
+ array('test_entity', 4),
+ ), t('Test the "not equal to" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', 2, '<');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ ), t('Test the "less than" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 2, '<');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity', 1),
+ ), t('Test the "less than" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', 2, '<=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ ), t('Test the "less than or equal to" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 2, '<=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity', 1),
+ array('test_entity', 2),
+ ), t('Test the "less than or equal to" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', 4, '>');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test the "greater than" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 2, '>');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ array('test_entity', 3),
+ array('test_entity', 4),
+ ), t('Test the "greater than" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', 4, '>=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test the "greater than or equal to" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 3, '>=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ array('test_entity', 3),
+ array('test_entity', 4),
+ ), t('Test the "greater than or equal to" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', array(3, 4), 'NOT IN');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test the "not in" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', array(3, 4, 100, 101), 'NOT IN');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity', 1),
+ array('test_entity', 2),
+ ), t('Test the "not in" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', array(3, 4), 'IN');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ ), t('Test the "in" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', array(2, 3), 'IN');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity', 2),
+ array('test_entity', 3),
+ ), t('Test the "in" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', array(1, 3), 'BETWEEN');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ ), t('Test the "between" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', array(1, 3), 'BETWEEN');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity', 1),
+ array('test_entity', 2),
+ array('test_entity', 3),
+ ), t('Test the "between" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('fttype', 'bun', 'STARTS_WITH');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test the "starts_with" operation on a property.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[1], 'shape', 'squ', 'STARTS_WITH');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle', 5),
+ ), t('Test the "starts_with" operation on a field.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 3);
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity', 3),
+ ), t('Test omission of an operator with a single item.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', array(2, 3));
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ array('test_entity', 2),
+ array('test_entity', 3),
+ ), t('Test omission of an operator with multiple items.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->propertyCondition('ftid', 1, '>')
+ ->fieldCondition($this->fields[0], 'value', 4, '<');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 2),
+ array('test_entity_bundle_key', 3),
+ ), t('Test entity, property and field conditions.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle_key')
+ ->entityCondition('bundle', 'bundle', 'STARTS_WITH')
+ ->propertyCondition('ftid', 4)
+ ->fieldCondition($this->fields[0], 'value', 4);
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 4),
+ ), t('Test entity condition with "starts_with" operation, and property and field conditions.'));
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $query->propertyOrderBy('ftid', 'asc');
+ $query->range(0, 2);
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ ), t('Test limit on a property.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $query->fieldCondition($this->fields[0], 'value', 0, '>=');
+ $query->fieldOrderBy($this->fields[0], 'value', 'asc');
+ $query->range(0, 2);
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 1),
+ array('test_entity_bundle_key', 2),
+ ), t('Test limit on a field.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $query->propertyOrderBy('ftid', 'asc');
+ $query->range(4, 6);
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 5),
+ array('test_entity_bundle_key', 6),
+ ), t('Test offset on a property.'), TRUE);
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'test_entity_bundle_key');
+ $query->fieldCondition($this->fields[0], 'value', 0, '>');
+ $query->fieldOrderBy($this->fields[0], 'value', 'asc');
+ $query->range(2, 4);
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ ), t('Test offset on a field.'), TRUE);
+
+ for ($i = 6; $i < 10; $i++) {
+ $entity = new stdClass;
+ $entity->ftid = $i;
+ $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i - 5;
+ drupal_write_record('test_entity_bundle', $entity);
+ field_attach_insert('test_entity_bundle', $entity);
+ }
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', 2, '>');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle_key', 4),
+ array('test_entity', 3),
+ array('test_entity', 4),
+ array('test_entity_bundle', 8),
+ array('test_entity_bundle', 9),
+ ), t('Select a field across multiple entities.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[1], 'shape', 'square');
+ $query->fieldCondition($this->fields[1], 'color', 'blue');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle', 5),
+ ), t('Test without a delta group.'));
+
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[1], 'shape', 'square', '=', 'group');
+ $query->fieldCondition($this->fields[1], 'color', 'blue', '=', 'group');
+ $this->assertEntityFieldQuery($query, array(), t('Test with a delta group.'));
+
+ // Test query on a deleted field.
+ field_attach_delete_bundle('test_entity_bundle_key', 'bundle1');
+ field_attach_delete_bundle('test_entity', 'test_bundle');
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', '3');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle', 8),
+ ), t('Test query on a field after deleting field from some entities.'));
+
+ field_attach_delete_bundle('test_entity_bundle', 'test_entity_bundle');
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($this->fields[0], 'value', '3');
+ $this->assertEntityFieldQuery($query, array(), t('Test query on a field after deleting field from all entities.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->fieldCondition($this->fields[0], 'value', '3')
+ ->deleted(TRUE);
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle_key', 3),
+ array('test_entity_bundle', 8),
+ array('test_entity', 3),
+ ), t('Test query on a deleted field with deleted option set to TRUE.'));
+
+ $pass = FALSE;
+ $query = new EntityFieldQuery();
+ try {
+ $query->execute();
+ }
+ catch (EntityFieldQueryException $exception) {
+ $pass = ($exception->getMessage() == t('For this query an entity type must be specified.'));
+ }
+ $this->assertTrue($pass, t("Can't query the universe."));
+ }
+
+ /**
+ * Fetches the results of an EntityFieldQuery and compares.
+ *
+ * @param $query
+ * An EntityFieldQuery to run.
+ * @param $intended_results
+ * A list of results, every entry is again a list, first being the entity
+ * type, the second being the entity_id.
+ * @param $message
+ * The message to be displayed as the result of this test.
+ * @param $ordered
+ * If FALSE then the result of EntityFieldQuery will match
+ * $intended_results even if the order is not the same. If TRUE then order
+ * should match too.
+ */
+ function assertEntityFieldQuery($query, $intended_results, $message, $ordered = FALSE) {
+ $results = array();
+ foreach ($query->execute() as $entity_type => $entity_ids) {
+ foreach ($entity_ids as $entity_id => $stub_entity) {
+ $results[] = array($entity_type, $entity_id);
+ }
+ }
+ if (!isset($ordered) || !$ordered) {
+ sort($results);
+ sort($intended_results);
+ }
+ $this->assertEqual($results, $intended_results, $message);
+ }
+}
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 32c55cf92..522866073 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -285,6 +285,25 @@ function hook_entity_update($entity, $type) {
}
/**
+ * Alter or execute an EntityFieldQuery.
+ *
+ * @param EntityFieldQuery $query
+ * An EntityFieldQuery. One of the most important properties to be changed is
+ * EntityFieldQuery::executeCallback. If this is set to an existing function,
+ * this function will get the query as its single argument and its result
+ * will be the returned as the result of EntityFieldQuery::execute(). This can
+ * be used to change the behavior of EntityFieldQuery entirely. For example,
+ * the default implementation can only deal with one field storage engine, but
+ * it is possible to write a module that can query across field storage
+ * engines. Also, the default implementation presumes entities are stored in
+ * SQL, but the execute callback could instead query any other entity storage,
+ * local or remote.
+ */
+function hook_entity_query_alter($query) {
+ $query->executeCallback = 'my_module_query_callback';
+}
+
+/**
* Define administrative paths.
*
* Modules may specify whether or not the paths they define in hook_menu() are