diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index 44c3040aba6582b3ef8cff262d8eb8f1a29a5c97..af5f537f6dda2a7f220ede5074e8dd615ee56ab1 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -729,6 +729,23 @@ function entity_query($entity_type, $conjunction = 'AND') {
   return drupal_container()->get('entity.query')->get($entity_type, $conjunction);
 }
 
+/**
+ * Returns the entity query aggregate object for this entity type.
+ *
+ * @param $entity_type
+ *   The entity type, e.g. node, for which the query object should be
+ *   returned.
+ * @param $conjunction
+ *   AND if all conditions in the query need to apply, OR if any of them is
+ *   enough. Optional, defaults to AND.
+ *
+ * @return \Drupal\Core\Entity\Query\QueryInterface
+ *   The query object that can query the given entity type.
+ */
+function entity_query_aggregate($entity_type, $conjunction = 'AND') {
+  return drupal_container()->get('entity.query')->getAggregate($entity_type, $conjunction);
+}
+
 /**
  * Generic access callback for entity pages.
  *
diff --git a/core/lib/Drupal/Core/Config/Entity/Query/Query.php b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
index baba97356ae29f2a1d5e11625b3b958f5f479491..8577c08bdcf15aab9830f0f7b65bb703b29539eb 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/Query.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
@@ -11,11 +11,12 @@
 use Drupal\Core\Entity\EntityManager;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Query\QueryBase;
+use Drupal\Core\Entity\Query\QueryInterface;
 
 /**
  * Defines the entity query for configuration entities.
  */
