=== modified file 'modules/field/field.attach.inc'
--- modules/field/field.attach.inc	2010-04-04 12:48:18 +0000
+++ modules/field/field.attach.inc	2010-04-29 07:31:50 +0000
@@ -1044,108 +1044,240 @@ function field_attach_delete_revision($e
 /**
  * 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.
+ * Storage engines are not required to support all kinds of queries. A
+ * FieldQueryException will be raised if an unsupported condition is specified
+ * or if the query has field conditions / sorts which are stored in different
+ * field storage engines. It entirely depends on the field storage engine what
+ * sort of queries it supports and what kind it does not. For example, if a
+ * field is stored in a separate datastore than the entities they belong to
+ * then it's very likely that passing in both a fieldCondition and an
+ * entityCondition will raise this exception.
  */
-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;
+class FieldAttachQuery {
+  protected $fieldConditions = array();
+  protected $entityConditions = array();
+  protected $entityTypes = array();
+  protected $fieldOrder = array();
+  protected $entityOrder = array();
+  protected $range = array();
+  protected $deleted = FALSE;
+  protected $storage = NULL;
+
+  /**
+   * A condition on field values.
+   *
+   * @param $field_name
+   *   Name of the field.
+   * @param $column
+   *   A column defined in the hook_field_schema() of this field. entity_id
+   *   can also be used.
+   * @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 the $operator.
+   * @param $operator
+   *   Possible values:
+   *   - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH': 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.
+   * @param $delta_group
+   *   An arbitrary identifier, conditions in the same group must have the same
+   *   delta. For example, if a multivalue field has two columns, 'color' and
+   *   'shape' and two values are red square and blue circle and the query
+   *   should find a red circle then the two conditions
+   *   (color = red, shape = circle) will wrongly find this entity. If they
+   *   have the same delta then it will work as expected.
+   * @param $language_group
+   *   An arbitrary identifier, conditions in the same group must have the same
+   *   language.
+   * @return fieldAttachQuery
+   *   The called object.
+   */
+  public function fieldCondition($field_name, $column, $value, $operator = NULL, $delta_group = NULL, $language_group = NULL) {
+    $this->findStorage($field_name);
+    $this->fieldConditions[] = array(
+      'field_name' => $field_name,
+      'column' => $column,
+      'value' => $value,
+      'operator' => $operator,
+      'delta_group' => $delta_group,
+      'language_group' => $language_group,
+    );
+  }
+
+  /**
+   * A condition on the entity base table.
+   *
+   * entityTypes() and entityCondition() can not be used on the same query as
+   * entityCondition() also indicates the single entity_type to be used.
+   *
+   * @param $entity_type
+   *   An entity type.
+   * @param $column
+   *   A column defined in the hook_schema() of the base table of this entity
+   *   type.
+   * @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 the $operator.
+   * @param $operator
+   *   Possible values:
+   *   - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH': 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.
+   * @return fieldAttachQuery
+   *   The called object.
+   */
+  public function entityCondition($entity_type, $column, $value, $operator = NULL) {
+    $this->entityConditions[] = array(
+      'entity_type' => $entity_type,
+      'column' => $column,
+      'value' => $value,
+      'operator' => $operator,
+    );
+  }
+
+  /**
+   * A list of entity types to restrict the query to.
+   *
+   * entityTypes() and entityCondition() can not be used on the same query as
+   * entityCondition() also indicates the single entity_type to be used.
+   *
+   * @param $entity_types
+   *   A list of entity types.
+   */
+  public function entityTypes($entity_types) {
+    $this->entityTypes = $entity_types;
+  }
+
+  /**
+   * Orders the result set by a given entity column.
+   *
+   * 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 FieldAttachQuery
+   *   The called object.
+   */
+  public function entityOrderBy($column, $direction) {
+    $this->entityOrder[] = array(
+      'column' => $column,
+      'direction' => $direction,
+    );
+  }
+  public function fieldOrderBy($field_name, $column, $direction) {
+    $this->findStorage($field_name);
+    $this->fieldOrder[] = array(
+      'field_name' => $field_name,
+      'column' => $column,
+      'direction' => $direction,
+    );
+  }
+  /**
+   * 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 $limit
+   *   The number of entities to return from the result set.
+   * @return fieldAttachQuery
+   *   The called object.
+   */
+  public function range($start = NULL, $length = NULL) {
+    $this->range = array(
+      'start' => $start,
+      'length' => $length,
+    );
+  }
+
+  /**
+   * Filter on the data being deleted.
+   *
+   * @param $deleted
+   *   TRUE to only return deleted data, FALSE to return non-deleted data, NULL
+   *   to return everything. Defaults to FALSE.
+   */
+  public function deleted($deleted) {
+    $this->deleted = $deleted;
+  }
 
-  // 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);
+  public function execute() {
+    foreach (module_implements('field_storage_pre_query') as $module) {
+      $function = $module . '_field_storage_pre_query';
+      $result = $function($this->entityTypes, $this->entityConditions, $this->fieldConditions, $this->fieldOrder, $this->entityOrder, $this->range, $this->deleted);
+      if ($result) {
+        return $result;
+      }
+    }
+    if (isset($this->storage)) {
+      $function = $this->storage['module'] . '_field_storage_query';
+      return $function($this->entityTypes, $this->entityConditions, $this->fieldConditions, $this->fieldOrder, $this->entityOrder, $this->range, $this->deleted);
+    }
+    return $this->entityQuery();
   }
 
-  return $results;
+  protected function entityQuery() {
+    $entity_type = '';
+    if ($this->entityConditions) {
+      $entity_type = $this->entityConditions[0]['entity_type'];
+    }
+    else {
+      $n = count($this->entityTypes);
+      if ($n == 1) {
+        $entity_type = $this->entityTypes[0];
+      }
+      elseif ($n > 1) {
+        throw new FieldQueryException(t("Can't query multiple entity tables at once."));
+      }
+      else {
+        throw new FieldQueryException(t('Empty query'));
+      }
+    }
+    $entity_info = entity_get_info($entity_type);
+    $base_table = isset($entity_info['base_table']) ? $entity_info['base_table'] : $entity_type;
+    $query = db_select($base_table);
+    $query->addField($base_table, $entity_info['entity keys']['id'], 'entity_id');
+    $query->addExpression(':entity_type', 'entity_type', array(':entity_type' => $entity_type));
+    foreach ($this->entityConditions as $entity_condition) {
+      if ($entity_type != $entity_condition['entity_type']) {
+        throw new FieldQueryException(t("Can't query multiple entity tables at once."));
+      }
+      $query->condition($entity_condition['column'], $entity_condition['value'], $entity_condition['operator']);
+    }
+    foreach ($this->entityOrder as $order) {
+      $query->orderBy("$base_table." . $order['column'], $order['direction']);
+    }
+    if ($this->range) {
+      $query->range($this->range['start'], $this->range['length']);
+    }
+    return $query->execute();
+  }
+
+  protected function findStorage($field_name) {
+    $field = field_info_field($field_name);
+    $storage = $field['storage'];
+    if (!isset($this->storage)) {
+      $this->storage = $storage;
+    }
+    elseif (array_diff($this->storage, $field['storage'])) {
+      throw new FieldQueryException(t("Can't handle more than one field storage engine"));
+    }
+  }
 }
 
 /**

=== modified file 'modules/field/modules/field_sql_storage/field_sql_storage.module'
--- modules/field/modules/field_sql_storage/field_sql_storage.module	2010-03-27 05:52:49 +0000
+++ modules/field/modules/field_sql_storage/field_sql_storage.module	2010-04-29 06:58:01 +0000
@@ -472,129 +472,87 @@ function field_sql_storage_field_storage
 /**
  * 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;
-    }
-    // Translate field columns into prefixed db columns.
-    if (in_array($column, $field_columns)) {
-      $column = _field_sql_storage_columnname($field_name, $column);
-    }
-    // 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]);
-        }
+function field_sql_storage_field_storage_query($entity_types, $entity_conditions, $field_conditions, $field_order, $entity_order, $range, $deleted) {
+  $delta_groups = array();
+  foreach ($field_conditions as $key => $condition) {
+    $field = field_info_field($condition['field_name']);
+    $tablename = _field_sql_storage_tablename($field);
+    if (isset($query)) {
+      $table_alias = $query->join($tablename, $tablename, "$tablename.etid = $field_base_table.etid AND $tablename.entity_id = $field_base_table.entity_id");
+    }
+    else {
+      $query = db_select($tablename);
+      $query->addField($tablename, 'entity_id', 'entity_id');
+      $query->join('field_config_entity_type', 'fcet', "fcet.etid = $tablename.etid");
+      $query->addField('fcet', 'type', 'entity_type');
+      $table_alias = $field_base_table = $tablename;
+    }
+    $columnname = _field_sql_storage_columnname($condition['field_name'], $condition['column']);
+    $query->condition("$table_alias.$columnname", $condition['value'], $condition['operator']);
+    if (isset($condition['delta_group'])) {
+      $delta_group = $condition['delta_group'];
+      if (!isset($delta_groups[$delta_group])) {
+        $delta_groups[$delta_group] = $table_alias;
       }
       else {
-        $value = _field_sql_storage_etid($value);
+        $query->where("$table_alias.delta = " . $delta_groups[$delta_group] . '.delta');
       }
     }
-    // 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);
-  }
-
-  // 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();
-  }
-
-  // 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 ($entity_conditions) {
+    $entity_type = '';
+    foreach ($entity_conditions as $entity_condition) {
+      if (!$entity_type) {
+        $entity_type = $entity_condition['entity_type'];
+        $entity_base_table = _field_sql_storage_query_join_entity($query, $entity_type, $field_base_table);
       }
+      elseif ($entity_type != $entity_condition['entity_type']) {
+        throw new FieldQueryException(t("Can't have conditions on two different entity types"));
+      }
+      $query->condition("$entity_base_table." . $entity_condition['column'], $entity_condition['value'], isset($entity_condition['operator']) ? $entity_condition['operator'] : NULL);
     }
-  } 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_types = $entity_type;
+  }
+  if ($entity_types) {
+    $query->condition('fcet.type', $entity_types);
+  }
+  foreach ($entity_order as $order) {
+    if (!isset($entity_base_table)) {
+      $entity_base_table = _field_sql_storage_query_join_entity($query, $entity_types[0], $field_base_table);
+    }
+    $query->orderBy("$entity_base_table." . $order['column'], $order['direction']);
   }
+  foreach ($field_order as $order) {
+    $columnname = $columnname = _field_sql_storage_columnname($order['field_name'], $order['column']);
+    $field = field_info_field($order['field_name']);
+    $tablename = _field_sql_storage_tablename($field);
+    $query->orderBy("$tablename.$columnname", $order['direction']);
+  }
+  if ($range) {
+    $query->range($range['start'], $options['limit']);
+  }
+  if (isset($deleted)) {
+    $query->condition("$table_alias.deleted", $deleted);
+  }
+  return $query->execute();
+}
 
-  return $return;
+function _field_sql_storage_query_join_entity($query, $entity_type, $field_base_table) {;
+  $entity_info = entity_get_info($entity_type);
+  $entity_base_table = isset($entity_info['base_table']) ? $entity_info['base_table'] : $entity_type;
+  if (empty($entity_info['entity keys']['revision'])) {
+    $entity_field = $entity_info['entity keys']['id'];
+    $field = 'entity_id';
+  }
+  else {
+    $entity_field = $entity_info['entity keys']['revision'];
+    $field = 'revision_id';
+  }
+  $query->join($entity_base_table, $entity_base_table, "$entity_base_table.$entity_field = $field_base_table.$field");
+  return $entity_base_table;
 }
 
+
 /**
  * Implements hook_field_storage_delete_revision().
  *

=== modified file 'modules/field/tests/field.test'
--- modules/field/tests/field.test	2010-04-11 18:33:43 +0000
+++ modules/field/tests/field.test	2010-04-29 06:57:38 +0000
@@ -623,215 +623,6 @@ class FieldAttachStorageTestCase extends
     $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'));
-  }
 }
 
 /**
@@ -3108,3 +2899,282 @@ class FieldBulkDeleteTestCase extends Fi
     $this->assertEqual(count($fields), 0, 'The field is purged.');
   }
 }
+
+class FieldAttachQueryTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Field attach query',
+      'description' => 'Test field_attach_query.',
+      'group' => 'Field API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('field_test'));
+
+    field_attach_create_bundle('bundle1', 'test_entity_1');
+    field_attach_create_bundle('bundle2', 'test_entity_1');
+    field_attach_create_bundle('test_entity_2', 'test_entity_2');
+
+    $instances = array();
+    $this->fields = array();
+    $this->fields[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);
+    $field_id = $field['id'];
+    $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_1';
+    field_create_instance($instances[0]);
+    $instances[0]['bundle'] = $instances[0]['entity_type'] = 'test_entity_2';
+    field_create_instance($instances[0]);
+
+    $this->fields[1] = $field_name = drupal_strtolower($this->randomName() . '_field_name');
+    $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4);
+    $field = field_create_field($field);
+    $field_id = $field['id'];
+    $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_1';
+    field_create_instance($instances[1]);
+    $instances[1]['bundle'] = $instances[1]['entity_type'] = 'test_entity_2';
+    field_create_instance($instances[1]);
+
+    // Write entity base table if there is one.
+    $entities = array();
+
+    // First, of type test_entity_1
+    for ($i = 1; $i < 5; $i++) {
+      $entity = new stdClass;
+      $entity->ftid = $i;
+      $entity->fttype = 'bundle1';
+      $entity->{$this->fields[0]}[LANGUAGE_NONE][0]['value'] = $i;
+      drupal_write_record('test_entity_1', $entity);
+      field_attach_insert('test_entity_1', $entity);
+    }
+
+    $entity = new stdClass;
+    $entity->ftid = 5;
+    $entity->fttype = 'bundle1';
+    $entity->{$this->fields[1]}[LANGUAGE_NONE][0]['shape'] = 'square';
+    $entity->{$this->fields[1]}[LANGUAGE_NONE][0]['color'] = 'red';
+    $entity->{$this->fields[1]}[LANGUAGE_NONE][1]['shape'] = 'circle';
+    $entity->{$this->fields[1]}[LANGUAGE_NONE][1]['color'] = 'blue';
+    drupal_write_record('test_entity_2', $entity);
+    field_attach_insert('test_entity_2', $entity);
+  }
+
+  /**
+   * Test field_attach_query().
+   */
+  function testFieldAttachQuery() {
+    // First, test without options.
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', 3, '=');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 3),
+    ), t('Test the "equal to" operation.'));
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', 2, '>');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 3),
+      array('test_entity_1', 4),
+    ), t('Test the "greater than" operation.'));
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', array(3, 4), 'IN');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 3),
+      array('test_entity_1', 4),
+    ), t('Test the "in" operation.'));
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', array(1, 3), 'BETWEEN');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 1),
+      array('test_entity_1', 2),
+      array('test_entity_1', 3),
+    ), t('Test the "between" operation.'));
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', 3);
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 3),
+    ), t('Test omission of an operator with a single item.'));
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', array(3, 4));
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 3),
+      array('test_entity_1', 4),
+    ), t('Test omission of an operator with multiple items.'));
+
+    $query = new fieldAttachQuery();
+    $query->entityCondition('test_entity_1', 'ftid', 1);
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 1),
+    ), t('Test entity conditions.'));
+
+    $query = new fieldAttachQuery();
+    $query->entityCondition('test_entity_1', 'ftid', 1, '>');
+    $query->fieldCondition($this->fields[0], 'value', 4, '<');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 2),
+      array('test_entity_1', 3),
+    ), t('Test entity and field conditions.'));
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', 0, '>');
+    $query->fieldOrderBy($this->fields[0], 'value', 'asc');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 1),
+      array('test_entity_1', 2),
+      array('test_entity_1', 3),
+      array('test_entity_1', 4),
+    ), t('Field sort: ascending.'), TRUE);
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', 0, '>');
+    $query->fieldOrderBy($this->fields[0], 'value', 'desc');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 4),
+      array('test_entity_1', 3),
+      array('test_entity_1', 2),
+      array('test_entity_1', 1),
+    ), t('Field sort: descending.'), TRUE);
+
+    $query = new fieldAttachQuery();
+    $query->entityTypes(array('test_entity_1'));
+    $query->entityOrderBy('ftid', 'asc');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 1),
+      array('test_entity_1', 2),
+      array('test_entity_1', 3),
+      array('test_entity_1', 4),
+    ), t('Entity sort: ascending.'), TRUE);
+
+    $query = new fieldAttachQuery();
+    $query->entityTypes(array('test_entity_1'));
+    $query->entityOrderBy('ftid', 'desc');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 4),
+      array('test_entity_1', 3),
+      array('test_entity_1', 2),
+      array('test_entity_1', 1),
+    ), t('Entity sort: descending.'), TRUE);
+
+    $query = new fieldAttachQuery();
+    $query->entityTypes(array('test_entity_1'));
+    $query->entityOrderBy('ftid', 'asc');
+    $query->range(0, 2);
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 1),
+      array('test_entity_1', 2),
+    ), t('Test limit.'), TRUE);
+
+    $query = new fieldAttachQuery();
+    $query->entityTypes(array('test_entity_1'));
+    $query->entityOrderBy('ftid', 'asc');
+    $query->range(2, 4);
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 3),
+      array('test_entity_1', 4),
+    ), t('Test offset.'), TRUE);
+
+    for ($i = 5; $i < 10; $i++) {
+      $entity = new stdClass;
+      $entity->ftid = $i;
+      $entity->{$this->fields[0]}[LANGUAGE_NONE][0]['value'] = $i - 5;
+      drupal_write_record('test_entity_2', $entity);
+      field_attach_insert('test_entity_2', $entity);
+    }
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[0], 'value', 2, '>');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_1', 3),
+      array('test_entity_1', 4),
+      array('test_entity_2', 8),
+      array('test_entity_2', 9),
+    ), t('Select a field across multiple entities.'));
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[1], 'shape', 'square');
+    $query->fieldCondition($this->fields[1], 'color', 'blue');
+    $this->_testFieldAttachQuery($query, array(
+      array('test_entity_2', 5),
+    ), t('Test without a delta group.'));
+
+    $query = new fieldAttachQuery();
+    $query->fieldCondition($this->fields[1], 'shape', 'square', '=', 'group');
+    $query->fieldCondition($this->fields[1], 'color', 'blue', '=', 'group');
+    $this->_testFieldAttachQuery($query, array(), t('Test with a delta group.'));
+
+    $pass = FALSE;
+    $query = new fieldAttachQuery();
+    try {
+      $query->execute();
+    }
+    catch (FieldQueryException $exception) {
+      $pass = ($exception->getMessage() == t('Empty query'));
+    }
+    $this->assertTrue($pass, t("Can't query the universe."));
+  }
+
+  function _testFieldAttachQuery($query, $intended_results, $assertion, $sort = FALSE) {
+    $results = array();
+    foreach ($query->execute() as $entity) {
+      if (!is_object($entity)) {
+        $entity = (object)$entity;
+      }
+      // These should be the test entities where that field is 2 or more.
+      $results[] = array($entity->entity_type, $entity->entity_id);
+    }
+    if (!isset($sort) || !$sort) {
+      sort($results);
+      sort($intended_results);
+    }
+    $this->assertEqual($results, $intended_results, $assertion);
+  }
+}