-class Query extends QueryBase {
+class Query extends QueryBase implements QueryInterface {
 
   /**
    * Stores the entity manager.
@@ -93,10 +94,11 @@ public function execute() {
     $result = $this->condition->compile($configs);
 
     // Apply sort settings.
-    foreach ($this->sort as $property => $sort) {
+    foreach ($this->sort as $sort) {
       $direction = $sort['direction'] == 'ASC' ? -1 : 1;
-      uasort($result, function($a, $b) use ($property, $direction) {
-        return ($a[$property] <= $b[$property]) ? $direction : -$direction;
+      $field = $sort['field'];
+      uasort($result, function($a, $b) use ($field, $direction) {
+        return ($a[$field] <= $b[$field]) ? $direction : -$direction;
       });
     }
 
diff --git a/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
index fe937139efde1cba29a763ab7e7327be58191617..35bc2fd9d8e8551b07fbfed656a2d9354490c8dc 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
@@ -10,6 +10,8 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\StorageInterface;
 use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\Query\QueryAggregateInterface;
+use Drupal\Core\Entity\Query\QueryException;
 
 /**
  * Provides a factory for creating entity query objects for the config backend.
@@ -34,7 +36,7 @@ public function __construct(StorageInterface $config_storage) {
   }
 
   /**
-   * Instantiate a entity query for a certain entity type.
+   * Instantiates an entity query for a given entity type.
    *
    * @param string $entity_type
    *   The entity type for the query.
@@ -50,4 +52,24 @@ public function get($entity_type, $conjunction, EntityManager $entity_manager) {
     return new Query($entity_type, $conjunction, $entity_manager, $this->configStorage);
   }
 
+  /**
+   * Returns a aggregation query object for a given entity type.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $conjunction
+   *   - AND: all of the conditions on the query need to match.
+   *   - OR: at least one of the conditions on the query need to match.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *  The entity manager.
+   *
+   * @throws \Drupal\Core\Entity\Query\QueryException
+   * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
+   *   The query object that can query the given entity type.
+   */
+   public function getAggregate($entity_type, $conjunction, EntityManager $entity_manager) {
+      throw new QueryException('Aggregation over configuration enitties is not supported');
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionAggregateBase.php b/core/lib/Drupal/Core/Entity/Query/ConditionAggregateBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..5973e60737c88017027589ccb504217fc94b09cc
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Query/ConditionAggregateBase.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Query\ConditionAggregateBase.
+ */
+
+namespace Drupal\Core\Entity\Query;
+
+/**
+ * Defines a common base class for all aggregation entity condition implementations.
+ */
+abstract class ConditionAggregateBase extends ConditionFundamentals implements ConditionAggregateInterface {
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionAggregateInterface::condition().
+   */
+  public function condition($field, $function = NULL, $value = NULL, $operator = NULL, $langcode = NULL) {
+    $this->conditions[] = array(
+      'field' => $field,
+      'function' => $function,
+      'value' => $value,
+      'operator' => $operator,
+      'langcode' => $langcode,
+    );
+
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionAggregateInterface.php b/core/lib/Drupal/Core/Entity/Query/ConditionAggregateInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..882681b03ed70bb68eee24a5a759ca37f9e24485
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Query/ConditionAggregateInterface.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Query\ConditionAggregateInterface.
+ */
+
+namespace Drupal\Core\Entity\Query;
+
+/**
+ * Defines aggregated entity query conditions.
+ */
+interface ConditionAggregateInterface extends \Countable {
+
+  /**
+   * Gets the current conjunction.
+   *
+   * @return string
+   *   Can be AND or OR.
+   */
+  public function getConjunction();
+
+  /**
+   * Adds a condition.
+   *
+   * @param string|ConditionAggregateInterface $field
+   * @param string $function
+   * @param mixed $value
+   * @param string $operator
+   * @param string $langcode
+   *
+   * @return \Drupal\Core\Entity\Query\ConditionAggregateInterface
+   *   The called object.
+   * @see \Drupal\Core\Entity\Query\QueryInterface::condition()
+   */
+  public function condition($field, $function = NULL, $value = NULL, $operator = NULL, $langcode = NULL);
+
+  /**
+   * Queries for the existence of a field.
+   *
+   * @param $field
+   * @param string $langcode
+   * @return ConditionInterface
+   * @see \Drupal\Core\Entity\Query\QueryInterface::exists()
+   */
+  public function exists($field, $function, $langcode = NULL);
+
+  /**
+   * Queries for the nonexistence of a field.
+   *
+   * @param string $field
+   * @return ConditionInterface;
+   * @see \Drupal\Core\Entity\Query\QueryInterface::notexists()
+   */
+  public function notExists($field, $function, $langcode = NULL);
+
+  /**
+   * Gets a complete list of all conditions in this conditional clause.
+   *
+   * This method returns by reference. That allows alter hooks to access the
+   * data structure directly and manipulate it before it gets compiled.
+   *
+   * @return array
+   */
+  public function &conditions();
+
+  /**
+   * Compiles this conditional clause.
+   *
+   * @param $query
+   *   The query object this conditional clause belongs to.
+   */
+  public function compile($query);
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionBase.php b/core/lib/Drupal/Core/Entity/Query/ConditionBase.php
index 2c428a4bddddc64de60787baac3485b0f37927f2..9c2da41c2c4301c4c82a8ef280d88a2c396c2895 100644
--- a/core/lib/Drupal/Core/Entity/Query/ConditionBase.php
+++ b/core/lib/Drupal/Core/Entity/Query/ConditionBase.php
@@ -8,40 +8,9 @@
 namespace Drupal\Core\Entity\Query;
 
 /**
- * Common code for all implementations of the entity query condition interface.
+ * Defines a common base class for all entity condition implementations.
  */
-abstract class ConditionBase implements ConditionInterface {
-
-  /**
-   * Array of conditions.
-   *
-   * @var array
-   */
-  protected $conditions = array();
-
-  /**
-   * Constructs a Condition object.
-   *
-   * @param string $conjunction
-   *   The operator to use to combine conditions: 'AND' or 'OR'.
-   */
-  public function __construct($conjunction = 'AND') {
-    $this->conjunction = $conjunction;
-  }
-
-  /**
-   * Implements \Drupal\Core\Entity\Query\ConditionInterface::getConjunction().
-   */
-  public function getConjunction() {
-    return $this->conjunction;
-  }
-
-  /**
-   * Implements \Countable::count().
-   */
-  public function count() {
-    return count($this->conditions) - 1;
-  }
+abstract class ConditionBase extends ConditionFundamentals implements ConditionInterface {
 
   /**
    * Implements \Drupal\Core\Entity\Query\ConditionInterface::compile().
@@ -56,23 +25,4 @@ public function condition($field, $value = NULL, $operator = NULL, $langcode = N
 
     return $this;
   }
-
-  /**
-   * Implements \Drupal\Core\Entity\Query\ConditionInterface::conditions().
-   */
-  public function &conditions() {
-    return $this->conditions;
-  }
-
-  /**
-   * Makes sure condition groups are cloned as well.
-   */
-  function __clone() {
-    foreach ($this->conditions as $key => $condition) {
-      if ($condition['field'] instanceOf ConditionInterface) {
-        $this->conditions[$key]['field'] = clone($condition['field']);
-      }
-    }
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionFundamentals.php b/core/lib/Drupal/Core/Entity/Query/ConditionFundamentals.php
new file mode 100644
index 0000000000000000000000000000000000000000..828ff62fd0cf9055f224f0b8b21f6fd598d06891
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Query/ConditionFundamentals.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Query\ConditionFundamentals.
+ */
+
+namespace Drupal\Core\Entity\Query;
+
+/**
+ * Common code for all implementations of the entity query condition interfaces.
+ */
+abstract class ConditionFundamentals {
+
+  /**
+   * Array of conditions.
+   *
+   * @var array
+   */
+  protected $conditions = array();
+
+  /**
+   * The conjunction of this condition group. The value is one of the following:
+   *
+   * - AND (default)
+   * - OR
+   *
+   * @var string
+   */
+  protected $conjunction;
+
+  /**
+   * Constructs a Condition object.
+   *
+   * @param string $conjunction
+   *   The operator to use to combine conditions: 'AND' or 'OR'.
+   */
+  public function __construct($conjunction = 'AND') {
+    $this->conjunction = $conjunction;
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionInterface::getConjunction().
+   */
+  public function getConjunction() {
+    return $this->conjunction;
+  }
+
+  /**
+   * Implements \Countable::count().
+   */
+  public function count() {
+    return count($this->conditions) - 1;
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionInterface::conditions().
+   */
+  public function &conditions() {
+    return $this->conditions;
+  }
+
+  /**
+   * Implements the magic __clone function.
+   *
+   * Makes sure condition groups are cloned as well.
+   */
+  public function __clone() {
+    foreach ($this->conditions as $key => $condition) {
+      if ($condition['field'] instanceOf ConditionInterface) {
+        $this->conditions[$key]['field'] = clone($condition['field']);
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php b/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php
index d55ccedce136b9abd6ff3b96798a28706d6e7f66..5c8bb6a9c29d054e9021b6105d8fe6ef1802bf25 100644
--- a/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php
+++ b/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php
@@ -32,7 +32,7 @@ public function count();
   /**
    * Adds a condition.
    *
-   * @param string $field
+   * @param string|\Drupal\Core\Entity\Query\ConditionInterface $field
    * @param mixed $value
    * @param string $operator
    * @param string $langcode
diff --git a/core/lib/Drupal/Core/Entity/Query/QueryAggregateInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryAggregateInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..486c246ba4e5bc4838b7ab5d206da96662e3554f
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Query/QueryAggregateInterface.php
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Query\QueryAggregateInterface.
+ */
+
+namespace Drupal\Core\Entity\Query;
+
+/**
+ * Defines a interface for aggregated entity queries.
+ */
+interface QueryAggregateInterface extends QueryInterface {
+
+  /**
+   * Specifies a field and a function to aggregate on.
+   *
+   * Available functions: SUM, AVG, MIN, MAX and COUNT.
+   *
+   * @todo What about GROUP_CONCAT support?
+   *
+   * @param string $field
+   *   The name of the field to aggregate by.
+   * @param string $function
+   *   The aggregation function, for example COUNT or MIN.
+   * @param string $langcode
+   *   (optional) The language code.
+   * @param string $alias
+   *   (optional) The key that will be used on the resultset.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
+   *   The called object.
+   */
+  public function aggregate($field, $function, $langcode = NULL, &$alias = NULL);
+
+  /**
+   * Specifies the field to group on.
+   *
+   * @param string $field
+   *   The name of the field to group by.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
+   *   The called object.
+   */
+  public function groupBy($field);
+
+  /**
+   * Sets a condition for an aggregated value.
+   *
+   * @param string $field
+   *   The name of the field to aggregate by.
+   * @param string $function
+   *   The aggregation function, for example COUNT or MIN.
+   * @param mixed $value
+   *   The actual value of the field.
+   *
+   * @param $operator
+   *   Possible values:
+   *   - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS',
+   *     'ENDS_WITH': 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 string $langcode
+   *   (optional) The language code.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
+   *   The called object.
+   *
+   * @see \Drupal\Core\Entity\Query\QueryInterface::condition().
+   */
+  public function conditionAggregate($field, $function = NULL, $value = NULL, $operator = '=', $langcode = NULL);
+
+  /**
+   * Queries for the existence of a field.
+   *
+   * @param string $field
+   *   The name of the field.
+   * @param string $function
+   *   The aggregate function.
+   * @param $langcode
+   *   (optional) The language code.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
+   *   The called object.
+   */
+  public function existsAggregate($field, $function, $langcode = NULL);
+
+  /**
+   * Queries for the nonexistence of a field.
+   *
+   * @param string $field.
+   *   The name of a field.
+   * @param string $function
+   *   The aggregate function.
+   * @param string $langcode
+   *   (optional) The language code.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
+   *   The called object.
+   */
+  public function notExistsAggregate($field, $function, $langcode = NULL);
+
+  /**
+   * Creates an object holding a group of conditions.
+   *
+   * See andConditionAggregateGroup() and orConditionAggregateGroup() for more.
+   *
+   * @param string $conjunction
+   *   - AND (default): this is the equivalent of andConditionAggregateGroup().
+   *   - OR: this is the equivalent of andConditionAggregateGroup().
+   *
+   * @return ConditionInterface
+   *   An object holding a group of conditions.
+   */
+  public function conditionAggregateGroupFactory($conjunction = 'AND');
+
+  /**
+   * Sorts by an aggregated value.
+   *
+   * @param string $field
+   *   The name of a field.
+   * @param string $function
+   *   The aggregate function. This is only marked optional for interface
+   *   compatibility, it is illegal to leave it out.
+   * @param string $direction
+   *   The order of sorting, either DESC for descending of ASC for ascending.
+   * @param string $langcode
+   *   (optional) The language code.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
+   *   The called object.
+   */
+  public function sortAggregate($field, $function, $direction = 'ASC', $langcode = NULL);
+
+  /**
+   * Executes the aggregate query.
+   *
+   * @return array
+   *   A list of result row arrays. Each result row contains the aggregate
+   *   results as keys and also the groupBy columns as keys:
+   * @code
+   * $result = $query
+   *   ->aggregate('nid', 'count')
+   *   ->condition('status', 1)
+   *   ->groupby('type')
+   *   ->executeAggregate();
+   * @endcode
+   * Will return:
+   * @code
+   * $result[0] = array('count_nid' => 3, 'type' => 'page');
+   * $result[1] = array('count_nid' => 1, 'type' => 'poll');
+   * $result[2] = array('count_nid' => 4, 'type' => 'story');
+   * @endcode
+   */
+  // public function execute();
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Query/QueryBase.php b/core/lib/Drupal/Core/Entity/Query/QueryBase.php
index 77dc07de5a0c6b6a1555bd2187e4d72b4f78900d..c3c69327ec873a7fd3b0c2561d4f87f85ffff28f 100644
--- a/core/lib/Drupal/Core/Entity/Query/QueryBase.php
+++ b/core/lib/Drupal/Core/Entity/Query/QueryBase.php
@@ -12,7 +12,7 @@
 /**
  * The base entity query class.
  */
-abstract class QueryBase implements QueryInterface {
+abstract class QueryBase {
 
   /**
    * The entity type this query runs against.
@@ -22,7 +22,7 @@ abstract class QueryBase implements QueryInterface {
   protected $entityType;
 
   /**
-   * The sort data.
+   * The list of sorts.
    *
    * @var array
    */
@@ -38,10 +38,38 @@ abstract class QueryBase implements QueryInterface {
   /**
    * Conditions.
    *
-   * @var ConditionInterface
+   * @var \Drupal\Core\Entity\Query\ConditionInterface
    */
   protected $condition;
 
+  /**
+   * The list of aggregate expressions.
+   *
+   * @var array
+   */
+  protected $aggregate = array();
+
+  /**
+   * The list of columns to group on.
+   *
+   * @var array
+   */
+  protected $groupBy = array();
+
+  /**
+   * Aggregate Conditions
+   *
+   * @var \Drupal\Core\Entity\Query\ConditionAggregateInterface
+   */
+  protected $conditionAggregate;
+
+  /**
+   * The list of sorts over the aggregate results.
+   *
+   * @var array
+   */
+  protected $sortAggregate = array();
+
   /**
    * The query range.
    *
@@ -49,6 +77,20 @@ abstract class QueryBase implements QueryInterface {
    */
   protected $range = array();
 
+  /**
+   * The query metadata for alter purposes.
+   *
+   * @var array
+   */
+  protected $alterMetaData;
+
+  /**
+   * The query tags.
+   *
+   * @var array
+   */
+  protected $alterTags;
+
   /**
    * Whether access check is requested or not. Defaults to TRUE.
    *
@@ -81,6 +123,9 @@ public function __construct($entity_type, $conjunction) {
     $this->entityType = $entity_type;
     $this->conjunction = $conjunction;
     $this->condition = $this->conditionGroupFactory($conjunction);
+    if ($this instanceof QueryAggregateInterface) {
+      $this->conditionAggregate = $this->conditionAggregateGroupFactory($conjunction);
+    }
   }
 
   /**
@@ -142,8 +187,9 @@ public function orConditionGroup() {
   /**
    * Implements \Drupal\Core\Entity\Query\QueryInterface::sort().
    */
-  public function sort($property, $direction = 'ASC', $langcode = NULL) {
-    $this->sort[$property] = array(
+  public function sort($field, $direction = 'ASC', $langcode = NULL) {
+    $this->sort[] = array(
+      'field' => $field,
       'direction' => $direction,
       'langcode' => $langcode,
     );
@@ -283,4 +329,77 @@ public function addMetaData($key, $object) {
   public function getMetaData($key) {
     return isset($this->alterMetaData[$key]) ? $this->alterMetaData[$key] : NULL;
   }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\QueryAggregateInterface::aggregate()
+   */
+  public function aggregate($field, $function, $langcode = NULL, &$alias = NULL) {
+    if (!isset($alias)) {
+      $alias = $this->getAggregationAlias($field, $function);
+    }
+
+    $this->aggregate[$alias] = array(
+      'field' => $field,
+      'function' => $function,
+      'alias' => $alias,
+      'langcode' => $langcode,
+    );
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\QueryAggregateInterface::conditionAggregate().
+   */
+  public function conditionAggregate($field, $function = NULL, $value = NULL, $operator = '=', $langcode = NULL) {
+    $this->aggregate($field, $function, $langcode);
+    $this->conditionAggregate->condition($field, $function, $value, $operator, $langcode);
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\QueryAggregateInterface::sortAggregate().
+   */
+  public function sortAggregate($field, $function, $direction = 'ASC', $langcode = NULL) {
+    $alias = $this->getAggregationAlias($field, $function);
+
+    $this->sortAggregate[$alias] = array(
+      'field' => $field,
+      'function' => $function,
+      'direction' => $direction,
+      'langcode' => $langcode,
+    );
+    $this->aggregate($field, $function, $langcode, $alias);
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\QueryAggregateInterface::execute().
+   */
+  public function groupBy($field, $langcode = NULL) {
+    $this->groupBy[] = array(
+      'field' => $field,
+      'langcode' => $langcode,
+    );
+
+    return $this;
+  }
+
+  /**
+   * Generates an alias for a field and it's aggregated function.
+   *
+   * @param string $field
+   *   The field name used in the alias.
+   * @param string $function
+   *   The aggregation function used in the alias.
+   *
+   * @return string
+   *   The alias for the field.
+   */
+  protected function getAggregationAlias($field, $function) {
+    return strtolower($field . '_'. $function);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Query/QueryFactory.php b/core/lib/Drupal/Core/Entity/Query/QueryFactory.php
index da739fd17e0b713761dec6ad4e85e7d1ceac649f..fad531ca90a9b2fd1339fd1f3db9ebc8557a5904 100644
--- a/core/lib/Drupal/Core/Entity/Query/QueryFactory.php
+++ b/core/lib/Drupal/Core/Entity/Query/QueryFactory.php
@@ -49,4 +49,21 @@ public function get($entity_type, $conjunction = 'AND') {
     return $this->container->get($service_name)->get($entity_type, $conjunction, $this->entityManager);
   }
 
+  /**
+   * Returns an aggregated query object for a given entity type.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $conjunction
+   *   - AND: all of the conditions on the query need to match.
+   *   - OR: at least one of the conditions on the query need to match.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
+   *   The aggregated query object that can query the given entity type.
+   */
+  public function getAggregate($entity_type, $conjunction = 'AND') {
+    $service_name = $this->entityManager->getStorageController($entity_type)->getQueryServicename();
+    return $this->container->get($service_name)->getAggregate($entity_type, $conjunction, $this->entityManager);
+  }
+
 }
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Condition.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Condition.php
index a0d6d6a63299c974fda50d9bbd09300975ad9789..30ac52012447562739aaff5b52c80d2f38cb72db 100644
--- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Condition.php
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Condition.php
@@ -56,6 +56,11 @@ public function notExists($field, $langcode = NULL) {
     return $this->condition($field, NULL, 'IS NULL', $langcode);
   }
 
+  /**
+   * Translates the string operators to SQL equivalents.
+   *
+   * @param array $condition
+   */
   protected function translateCondition(&$condition) {
     switch ($condition['operator']) {
       case 'STARTS_WITH':
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/ConditionAggregate.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/ConditionAggregate.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb5290e008c7dc396956a0aa81aaaf664d9dfa3d
--- /dev/null
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/ConditionAggregate.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field_sql_storage\Query\ConditionAggregate.
+ */
+
+namespace Drupal\field_sql_storage\Entity;
+
+use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\Entity\Query\ConditionAggregateBase;
+use Drupal\Core\Entity\Query\ConditionAggregateInterface;
+
+/**
+ * Defines the aggregate condition for sql based storage.
+ */
+class ConditionAggregate extends ConditionAggregateBase {
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionInterface::compile().
+   */
+  public function compile($conditionContainer) {
+    // If this is not the top level condition group then the sql query is
+    // added to the $conditionContainer object by this function itself. The
+    // SQL query object is only necessary to pass to Query::addField() so it
+    // can join tables as necessary. On the other hand, conditions need to be
+    // added to the $conditionContainer object to keep grouping.
+    $sql_query = ($conditionContainer instanceof SelectInterface) ? $conditionContainer : $conditionContainer->sqlQuery;
+    $tables = new Tables($sql_query);
+    foreach ($this->conditions as $condition) {
+      if ($condition['field'] instanceOf ConditionAggregateInterface) {
+        $sql_condition = new Condition($condition['field']->getConjunction());
+        // Add the SQL query to the object before calling this method again.
+        $sql_condition->sqlQuery = $sql_query;
+        $condition['field']->compile($sql_condition);
+        $sql_query->condition($sql_condition);
+      }
+      else {
+        $type = ((strtoupper($this->conjunction) == 'OR') || ($condition['operator'] == 'IS NULL')) ? 'LEFT' : 'INNER';
+        $this->translateCondition($condition);
+        $field = $tables->addField($condition['field'], $type, $condition['langcode']);
+        $function = $condition['function'];
+        $placeholder = ':db_placeholder_' . $conditionContainer->nextPlaceholder();
+        $conditionContainer->having("$function($field) {$condition['operator']} $placeholder", array($placeholder => $condition['value']));
+      }
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionInterface::exists().
+   */
+  public function exists($field, $function, $langcode = NULL) {
+    return $this->condition($field, $function, NULL, 'IS NOT NULL', $langcode);
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionInterface::notExists().
+   */
+  public function notExists($field, $function, $langcode = NULL) {
+    return $this->condition($field, $function, NULL, 'IS NULL', $langcode);
+  }
+
+  /**
+   * Translates the string operators to SQL equivalents.
+   *
+   * @param array $condition
+   *   An associative array containing the following keys:
+   *     - value: The value to filter by
+   *     - operator: The operator to use for comparison, for example "=".
+   */
+  protected function translateCondition(&$condition) {
+    switch ($condition['operator']) {
+      case 'STARTS_WITH':
+        $condition['value'] .= '%';
+        $condition['operator'] = 'LIKE';
+        break;
+      case 'CONTAINS':
+        $condition['value'] = '%' . $condition['value'] . '%';
+        $condition['operator'] = 'LIKE';
+        break;
+      case 'ENDS_WITH':
+        $condition['value'] = '%' . $condition['value'];
+        $condition['operator'] = 'LIKE';
+        break;
+    }
+  }
+
+}
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
index 41091a6113b6f14ed399252843c06be1cb68366d..9d8ab5dcf2bf912797c91ecdbc99d7bb4481b5ce 100644
--- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
@@ -7,13 +7,52 @@
 
 namespace Drupal\field_sql_storage\Entity;
 
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityManager;
 use Drupal\Core\Entity\Query\QueryBase;
 use Drupal\Core\Entity\Query\QueryException;
+use Drupal\Core\Entity\Query\QueryInterface;
 
 /**
  * The SQL storage entity query class.
  */
-class Query extends QueryBase {
+class Query extends QueryBase implements QueryInterface {
+
+  /**
+   * Contains the entity info for the entity type of that query.
+   *
+   * @var array
+   *
+   * @see \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityInfo;
+
+  /**
+   * The build sql select query.
+   *
+   * @var \Drupal\Core\Database\Query\SelectInterface
+   */
+  protected $sqlQuery;
+
+  /**
+   * An array of fields keyed by the field alias.
+   *
+   * Each entry correlates to the arguments of
+   * \Drupal\Core\Database\Query\SelectInterface::addField(), so the first one
+   * is the table alias, the second one the field and the last one optional the
+   * field alias.
+   *
+   * @var array
+   */
+  protected $sqlFields = array();
+
+  /**
+   * An array of strings added as to the group by, keyed by the string to avoid
+   * duplicates.
+   *
+   * @var array
+   */
+  protected $sqlGroupBy = array();
 
   /**
    * @var \Drupal\Core\Database\Connection
@@ -21,19 +60,32 @@ class Query extends QueryBase {
   protected $connection;
 
   /**
+   * Stores the entity manager used by the query.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a query object.
+   *
    * @param string $entity_type
    *   The entity type.
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   The entity manager storing the entity info.
    * @param string $conjunction
    *   - AND: all of the conditions on the query need to match.
    *   - OR: at least one of the conditions on the query need to match.
    * @param \Drupal\Core\Database\Connection $connection
    *   The database connection to run the query against.
    */
-  public function __construct($entity_type, $conjunction, $connection) {
-    parent::__construct($entity_type, $conjunction);
+  public function __construct($entity_type, EntityManager $entity_manager, $conjunction, Connection $connection) {
+    parent::__construct($entity_type, $conjunction, $connection);
+    $this->entityManager = $entity_manager;
     $this->connection = $connection;
   }
 
+
   /**
    * Implements Drupal\Core\Entity\Query\QueryInterface::conditionGroupFactory().
    */
@@ -42,97 +94,137 @@ public function conditionGroupFactory($conjunction = 'AND') {
   }
 
   /**
-   * Implements Drupal\Core\Entity\Query\QueryInterface::execute().
+   * Implements \Drupal\Core\Entity\Query\QueryInterface::execute().
    */
   public function execute() {
+    return $this
+      ->prepare()
+      ->compile()
+      ->addSort()
+      ->finish()
+      ->result();
+  }
+
+  /**
+   * Prepares the basic query with proper metadata/tags and base fields.
+   *
+   * @throws \Drupal\Core\Entity\Query\QueryException
+   *   Thrown if the base table does not exists.
+   *
+   * @return \Drupal\field_sql_storage\Entity\Query
+   *   Returns the called object.
+   */
+  protected function prepare() {
     $entity_type = $this->entityType;
-    // @todo change to a method call once http://drupal.org/node/1892462 is in.
-    $entity_info = entity_get_info($entity_type);
-    if (!isset($entity_info['base_table'])) {
+    $this->entityInfo = $this->entityManager->getDefinition($entity_type);
+    if (!isset($this->entityInfo['base_table'])) {
       throw new QueryException("No base table, invalid query.");
     }
-    $base_table = $entity_info['base_table'];
+    $base_table = $this->entityInfo['base_table'];
     $simple_query = TRUE;
-    if (isset($entity_info['data_table'])) {
+    if (isset($this->entityInfo['data_table'])) {
       $simple_query = FALSE;
     }
-    $sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction));
-    $sqlQuery->addMetaData('entity_type', $entity_type);
-    $id_field = $entity_info['entity_keys']['id'];
-    $fields[$id_field] = TRUE;
-    if (empty($entity_info['entity_keys']['revision'])) {
-      // Add the key field for fetchAllKeyed(). When there is no revision
-      // support, this is the entity key.
-      $sqlQuery->addField('base_table', $entity_info['entity_keys']['id']);
+    $this->sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction));
+    $this->sqlQuery->addMetaData('entity_type', $entity_type);
+    $id_field = $this->entityInfo['entity_keys']['id'];
+    // Add the key field for fetchAllKeyed().
+    if (empty($this->entityInfo['entity_keys']['revision'])) {
+      // When there is no revision support, the key field is the entity key.
+      $this->sqlFields["base_table.$id_field"] = array('base_table', $id_field);
+      // Now add the value column for fetchAllKeyed(). This is always the
+      // entity id.
+      $this->sqlFields["base_table.$id_field" . '_1'] = array('base_table', $id_field);
     }
     else {
-      // Add the key field for fetchAllKeyed(). When there is revision
-      // support, this is the revision key.
-      $revision_field = $entity_info['entity_keys']['revision'];
-      $fields[$revision_field] = TRUE;
-      $sqlQuery->addField('base_table', $revision_field);
-    }
-    // Now add the value column for fetchAllKeyed(). This is always the
-    // entity id.
-    $sqlQuery->addField('base_table', $id_field);
+      // When there is revision support, the key field is the revision key.
+      $revision_field = $this->entityInfo['entity_keys']['revision'];
+      $this->sqlFields["base_table.$revision_field"] = array('base_table', $revision_field);
+      // Now add the value column for fetchAllKeyed(). This is always the
+      // entity id.
+      $this->sqlFields["base_table.$id_field"] = array('base_table', $id_field);
+    }
     if ($this->accessCheck) {
-      $sqlQuery->addTag($entity_type . '_access');
+      $this->sqlQuery->addTag($entity_type . '_access');
     }
-    $sqlQuery->addTag('entity_query');
-    $sqlQuery->addTag('entity_query_' . $this->entityType);
+    $this->sqlQuery->addTag('entity_query');
+    $this->sqlQuery->addTag('entity_query_' . $this->entityType);
 
     // Add further tags added.
     if (isset($this->alterTags)) {
       foreach ($this->alterTags as $tag => $value) {
-        $sqlQuery->addTag($tag);
+        $this->sqlQuery->addTag($tag);
       }
     }
 
     // Add further metadata added.
     if (isset($this->alterMetaData)) {
       foreach ($this->alterMetaData as $key => $value) {
-        $sqlQuery->addMetaData($key, $value);
+        $this->sqlQuery->addMetaData($key, $value);
       }
     }
     // This now contains first the table containing entity properties and
     // last the entity base table. They might be the same.
-    $sqlQuery->addMetaData('age', $this->age);
-    $sqlQuery->addMetaData('simple_query', $simple_query);
-    $this->condition->compile($sqlQuery);
+    $this->sqlQuery->addMetaData('age', $this->age);
+    $this->sqlQuery->addMetaData('simple_query', $simple_query);
+    return $this;
+  }
+
+  /**
+   * Compiles the conditions.
+   *
+   * @return \Drupal\field_sql_storage\Entity\Query
+   *   Returns the called object.
+   */
+  protected function compile() {
+    $this->condition->compile($this->sqlQuery);
+    return $this;
+  }
+
+  /**
+   * Adds the sort to the build query.
+   *
+   * @return \Drupal\field_sql_storage\Entity\Query
+   *   Returns the called object.
+   */
+  protected function addSort() {
     if ($this->count) {
-      $this->sort = FALSE;
+      $this->sort = array();
     }
     // Gather the SQL field aliases first to make sure every field table
     // necessary is added. This might change whether the query is simple or
     // not. See below for more on simple queries.
     $sort = array();
     if ($this->sort) {
-      $tables = new Tables($sqlQuery);
-      foreach ($this->sort as $property => $data) {
-        $sort[$property] = isset($fields[$property]) ? $property : $tables->addField($property, 'LEFT', $data['langcode']);
+      foreach ($this->sort as $key => $data) {
+        $sort[$key] = $this->getSqlField($data['field'], $data['langcode']);
       }
     }
+    $simple_query = $this->isSimpleQuery();
     // If the query is set up for paging either via pager or by range or a
     // count is requested, then the correct amount of rows returned is
     // important. If the entity has a data table or multiple value fields are
     // involved then each revision might appear in several rows and this needs
     // a significantly more complex query.
-    $simple_query = (!$this->pager && !$this->range && !$this->count) || $sqlQuery->getMetaData('simple_query');
     if (!$simple_query) {
       // First, GROUP BY revision id (if it has been added) and entity id.
       // Now each group contains a single revision of an entity.
-      foreach (array_keys($fields) as $field) {
-        $sqlQuery->groupBy($field);
+      foreach ($this->sqlFields as $field) {
+        $group_by = "$field[0].$field[1]";
+        $this->sqlGroupBy[$group_by] = $group_by;
       }
     }
     // Now we know whether this is a simple query or not, actually do the
     // sorting.
-    foreach ($sort as $property => $sql_alias) {
-      $direction = $this->sort[$property]['direction'];
-      if ($simple_query || isset($fields[$property])) {
+    foreach ($sort as $key => $sql_alias) {
+      $direction = $this->sort[$key]['direction'];
+      if ($simple_query || isset($this->sqlGroupBy[$sql_alias])) {
         // Simple queries, and the grouped columns of complicated queries
         // can be ordered normally, without the aggregation function.
-        $sqlQuery->orderBy($sql_alias, $direction);
+        $this->sqlQuery->orderBy($sql_alias, $direction);
+        if (!isset($this->sqlFields[$sql_alias])) {
+          $this->sqlFields[$sql_alias] = explode('.', $sql_alias);
+        }
       }
       else {
         // Order based on the smallest element of each group if the
@@ -140,21 +232,80 @@ public function execute() {
         // if the direction is descending.
         $function = $direction == 'ASC' ? 'min' : 'max';
         $expression = "$function($sql_alias)";
-        $sqlQuery->addExpression($expression, "order_by_{$property}_$direction");
-        $sqlQuery->orderBy($expression, $direction);
+        $this->sqlQuery->addExpression($expression);
+        $this->sqlQuery->orderBy($expression, $direction);
       }
     }
+    return $this;
+  }
+
+  /**
+   * Finish the query by adding fields, GROUP BY and range.
+   *
+   * @return \Drupal\field_sql_storage\Entity\Query
+   *   Returns the called object.
+   */
+  protected function finish() {
     $this->initializePager();
     if ($this->range) {
-      $sqlQuery->range($this->range['start'], $this->range['length']);
+      $this->sqlQuery->range($this->range['start'], $this->range['length']);
+    }
+   foreach ($this->sqlGroupBy as $field) {
+      $this->sqlQuery->groupBy($field);
     }
+    foreach ($this->sqlFields as $field) {
+      $this->sqlQuery->addField($field[0], $field[1], isset($field[2]) ? $field[2] : NULL);
+    }
+    return $this;
+  }
+
+  /**
+   * Executes the query and returns the result.
+   *
+   * @return int|array
+   *   Returns the query result as entity IDs.
+   */
+  protected function result() {
     if ($this->count) {
-      return $sqlQuery->countQuery()->execute()->fetchField();
+      return $this->sqlQuery->countQuery()->execute()->fetchField();
     }
     // Return a keyed array of results. The key is either the revision_id or
     // the entity_id depending on whether the entity type supports revisions.
     // The value is always the entity id.
-    return $sqlQuery->execute()->fetchAllKeyed();
+    return $this->sqlQuery->execute()->fetchAllKeyed();
+  }
+
+  protected function getSqlField($field, $langcode) {
+    if (!isset($this->tables)) {
+      $this->tables = new Tables($this->sqlQuery);
+    }
+    $base_property = "base_table.$field";
+    if (isset($this->sqlFields[$base_property])) {
+      return $base_property;
+    }
+    else {
+      return $this->tables->addField($field, 'LEFT', $langcode);
+    }
+  }
+
+  /**
+   * Returns whether the query requires GROUP BY and ORDER BY MIN/MAX.
+   *
+   * @return bool
+   */
+  protected function isSimpleQuery() {
+    return (!$this->pager && !$this->range && !$this->count) || $this->sqlQuery->getMetaData('simple_query');
+  }
+
+  /**
+   * Implements the magic __clone method.
+   *
+   * Reset fields and GROUP BY when cloning.
+   */
+  public function __clone() {
+    parent::__clone();
+    $this->sqlFields = array();
+    $this->sqlGroupBy = array();
   }
 
 }
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryAggregate.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryAggregate.php
new file mode 100644
index 0000000000000000000000000000000000000000..c8bbb8129b2cd2badd5d580aabe92cb9c9f0007f
--- /dev/null
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryAggregate.php
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field_sql_storage\Entity\QueryAggregate.
+ */
+
+namespace Drupal\field_sql_storage\Entity;
+
+use Drupal\Core\Entity\Query\QueryAggregateInterface;
+
+/**
+ * The SQL storage entity query aggregate class.
+ */
+class QueryAggregate extends Query implements QueryAggregateInterface {
+
+  /**
+   * Stores the sql expressions used to build the sql query.
+   *
+   * @var array
+   *   An array of expressions.
+   */
+  protected $sqlExpressions = array();
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\QueryAggregateInterface::execute().
+   */
+  public function execute() {
+    return $this
+      ->prepare()
+      ->addAggregate()
+      ->compile()
+      ->compileAggregate()
+      ->addGroupBy()
+      ->addSort()
+      ->addSortAggregate()
+      ->finish()
+      ->result();
+  }
+
+  /**
+   * Overrides \Drupal\field_sql_storage\Entity::prepare().
+   */
+  public function prepare() {
+    parent::prepare();
+    // Throw away the id fields.
+    $this->sqlFields = array();
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\QueryAggregateInterface::conditionAggregateGroupFactory().
+   */
+  public function conditionAggregateGroupFactory($conjunction = 'AND') {
+    return new ConditionAggregate($conjunction);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\QueryBase::exists().
+   */
+  public function existsAggregate($field, $function, $langcode = NULL) {
+    return $this->conditionAggregate->exists($field, $function, $langcode);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\QueryBase::notExists().
+   */
+  public function notExistsAggregate($field, $function, $langcode = NULL) {
+    return $this->conditionAggregate->notExists($field, $function, $langcode);
+  }
+
+
+  /**
+   * Adds the aggregations to the query.
+   *
+   * @return \Drupal\field_sql_storage\Entity\QueryAggregate
+   *   Returns the called object.
+   */
+  protected function addAggregate() {
+    if ($this->aggregate) {
+      foreach ($this->aggregate as $aggregate) {
+        $sql_field = $this->getSqlField($aggregate['field'], $aggregate['langcode']);
+        $this->sqlExpressions[$aggregate['alias']] = $aggregate['function'] . "($sql_field)";
+      }
+    }
+    return $this;
+  }
+
+  /**
+   * Builds the aggregation conditions part of the query.
+   *
+   * @return \Drupal\field_sql_storage\Entity\QueryAggregate
+   *   Returns the called object.
+   */
+  protected function compileAggregate() {
+    $this->conditionAggregate->compile($this->sqlQuery);
+    return $this;
+  }
+
+  /**
+   * Adds the groupby values to the actual query.
+   *
+   * @return \Drupal\field_sql_storage\Entity\QueryAggregate
+   *   Returns the called object.
+   */
+  protected function addGroupBy() {
+    foreach ($this->groupBy as $group_by) {
+      $field = $group_by['field'];
+      $sql_field = $this->getSqlField($field, $group_by['langcode']);
+      $this->sqlGroupBy[$sql_field] = $sql_field;
+      list($table, $real_sql_field) = explode('.', $sql_field);
+      $this->sqlFields[$sql_field] = array($table, $real_sql_field, $this->createSqlAlias($field, $real_sql_field));
+    }
+
+    return $this;
+  }
+
+  /**
+   * Builds the aggregation sort part of the query.
+   *
+   * @return \Drupal\field_sql_storage\Entity\QueryAggregate
+   *   Returns the called object.
+   */
+  protected function addSortAggregate() {
+    if(!$this->count) {
+      foreach ($this->sortAggregate as $alias => $sort) {
+        $this->sqlQuery->orderBy($this->sqlExpressions[$alias], $sort['direction']);
+      }
+    }
+    return $this;
+  }
+
+
+  /**
+   * Overrides \Drupal\field_sql_storage\Entity\Query::finish().
+   *
+   * Adds the sql expressions to the query.
+   */
+  protected function finish() {
+    foreach ($this->sqlExpressions as $alias => $expression) {
+      $this->sqlQuery->addExpression($expression, $alias);
+    }
+    return parent::finish();
+  }
+
+  /**
+   * Builds a sql alias as expected in the result.
+   *
+   * @param string $field
+   *   The field as passed in by the caller.
+   * @param string $sql_field
+   *   The sql field as returned by getSqlField.
+   * @return string
+   *   The SQL alias expected in the return value. The dots in $sql_field are
+   *   replaced with underscores and if a default fallback to .value happened,
+   *   the _value is stripped.
+   */
+  function createSqlAlias($field, $sql_field) {
+    $alias = str_replace('.', '_', $sql_field);
+    // If the alias contains of field_*_value remove the _value at the end.
+    if (substr($alias, 0, 6) === 'field_' && substr($field, -6) !== '_value' && substr($alias, -6) === '_value') {
+      $alias = substr($alias, 0, -6);
+    }
+    return $alias;
+  }
+
+  /**
+   * Overrides \Drupal\field_sql_storage\Entity\Query::result().
+   *
+   * @return array|int
+   *   Returns the aggregated result, or a number if it's a count query.
+   */
+  protected function result() {
+    if ($this->count) {
+      return parent::result();
+    }
+    $return = array();
+    foreach ($this->sqlQuery->execute() as $row) {
+      $return[] = (array)$row;
+    }
+    return $return;
+  }
+
+}
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryFactory.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryFactory.php
index d8b36ba99fdc4a9a011d65dfc57385142df99e3a..a48608d8c168b9f6bde9ce13f881d167b2bddb03 100644
--- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryFactory.php
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryFactory.php
@@ -2,23 +2,62 @@
 
 /**
  * @file
- * Definition of Drupal\field_sql_storage\Entity\QueryFactory.
+ * Contains \Drupal\field_sql_storage\Entity\QueryFactory.
  */
 
 namespace Drupal\field_sql_storage\Entity;
 
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityManager;
 
 /**
  * Factory class creating entity query objects for the SQL backend.
+ *
+ * @see \Drupal\field_sql_storage\Entity\Query
+ * @see \Drupal\field_sql_storage\Entity\QueryAggregate
  */
 class QueryFactory {
 
+  /**
+   * Constructs a QueryFactory object.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection used by the entity query.
+   */
   function __construct(Connection $connection) {
     $this->connection = $connection;
   }
 
-  function get($entity_type, $conjunction = 'AND') {
-    return new Query($entity_type, $conjunction, $this->connection);
+  /**
+   * Constructs a entity query for a certain entity type.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $conjunction
+   *   - AND: all of the conditions on the query need to match.
+   *   - OR: at least one of the conditions on the query need to match.
+   *
+   * @return \Drupal\field_sql_storage\Entity\Query
+   *   The factored query.
+   */
+  function get($entity_type, $conjunction, EntityManager $entity_manager) {
+    return new Query($entity_type, $entity_manager, $conjunction, $this->connection);
   }
+
+  /**
+   * Constructs a entity aggregation query for a certain entity type.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $conjunction
+   *   - AND: all of the conditions on the query need to match.
+   *   - OR: at least one of the conditions on the query need to match.
+   *
+   * @return \Drupal\field_sql_storage\Entity\QueryAggregate
+   *   The factored aggregation query.
+   */
+  function getAggregate($entity_type, $conjunction, EntityManager $entity_manager) {
+    return new QueryAggregate($entity_type, $entity_manager, $conjunction, $this->connection);
+  }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryAggregateTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryAggregateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b957675085d647df8b5db8fe56e78f67df85e0a2
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryAggregateTest.php
@@ -0,0 +1,599 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\EntityQueryAggregateTest.
+ */
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\field_sql_storage\Entity\ConditionAggregate;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Defines a test for testing aggregation support for entity query.
+ *
+ * @todo Use EntityUnitBaseTest provided by http://drupal.org/node/1893108.
+ *
+ * @see \Drupal\entity_test\Plugin\Core\Entity\EntityTest
+ */
+class EntityQueryAggregateTest extends DrupalUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('user', 'system', 'field', 'number', 'text', 'field_sql_storage', 'entity_test');
+
+  /**
+   * The entity_test storage controller to create the test entities.
+   *
+   * @var \Drupal\entity_test\EntityTestStorageController
+   */
+  protected $entityStorageController;
+
+  /**
+   * The actual query result, to compare later.
+   *
+   * @var array
+   */
+  protected $queryResult;
+
+  /**
+   * The query factory to create entity queries.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  public $factory;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity Query aggregation',
+      'description' => 'Tests the Entity Query Aggregation API',
+      'group' => 'Entity API',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+    $this->installSchema('user', 'users');
+    $this->installSchema('system', 'sequences');
+    $this->installSchema('field', 'field_config');
+    $this->installSchema('field', 'field_config_instance');
+    $this->installSchema('entity_test', 'entity_test');
+
+    $this->entityStorageController = $this->container->get('plugin.manager.entity')->getStorageController('entity_test');
+    $this->factory = $this->container->get('entity.query');
+
+    // Add some fieldapi fields to be used in the test.
+    for ($i = 1; $i <= 2; $i++) {
+      $field = array(
+        'field_name' => 'field_test_' . $i,
+        'type' => 'number_integer',
+        'cardinality' => 2,
+      );
+      field_create_field($field);
+      $instance = array(
+        'field_name' => $field['field_name'],
+        'entity_type' => 'entity_test',
+        'bundle' => 'entity_test',
+      );
+      field_create_instance($instance);
+    }
+
+    $entity = $this->entityStorageController->create(array(
+      'id' => 1,
+      'user_id' => 1,
+      'field_test_1' => 1,
+      'field_test_2' => 2,
+    ));
+    $entity->enforceIsNew();
+    $entity->save();
+
+    $entity = $this->entityStorageController->create(array(
+      'id' => 2,
+      'user_id' => 2,
+      'field_test_1' => 1,
+      'field_test_2' => 7,
+    ));
+    $entity->enforceIsNew();
+    $entity->save();
+    $entity = $this->entityStorageController->create(array(
+      'id' => 3,
+      'user_id' => 2,
+      'field_test_1' => 2,
+      'field_test_2' => 1,
+    ));
+    $entity->enforceIsNew();
+    $entity->save();
+    $entity = $this->entityStorageController->create(array(
+      'id' => 4,
+      'user_id' => 2,
+      'field_test_1' => 2,
+      'field_test_2' => 8,
+    ));
+    $entity->enforceIsNew();
+    $entity->save();
+    $entity = $this->entityStorageController->create(array(
+      'id' => 5,
+      'user_id' => 3,
+      'field_test_1' => 2,
+      'field_test_2' => 2,
+    ));
+    $entity->enforceIsNew();
+    $entity->save();
+    $entity = $this->entityStorageController->create(array(
+      'id' => 6,
+      'user_id' => 3,
+      'field_test_1' => 3,
+      'field_test_2' => 8,
+    ));
+    $entity->enforceIsNew();
+    $entity->save();
+
+  }
+
+  /**
+   * Test aggregation support.
+   */
+  public function testAggregation() {
+    // Apply a simple groupby.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('user_id')
+      ->execute();
+
+    $this->assertResults(array(
+      array('user_id' => 1),
+      array('user_id' => 2),
+      array('user_id' => 3),
+    ));
+
+    $function_expected = array();
+    $function_expected['count'] = array(array('id_count' => 6));
+    $function_expected['min'] = array(array('id_min' => 1));
+    $function_expected['max'] = array(array('id_max' => 6));
+    $function_expected['sum'] = array(array('id_sum' => 21));
+    $function_expected['avg'] = array(array('id_avg' => (21.0/6.0)));
+
+    // Apply a simple aggregation for different aggregation functions.
+    foreach ($function_expected as $aggregation_function => $expected) {
+      $this->queryResult = $this->factory->getAggregate('entity_test')
+        ->aggregate('id', $aggregation_function)
+        ->execute();
+      $this->assertEqual($this->queryResult, $expected);
+    }
+
+    // Apply aggregation and groupby on the same query.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'COUNT')
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertResults(array(
+      array('user_id' => 1, 'id_count' => 1),
+      array('user_id' => 2, 'id_count' => 3),
+      array('user_id' => 3, 'id_count' => 2),
+    ));
+
+    // Apply aggregation and a condition which matches.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'COUNT')
+      ->conditionAggregate('id', 'COUNT', 8)
+      ->execute();
+    $this->assertResults(array());
+
+    // Don't call aggregate to test the implicit aggregate call.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->conditionAggregate('id', 'COUNT', 8)
+      ->execute();
+    $this->assertResults(array());
+
+    // Apply aggregation and a condition which matches.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'count')
+      ->conditionAggregate('id', 'COUNT', 6)
+      ->execute();
+    $this->assertResults(array(array('id_count' => 6)));
+
+    // Apply aggregation, a groupby and a condition which matches partially via
+    // the operator '='.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'count')
+      ->conditionAggregate('id', 'count', 2)
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertResults(array(array('id_count' => 2, 'user_id' => 3)));
+
+    // Apply aggregation, a groupby and a condition which matches partially via
+    // the operator '>'.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'count')
+      ->conditionAggregate('id', 'COUNT', 1, '>')
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertResults(array(
+      array('id_count' => 2, 'user_id' => 3),
+      array('id_count' => 3, 'user_id' => 2),
+    ));
+
+    // Apply aggregation and a sort. This might not be useful, but have a proper
+    // test coverage.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'COUNT')
+      ->sortAggregate('id', 'COUNT')
+      ->execute();
+    $this->assertSortedResults(array(array('id_count' => 6)));
+
+    // Don't call aggregate to test the implicit aggregate call.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->sortAggregate('id', 'COUNT')
+      ->execute();
+    $this->assertSortedResults(array(array('id_count' => 6)));
+
+    // Apply aggregation, groupby and a sort descending.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'COUNT')
+      ->groupBy('user_id')
+      ->sortAggregate('id', 'COUNT', 'DESC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('user_id' => 2, 'id_count' => 3),
+      array('user_id' => 3, 'id_count' => 2),
+      array('user_id' => 1, 'id_count' => 1),
+    ));
+
+    // Apply aggregation, groupby and a sort ascending.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'COUNT')
+      ->groupBy('user_id')
+      ->sortAggregate('id', 'COUNT', 'ASC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('user_id' => 1, 'id_count' => 1),
+      array('user_id' => 3, 'id_count' => 2),
+      array('user_id' => 2, 'id_count' => 3),
+    ));
+
+    // Apply aggregation, groupby, an aggregation condition and a sort with the
+    // operator '='.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'COUNT')
+      ->groupBy('user_id')
+      ->sortAggregate('id', 'COUNT')
+      ->conditionAggregate('id', 'COUNT', 2)
+      ->execute();
+    $this->assertSortedResults(array(array('id_count' => 2, 'user_id' => 3)));
+
+    // Apply aggregation, groupby, an aggregation condition and a sort with the
+    // operator '<' and order ASC.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'COUNT')
+      ->groupBy('user_id')
+      ->sortAggregate('id', 'COUNT', 'ASC')
+      ->conditionAggregate('id', 'COUNT', 3, '<')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('id_count' => 1, 'user_id' => 1),
+      array('id_count' => 2, 'user_id' => 3),
+    ));
+
+    // Apply aggregation, groupby, an aggregation condition and a sort with the
+    // operator '<' and order DESC.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('id', 'COUNT')
+      ->groupBy('user_id')
+      ->sortAggregate('id', 'COUNT', 'DESC')
+      ->conditionAggregate('id', 'COUNT', 3, '<')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('id_count' => 2, 'user_id' => 3),
+      array('id_count' => 1, 'user_id' => 1),
+    ));
+
+    // Test aggregation/groupby support for fieldapi fields.
+
+    // Just group by a fieldapi field.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->execute();
+    $this->assertResults(array(
+      array('field_test_1' => 1),
+      array('field_test_1' => 2),
+      array('field_test_1' => 3),
+    ));
+
+    // Group by a fieldapi field and aggregate a normal property.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('user_id', 'COUNT')
+      ->groupBy('field_test_1')
+      ->execute();
+
+    $this->assertResults(array(
+      array('field_test_1' => 1, 'user_id_count' => 2),
+      array('field_test_1' => 2, 'user_id_count' => 3),
+      array('field_test_1' => 3, 'user_id_count' => 1),
+    ));
+
+    // Group by a normal property and aggregate a fieldapi field.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('field_test_1', 'COUNT')
+      ->groupBy('user_id')
+      ->execute();
+
+    $this->assertResults(array(
+      array('user_id' => 1, 'field_test_1_count' => 1),
+      array('user_id' => 2, 'field_test_1_count' => 3),
+      array('user_id' => 3, 'field_test_1_count' => 2),
+    ));
+
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('field_test_1', 'SUM')
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertResults(array(
+      array('user_id' => 1, 'field_test_1_sum' => 1),
+      array('user_id' => 2, 'field_test_1_sum' => 5),
+      array('user_id' => 3, 'field_test_1_sum' => 5),
+    ));
+
+    // Aggregate by two different fieldapi fields.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('field_test_1', 'SUM')
+      ->aggregate('field_test_2', 'SUM')
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertResults(array(
+      array('user_id' => 1, 'field_test_1_sum' => 1, 'field_test_2_sum' => 2),
+      array('user_id' => 2, 'field_test_1_sum' => 5, 'field_test_2_sum' => 16),
+      array('user_id' => 3, 'field_test_1_sum' => 5, 'field_test_2_sum' => 10),
+    ));
+
+    // This time aggregate the same field twice.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('field_test_1', 'SUM')
+      ->aggregate('field_test_1', 'COUNT')
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertResults(array(
+      array('user_id' => 1, 'field_test_1_sum' => 1, 'field_test_1_count' => 1),
+      array('user_id' => 2, 'field_test_1_sum' => 5, 'field_test_1_count' => 3),
+      array('user_id' => 3, 'field_test_1_sum' => 5, 'field_test_1_count' => 2),
+    ));
+
+    // Group by and aggregate by a fieldapi field.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->aggregate('field_test_2', 'COUNT')
+      ->execute();
+    $this->assertResults(array(
+      array('field_test_1' => 1, 'field_test_2_count' => 2),
+      array('field_test_1' => 2, 'field_test_2_count' => 3),
+      array('field_test_1' => 3, 'field_test_2_count' => 1),
+    ));
+
+    // Group by and aggregate by a fieldapi field and use multiple aggregate
+    // functions.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->aggregate('field_test_2', 'COUNT')
+      ->aggregate('field_test_2', 'SUM')
+      ->execute();
+    $this->assertResults(array(
+      array('field_test_1' => 1, 'field_test_2_count' => 2, 'field_test_2_sum' => 9),
+      array('field_test_1' => 2, 'field_test_2_count' => 3, 'field_test_2_sum' => 11),
+      array('field_test_1' => 3, 'field_test_2_count' => 1, 'field_test_2_sum' => 8),
+    ));
+
+    // Apply an aggregate condition for a fieldapi field and group by a simple
+    // property.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->conditionAggregate('field_test_1', 'COUNT', 3)
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertResults(array(
+      array('user_id' => 2, 'field_test_1_count' => 3),
+      array('user_id' => 3, 'field_test_1_count' => 2),
+    ));
+
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('field_test_1', 'SUM')
+      ->conditionAggregate('field_test_1', 'COUNT', 2, '>')
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertResults(array(
+      array('user_id' => 2, 'field_test_1_sum' => 5, 'field_test_1_count' => 3),
+      array('user_id' => 3, 'field_test_1_sum' => 5, 'field_test_1_count' => 2),
+    ));
+
+    // Apply an aggregate condition for a simple property and a group by a
+    // fieldapi field.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->conditionAggregate('user_id', 'COUNT', 2)
+      ->groupBy('field_test_1')
+      ->execute();
+    $this->assertResults(array(
+      array('field_test_1' => 1, 'user_id_count' => 2),
+    ));
+
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->conditionAggregate('user_id', 'COUNT', 2, '>')
+      ->groupBy('field_test_1')
+      ->execute();
+    $this->assertResults(array(
+      array('field_test_1' => 1, 'user_id_count' => 2),
+      array('field_test_1' => 2, 'user_id_count' => 3),
+    ));
+
+    // Apply an aggregate condition and a group by fieldapi fields.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->conditionAggregate('field_test_2', 'COUNT', 2)
+      ->execute();
+    $this->assertResults(array(
+      array('field_test_1' => 1, 'field_test_2_count' => 2),
+    ));
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->conditionAggregate('field_test_2', 'COUNT', 2, '>')
+      ->execute();
+    $this->assertResults(array(
+      array('field_test_1' => 1, 'field_test_2_count' => 2),
+      array('field_test_1' => 2, 'field_test_2_count' => 3),
+    ));
+
+    // Apply an aggregate condition and a group by fieldapi fields with multiple
+    // conditions via AND.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->conditionAggregate('field_test_2', 'COUNT', 2)
+      ->conditionAggregate('field_test_2', 'SUM', 8)
+      ->execute();
+    $this->assertResults(array());
+
+    // Apply an aggregate condition and a group by fieldapi fields with multiple
+    // conditions via OR.
+    $this->queryResult = $this->factory->getAggregate('entity_test', 'OR')
+      ->groupBy('field_test_1')
+      ->conditionAggregate('field_test_2', 'COUNT', 2)
+      ->conditionAggregate('field_test_2', 'SUM', 8)
+      ->execute();
+    $this->assertResults(array(
+      array('field_test_1' => 1, 'field_test_2_count' => 2, 'field_test_2_sum' => 9),
+      array('field_test_1' => 3, 'field_test_2_count' => 1, 'field_test_2_sum' => 8),
+    ));
+
+    // Group by a normal property and aggregate a fieldapi field and sort by the
+    // groupby field.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('field_test_1', 'COUNT')
+      ->groupBy('user_id')
+      ->sort('user_id', 'DESC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('user_id' => 3, 'field_test_1_count' => 2),
+      array('user_id' => 2, 'field_test_1_count' => 3),
+      array('user_id' => 1, 'field_test_1_count' => 1),
+    ));
+
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->aggregate('field_test_1', 'COUNT')
+      ->groupBy('user_id')
+      ->sort('user_id', 'ASC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('user_id' => 1, 'field_test_1_count' => 1),
+      array('user_id' => 2, 'field_test_1_count' => 3),
+      array('user_id' => 3, 'field_test_1_count' => 2),
+    ));
+
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->conditionAggregate('field_test_1', 'COUNT', 2, '>')
+      ->groupBy('user_id')
+      ->sort('user_id', 'ASC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('user_id' => 2, 'field_test_1_count' => 3),
+      array('user_id' => 3, 'field_test_1_count' => 2),
+    ));
+
+    // Group by a normal property, aggregate a fieldapi field, and sort by the
+    // aggregated field.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->sortAggregate('field_test_1', 'COUNT', 'DESC')
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('user_id' => 2, 'field_test_1_count' => 3),
+      array('user_id' => 3, 'field_test_1_count' => 2),
+      array('user_id' => 1, 'field_test_1_count' => 1),
+    ));
+
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->sortAggregate('field_test_1', 'COUNT', 'ASC')
+      ->groupBy('user_id')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('user_id' => 1, 'field_test_1_count' => 1),
+      array('user_id' => 3, 'field_test_1_count' => 2),
+      array('user_id' => 2, 'field_test_1_count' => 3),
+    ));
+
+    // Group by and aggregate by fieldapi field, and sort by the groupby field.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->aggregate('field_test_2', 'COUNT')
+      ->sort('field_test_1', 'ASC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('field_test_1' => 1, 'field_test_2_count' => 2),
+      array('field_test_1' => 2, 'field_test_2_count' => 3),
+      array('field_test_1' => 3, 'field_test_2_count' => 1),
+    ));
+
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->aggregate('field_test_2', 'COUNT')
+      ->sort('field_test_1', 'DESC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('field_test_1' => 3, 'field_test_2_count' => 1),
+      array('field_test_1' => 2, 'field_test_2_count' => 3),
+      array('field_test_1' => 1, 'field_test_2_count' => 2),
+    ));
+
+    // Groupy and aggregate by fieldapi field, and sort by the aggregated field.
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->sortAggregate('field_test_2', 'COUNT', 'DESC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('field_test_1' => 2, 'field_test_2_count' => 3),
+      array('field_test_1' => 1, 'field_test_2_count' => 2),
+      array('field_test_1' => 3, 'field_test_2_count' => 1),
+    ));
+
+    $this->queryResult = $this->factory->getAggregate('entity_test')
+      ->groupBy('field_test_1')
+      ->sortAggregate('field_test_2', 'COUNT', 'ASC')
+      ->execute();
+    $this->assertSortedResults(array(
+      array('field_test_1' => 3, 'field_test_2_count' => 1),
+      array('field_test_1' => 1, 'field_test_2_count' => 2),
+      array('field_test_1' => 2, 'field_test_2_count' => 3),
+    ));
+
+  }
+
+  /**
+   * Asserts the results as expected regardless of order between and in rows.
+   *
+   * @param array $expected
+   *   An array of the expected results.
+   */
+  protected function assertResults($expected, $sorted = FALSE) {
+    $found = TRUE;
+    $expected_keys = array_keys($expected);
+    foreach ($this->queryResult as $key => $row) {
+      $keys = $sorted ? array($key) : $expected_keys;
+      foreach ($keys as $key) {
+        $expected_row = $expected[$key];
+        if (!array_diff_assoc($row, $expected_row) && !array_diff_assoc($expected_row, $row)) {
+          continue 2;
+        }
+      }
+      $found = FALSE;
+      break;
+    }
+    return $this->assertTrue($found, strtr('!expected expected, !found found', array('!expected' => print_r($expected, TRUE), '!found' => print_r($this->queryResult, TRUE))));
+  }
+
+  /**
+   * Asserts the results as expected regardless of order in rows.
+   *
+   * @param array $expected
+   *   An array of the expected results.
+   */
+  protected function assertSortedResults($expected) {
+    return $this->assertResults($expected, TRUE);
+  }
+}
+