=== modified file 'modules/field/tests/field_test.entity.inc'
--- modules/field/tests/field_test.entity.inc	2010-03-27 18:41:13 +0000
+++ modules/field/tests/field_test.entity.inc	2010-04-25 19:12:42 +0000
@@ -46,6 +46,27 @@ function field_test_entity_info() {
       'bundles' => $bundles,
       'view modes' => $test_entity_modes,
     ),
+    'test_entity_1' => array(
+      'name' => t('Test Entity 1'),
+      '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_2' => array(
+      'name' => t('Test Entity 2'),
+      '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,
+    ),
   );
 }
 

=== modified file 'modules/field/tests/field_test.field.inc'
--- modules/field/tests/field_test.field.inc	2010-03-12 19:51:40 +0000
+++ modules/field/tests/field_test.field.inc	2010-04-25 18:54:44 +0000
@@ -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.'),
+      'settings' => array(),
+      'instance_settings' => array(),
+      'default_widget' => 'test_field_widget',
+      'default_formatter' => 'field_test_default',
+    ),
   );
 }
 
@@ -33,18 +41,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,
+        ),
+      ),
+    );
+  }
 }
 
 /**

=== modified file 'modules/field/tests/field_test.install'
--- modules/field/tests/field_test.install	2009-12-04 16:49:45 +0000
+++ modules/field/tests/field_test.install	2010-04-25 06:05:12 +0000
@@ -41,7 +41,7 @@ function field_test_schema() {
         'description' => 'The type of this test_entity.',
         'type' => 'varchar',
         'length' => 32,
-        'not null' => TRUE,
+        'not null' => FALSE,
         'default' => '',
       ),
     ),
@@ -50,6 +50,37 @@ function field_test_schema() {
     ),
     'primary key' => array('ftid'),
   );
+  $schema['test_entity_1'] = array(
+    'description' => 'The base table for test_entities.',
+    'fields' => array(
+      'ftid' => array(
+        'description' => 'The {test_entity} this version belongs to.',
+        '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_2'] = array(
+    'description' => 'Stores information about each saved version of a {test_entity}.',
+    'fields' => array(
+      'ftid' => array(
+        'description' => 'The {test_entity} this version belongs to.',
+        '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(

=== modified file 'modules/simpletest/drupal_web_test_case.php'
--- modules/simpletest/drupal_web_test_case.php	2010-04-23 05:46:01 +0000
+++ modules/simpletest/drupal_web_test_case.php	2010-04-29 06:42:46 +0000
@@ -1279,7 +1279,7 @@ class DrupalWebTestCase extends DrupalTe
       $schema = drupal_get_schema(NULL, TRUE);
       $ret = array();
       foreach ($schema as $name => $table) {
-        db_drop_table($name);
+#        db_drop_table($name);
       }
 
       // Return the database prefix to the original.

=== modified file 'modules/system/system.queue.inc'
--- modules/system/system.queue.inc	2010-04-26 13:02:40 +0000
+++ modules/system/system.queue.inc	2010-04-29 14:14:53 +0000
@@ -24,7 +24,8 @@
  * DrupalQueueInterface::deleteItem(). If the consumer dies, the item will be
  * made available again by the DrupalQueueInterface implementation once the
  * lease expires. Another consumer will then be able to receive it when calling
- * DrupalQueueInterface::claimItem().
+ * DrupalQueueInterface::claimItem(). Due to this, the processing code should
+ * be aware that an item might be handed over for processing more than once.
  *
  * The $item object used by the DrupalQueueInterface can contain arbitrary
  * metadata depending on the implementation. Systems using the interface should
@@ -33,18 +34,22 @@
  * DrupalQueueInterface::claimItem() needs to be passed to
  * DrupalQueueInterface::deleteItem() once processing is completed.
  *
- * While the queue system makes a best effort to preserve order in messages,
- * due to the pluggable nature of the queue, there is no guarantee that items
- * will be delivered on claim in the order they were sent. For example, some
- * implementations like beanstalkd or others with distributed back-ends like
- * Amazon SQS will be managing jobs for a large set of producers and consumers
- * where a strict FIFO ordering will likely not be preserved.
- *
- * The system also makes no guarantees about a task only being executed once:
- * callers that have non-idempotent tasks either need to live with the
- * possiblity of the task being invoked multiple times in cases where a claim
- * lease expires, or need to implement their own transactions to make their
- * tasks idempotent.
+ * There are two kinds of queue backends available: strict, which preserves the
+ * order of messages and guarantees that every item will be executed at least
+ * once. The non-strict kind only does a best effort to preserve order in
+ * messages and to execute them only once but there is a small chance that some
+ * items get lost. For example, some distributed back-ends like Amazon SQS will
+ * be managing jobs for a large set of producers and consumers where a strict
+ * FIFO ordering will likely not be preserved. Another example would be an
+ * in-memory queue backend which might lose items if it crashes. However, such
+ * a backend would be able to deal with significantly more writes than a strict
+ * queue and for many tasks this is more important. See aggregator_cron() for
+ * an example of how can this not be a problem. Another example is doing
+ * Twitter statistics -- the small possibility of losing a few items is
+ * insignificant next to power of the queue binge able to keep up with writes.
+ * As described in the processing section, regardless of the queue being strict
+ * or not, the processing code should be aware that an item might be handed
+ * over for processing more than once.
  */
 
 /**
@@ -56,17 +61,25 @@ class DrupalQueue {
    *
    * @param $name
    *   Arbitrary string. The name of the queue to work with.
+   * @param $strict
+   *   TRUE if the ordering of items and guaranteeing every item executes at
+   *   least once is important, FALSE if scalability is the main concern.
    * @return
    *   The queue object for a given name.
    */
-  public static function get($name) {
+  public static function get($name, $strict = FALSE) {
     static $queues;
     if (!isset($queues[$name])) {
       $class = variable_get('queue_class_' . $name, NULL);
       if (!$class) {
         $class = variable_get('queue_default_class', 'SystemQueue');
       }
-      $queues[$name] = new $class($name);
+      $object = new $class($name);
+      if ($strict && !$object instanceof DrupalQueueStrictInterface) {
+        $class = variable_get('queue_default_strict_class', 'SystemQueue');
+        $object = new $class($name);
+      }
+      $queues[$name] = $object;
     }
     return $queues[$name];
   }
@@ -167,9 +180,19 @@ interface DrupalQueueInterface {
 }
 
 /**
+ * Strict queue interface.
+ *
+ * Classes impleenting this interface preserve the order of messages and
+ * guarantee that every item will be executed at least once.
+ */
+interface DrupalQueueStrictInterface extends DrupalQueueInterface {
+
+}
+
+/**
  * Default queue implementation.
  */
-class SystemQueue implements DrupalQueueInterface {
+class SystemQueue implements DrupalQueueStrictInterface {
   /**
    * The name of the queue this instance is working with.
    *

