Index: includes/pager.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/pager.inc,v
retrieving revision 1.64
diff -u -p -r1.64 pager.inc
--- includes/pager.inc	12 Oct 2008 04:30:05 -0000	1.64
+++ includes/pager.inc	7 Feb 2009 21:39:42 -0000
@@ -6,6 +6,150 @@
  * Functions to aid in presenting database results as a set of pages.
  */
 
+
+/**
+ * Query extender for pager queries.
+ *
+ * This is the "default" pager mechanism.  It creates a paged query with a fixed
+ * number of entries per page.
+ */
+class PagerDefault extends SelectQueryExtender {
+
+  /**
+   * The highest element we've autogenerated so far.
+   *
+   * @var int
+   */
+  static protected $maxElement = 0;
+
+  /**
+   * The number of elements per page to allow.
+   *
+   * @var int
+   */
+  protected $limit = 10;
+
+  /**
+   * The unique ID of this pager on this page.
+   *
+   * @var int
+   */
+  protected $element = NULL;
+
+  /**
+   * The count query that will be used for this pager.
+   *
+   * @var SelectQueryInterface
+   */
+  protected $customCountQuery = FALSE;
+
+  /**
+   * Override the execute method.
+   *
+   * Before we run the query, we need to add pager-based range() instructions
+   * to it.
+   */
+  public function execute() {
+    global $pager_page_array, $pager_total, $pager_total_items;
+
+    // A NULL limit is the "kill switch" for pager queries.
+    if (empty($this->limit)) {
+      return;
+    }
+    $this->ensureElement();
+
+    $page = isset($_GET['page']) ? $_GET['page'] : '';
+
+    // Convert comma-separated $page to an array, used by other functions.
+    $pager_page_array = explode(',', $page);
+
+    // We calculate the total of pages as ceil(items / limit).
+    $pager_total_items[$this->element] = $this->getCountQuery()->execute()->fetchField();
+    $pager_total[$this->element] = ceil($pager_total_items[$this->element] / $this->limit);
+    $pager_page_array[$this->element] = max(0, min((int)$pager_page_array[$this->element], ((int)$pager_total[$this->element]) - 1));
+    $this->range($pager_page_array[$this->element] * $this->limit, $this->limit);
+
+    // Now that we've added our pager-based range instructions, run the query normally.
+    return $this->query->execute();
+  }
+
+  /**
+   * Ensure that there is an element associated with this query.
+   *
+   * After running this query, access $this->element to get the element for this
+   * query.
+   */
+  protected function ensureElement() {
+    if (!empty($this->element)) {
+      return;
+    }
+
+    $this->element = self::$maxElement++;
+  }
+
+  /**
+   * Specify the count query object to use for this pager.
+   *
+   * You will rarely need to specify a count query directly.  If not specified,
+   * one is generated off of the pager query itself.
+   *
+   * @param SelectQueryInterface $query
+   *   The count query object.  It must return a single row with a single column,
+   *   which is the total number of records.
+   */
+  public function setCountQuery(SelectQueryInterface $query) {
+    $this->customCountQuery = $query;
+  }
+
+  /**
+   * Retrieve the count query for this pager.
+   *
+   * The count query may be specified manually or, by default, taken from the
+   * query we are extending.
+   *
+   * @return
+   *   A count SelectQueryInterface object.
+   */
+  protected function getCountQuery() {
+    if ($this->customCountQuery) {
+      return $this->customCountQuery;
+    }
+    else {
+      return $this->query->countQuery();
+    }
+  }
+
+  /**
+   * Specify the maximum number of elements per page for this query.
+   *
+   * The default if not specified is 10 items per page.
+   *
+   * @param $limit
+   *   An integer specifying the number of elements per page.  If passed a false
+   *   value (FALSE, 0, NULL), the pager is disabled.
+   */
+  public function limit($limit = 10) {
+    $this->limit = $limit;
+    return $this;
+  }
+
+  /**
+   * Specify the element ID for this pager query.
+   *
+   * The element is used to differentiate different pager queries on the same
+   * page so that they may be operated independently.  If you do not specify an
+   * element, every pager query on the page will get a unique element.  If for
+   * whatever reason you want to explicitly define an element for a given query,
+   * you may do so here.
+   *
+   * @param $element
+   */
+  public function element($element) {
+    $this->element = $element;
+    return $this;
+  }
+}
+
 /**
  * Perform a paged database query.
  *
Index: includes/tablesort.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/tablesort.inc,v
retrieving revision 1.49
diff -u -p -r1.49 tablesort.inc
--- includes/tablesort.inc	28 Jan 2009 07:43:26 -0000	1.49
+++ includes/tablesort.inc	7 Feb 2009 21:39:42 -0000
@@ -9,6 +9,120 @@
  * column headers that the user can click on to sort the table by that column.
  */
 
+
+/**
+ * Query extender class for tablesort queries.
+ */
+class TableSort extends SelectQueryExtender {
+
+  /**
+   * The array of fields that can be sorted by.
+   *
+   * @var array
+   */
+  protected $header = array();
+
+  public function execute() {
+    $ts = $this->init();
+    if ($ts['sql']) {
+      // Based on code from db_escape_table(), but this can also contain a dot.
+      $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']);
+
+      // Sort order can only be ASC or DESC.
+      $sort = drupal_strtoupper($ts['sort']);
+      $sort = in_array($sort, array('ASC', 'DESC')) ? $sort : '';
+      $this->orderBy($field, $sort);
+    }
+    return $this->query->execute();
+  }
+
+  public function setHeader(Array $header) {
+    $this->header = $header;
+    return $this;
+  }
+
+  /**
+   * Initialize the table sort context.
+   */
+  protected function init() {
+    $ts = $this->order();
+    $ts['sort'] = $this->getSort();
+    $ts['query_string'] = $this->getQueryString();
+    return $ts;
+  }
+
+  /**
+   * Determine the current sort direction.
+   *
+   * @param $headers
+   *   An array of column headers in the format described in theme_table().
+   * @return
+   *   The current sort direction ("asc" or "desc").
+   */
+  protected function getSort() {
+    if (isset($_GET['sort'])) {
+      return ($_GET['sort'] == 'desc') ? 'desc' : 'asc';
+    }
+    // User has not specified a sort. Use default if specified; otherwise use "asc".
+    else {
+      foreach ($this->header as $header) {
+        if (is_array($header) && array_key_exists('sort', $header)) {
+          return $header['sort'];
+        }
+      }
+    }
+    return 'asc';
+  }
+
+  /**
+   * Compose a query string to append to table sorting requests.
+   *
+   * @return
+   *   A query string that consists of all components of the current page request
+   *   except for those pertaining to table sorting.
+   */
+  protected function getQueryString() {
+    return drupal_query_string_encode($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE)));
+  }
+
+  /**
+   * Determine the current sort criterion.
+   *
+   * @param $headers
+   *   An array of column headers in the format described in theme_table().
+   * @return
+   *   An associative array describing the criterion, containing the keys:
+   *   - "name": The localized title of the table column.
+   *   - "sql": The name of the database field to sort on.
+   */
+  protected function order() {
+    $order = isset($_GET['order']) ? $_GET['order'] : '';
+    foreach ($this->header as $header) {
+      if (isset($header['data']) && $order == $header['data']) {
+        return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
+      }
+
+      if (isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
+        $default = array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
+      }
+    }
+
+    if (isset($default)) {
+      return $default;
+    }
+    else {
+      // The first column specified is initial 'order by' field unless otherwise specified
+      if (is_array($this->header[0])) {
+        $this->header[0] += array('data' => NULL, 'field' => NULL);
+        return array('name' => $this->header[0]['data'], 'sql' => $this->header[0]['field']);
+      }
+      else {
+        return array('name' => $this->header[0]);
+      }
+    }
+  }
+}
+
 /**
  * Initialize the table sort context.
  */
Index: includes/database/query.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database/query.inc,v
retrieving revision 1.14
diff -u -p -r1.14 query.inc
--- includes/database/query.inc	18 Jan 2009 23:55:32 -0000	1.14
+++ includes/database/query.inc	7 Feb 2009 21:39:42 -0000
@@ -108,6 +108,8 @@ interface QueryAlterableInterface {
    *
    * @param $tag
    *   The tag to add.
+   * @return
+   *   The called object.
    */
   public function addTag($tag);
 
@@ -154,7 +156,8 @@ interface QueryAlterableInterface {
    *   follows the same rules as any other PHP identifier.
    * @param $object
    *   The additional data to add to the query. May be any valid PHP variable.
-   *
+   * @return
+   *   The called object.
    */
   public function addMetaData($key, $object);
 
@@ -201,7 +204,12 @@ abstract class Query {
   abstract protected function execute();
 
   /**
-   * Returns the query as a prepared statement string.
+   * __toString() magic method.
+   *
+   * The toString operation is how we compile a query object to a prepared statement.
+   *
+   * @return
+   *   A prepared statement query string for this object.
    */
   abstract public function __toString();
 }
Index: includes/database/select.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database/select.inc,v
retrieving revision 1.13
diff -u -p -r1.13 select.inc
--- includes/database/select.inc	4 Jan 2009 19:56:50 -0000	1.13
+++ includes/database/select.inc	7 Feb 2009 21:39:42 -0000
@@ -7,9 +7,618 @@
  */
 
 /**
+ * Interface for extendable query objects.
+ *
+ * "Extenders" follow the "Decorator" OOP design pattern.  That is, they wrap
+ * and "decorate" another object.  In our case, they implement the same interface
+ * as select queries and wrap a select query, to which they delegate almost all
+ * operations.  Subclasses of this class may implement additional methods or
+ * override existing methods as appropriate.  Extenders may also wrap other
+ * extender objects, allowing for arbitrarily complex "enhanced" queries.
+ */
+interface QueryExtendableInterface {
+
+  /**
+   * Enhance this object by wrapping it in an extender object.
+   *
+   * @param $extender_name
+   *   The base name of the extending class.  The base name will be checked
+   *   against the current database connection to allow driver-specific subclasses
+   *   as well, using the same logic as the query objects themselves.  For example,
+   *   PagerDefault_mysql is the MySQL-specific override for PagerDefault.
+   * @return
+   *   The extender object, which now contains a reference to this object.
+   */
+  public function extend($extender_name);
+}
+
+/**
+ * Interface definition for a Select Query object.
+ */
+interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableInterface, QueryExtendableInterface {
+
+  /* Alter accessors to expose the query data to alter hooks. */
+
+  /**
+   * Returns a reference to the fields array for this query.
+   *
+   * Because this method returns by reference, alter hooks may edit the fields
+   * array directly to make their changes. If just adding fields, however, the
+   * use of addField() is preferred.
+   *
+   * Note that this method must be called by reference as well:
+   *
+   * @code
+   * $fields =& $query->getFields();
+   * @endcode
+   *
+   * @return
+   *   A reference to the fields array structure.
+   */
+  public function &getFields();
+
+  /**
+   * Returns a reference to the expressions array for this query.
+   *
+   * Because this method returns by reference, alter hooks may edit the expressions
+   * array directly to make their changes. If just adding expressions, however, the
+   * use of addExpression() is preferred.
+   *
+   * Note that this method must be called by reference as well:
+   *
+   * @code
+   * $fields =& $query->getExpressions();
+   * @endcode
+   *
+   * @return
+   *   A reference to the expression array structure.
+   */
+  public function &getExpressions();
+
+  /**
+   * Returns a reference to the order by array for this query.
+   *
+   * Because this method returns by reference, alter hooks may edit the order-by
+   * array directly to make their changes. If just adding additional ordering
+   * fields, however, the use of orderBy() is preferred.
+   *
+   * Note that this method must be called by reference as well:
+   *
+   * @code
+   * $fields =& $query->getOrderBy();
+   * @endcode
+   *
+   * @return
+   *   A reference to the expression array structure.
+   */
+  public function &getOrderBy();
+
+  /**
+   * Returns a reference to the tables array for this query.
+   *
+   * Because this method returns by reference, alter hooks may edit the tables
+   * array directly to make their changes. If just adding tables, however, the
+   * use of the join() methods is preferred.
+   *
+   * Note that this method must be called by reference as well:
+   *
+   * @code
+   * $fields =& $query->getTables();
+   * @endcode
+   *
+   * @return
+   *   A reference to the tables array structure.
+   */
+  public function &getTables();
+
+  /**
+   * Compiles and returns an associative array of the arguments for this prepared statement.
+   *
+   * @return
+   *   An associative array of all placeholder arguments for this query.
+   */
+  public function getArguments();
+
+  /* Query building operations */
+
+  /**
+   * Sets this query to be DISTINCT.
+   *
+   * @param $distinct
+   *   TRUE to flag this query DISTINCT, FALSE to disable it.
+   * @return
+   *   The called object.
+   */
+  public function distinct($distinct = TRUE);
+
+  /**
+   * Adds a field to the list to be SELECTed.
+   *
+   * @param $table_alias
+   *   The name of the table from which the field comes, as an alias. Generally
+   *   you will want to use the return value of join() here to ensure that it is
+   *   valid.
+   * @param $field
+   *   The name of the field.
+   * @param $alias
+   *   The alias for this field. If not specified, one will be generated
+   *   automatically based on the $table_alias and $field. The alias will be
+   *   checked for uniqueness, so the requested alias may not be the alias
+   *   that is assigned in all cases.
+   * @return
+   *   The unique alias that was assigned for this field.
+   */
+  public function addField($table_alias, $field, $alias = NULL);
+
+  /**
+   * Add multiple fields from the same table to be SELECTed.
+   *
+   * This method does not return the aliases set for the passed fields. In the
+   * majority of cases that is not a problem, as the alias will be the field
+   * name. However, if you do need to know the alias you can call getFields()
+   * and examine the result to determine what alias was created. Alternatively,
+   * simply use addField() for the few fields you care about and this method for
+   * the rest.
+   *
+   * @param $table_alias
+   *   The name of the table from which the field comes, as an alias. Generally
+   *   you will want to use the return value of join() here to ensure that it is
+   *   valid.
+   * @param $fields
+   *   An indexed array of fields present in the specified table that should be
+   *   included in this query. If not specified, $table_alias.* will be generated
+   *   without any aliases.
+   * @return
+   *   The called object.
+   */
+  public function fields($table_alias, array $fields = array());
+
+  /**
+   * Adds an expression to the list of "fields" to be SELECTed.
+   *
+   * An expression can be any arbitrary string that is valid SQL. That includes
+   * various functions, which may in some cases be database-dependent. This
+   * method makes no effort to correct for database-specific functions.
+   *
+   * @param $expression
+   *   The expression string. May contain placeholders.
+   * @param $alias
+   *   The alias for this expression. If not specified, one will be generated
+   *   automatically in the form "expression_#". The alias will be checked for
+   *   uniqueness, so the requested alias may not be the alias that is assigned
+   *   in all cases.
+   * @param $arguments
+   *   Any placeholder arguments needed for this expression.
+   * @return
+   *   The unique alias that was assigned for this expression.
+   */
+  public function addExpression($expression, $alias = NULL, $arguments = array());
+
+  /**
+   * Default Join against another table in the database.
+   *
+   * This method is a convenience method for innerJoin().
+   *
+   * @param $table
+   *   The table against which to join.
+   * @param $alias
+   *   The alias for the table. In most cases this should be the first letter
+   *   of the table, or the first letter of each "word" in the table.
+   * @param $condition
+   *   The condition on which to join this table. If the join requires values,
+   *   this clause should use a named placeholder and the value or values to
+   *   insert should be passed in the 4th parameter. For the first table joined
+   *   on a query, this value is ignored as the first table is taken as the base
+   *   table.
+   * @param $arguments
+   *   An array of arguments to replace into the $condition of this join.
+   * @return
+   *   The unique alias that was assigned for this table.
+   */
+  public function join($table, $alias = NULL, $condition = NULL, $arguments = array());
+
+  /**
+   * Inner Join against another table in the database.
+   *
+   * @param $table
+   *   The table against which to join.
+   * @param $alias
+   *   The alias for the table. In most cases this should be the first letter
+   *   of the table, or the first letter of each "word" in the table.
+   * @param $condition
+   *   The condition on which to join this table. If the join requires values,
+   *   this clause should use a named placeholder and the value or values to
+   *   insert should be passed in the 4th parameter. For the first table joined
+   *   on a query, this value is ignored as the first table is taken as the base
+   *   table.
+   * @param $arguments
+   *   An array of arguments to replace into the $condition of this join.
+   * @return
+   *   The unique alias that was assigned for this table.
+   */
+  public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array());
+
+  /**
+   * Left Outer Join against another table in the database.
+   *
+   * @param $table
+   *   The table against which to join.
+   * @param $alias
+   *   The alias for the table. In most cases this should be the first letter
+   *   of the table, or the first letter of each "word" in the table.
+   * @param $condition
+   *   The condition on which to join this table. If the join requires values,
+   *   this clause should use a named placeholder and the value or values to
+   *   insert should be passed in the 4th parameter. For the first table joined
+   *   on a query, this value is ignored as the first table is taken as the base
+   *   table.
+   * @param $arguments
+   *   An array of arguments to replace into the $condition of this join.
+   * @return
+   *   The unique alias that was assigned for this table.
+   */
+  public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array());
+
+  /**
+   * Right Outer Join against another table in the database.
+   *
+   * @param $table
+   *   The table against which to join.
+   * @param $alias
+   *   The alias for the table. In most cases this should be the first letter
+   *   of the table, or the first letter of each "word" in the table.
+   * @param $condition
+   *   The condition on which to join this table. If the join requires values,
+   *   this clause should use a named placeholder and the value or values to
+   *   insert should be passed in the 4th parameter. For the first table joined
+   *   on a query, this value is ignored as the first table is taken as the base
+   *   table.
+   * @param $arguments
+   *   An array of arguments to replace into the $condition of this join.
+   * @return
+   *   The unique alias that was assigned for this table.
+   */
+  public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array());
+
+  /**
+   * Join against another table in the database.
+   *
+   * This method does the "hard" work of queuing up a table to be joined against.
+   * In some cases, that may include dipping into the Schema API to find the necessary
+   * fields on which to join.
+   *
+   * @param $type
+   *   The type of join. Typically one one of INNER, LEFT OUTER, and RIGHT OUTER.
+   * @param $table
+   *   The table against which to join. May be a string or another SelectQuery
+   *   object. If a query object is passed, it will be used as a subselect.
+   * @param $alias
+   *   The alias for the table. In most cases this should be the first letter
+   *   of the table, or the first letter of each "word" in the table. If omitted,
+   *   one will be dynamically generated.
+   * @param $condition
+   *   The condition on which to join this table. If the join requires values,
+   *   this clause should use a named placeholder and the value or values to
+   *   insert should be passed in the 4th parameter. For the first table joined
+   *   on a query, this value is ignored as the first table is taken as the base
+   *   table.
+   * @param $arguments
+   *   An array of arguments to replace into the $condition of this join.
+   * @return
+   *   The unique alias that was assigned for this table.
+   */
+  public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array());
+
+  /**
+   * Orders the result set by a given field.
+   *
+   * If called multiple times, the query will order by each specified field in the
+   * order this method is called.
+   *
+   * @param $field
+   *   The field on which to order.
+   * @param $direction
+   *   The direction to sort. Legal values are "ASC" and "DESC".
+   * @return
+   *   The called object.
+   */
+  public function orderBy($field, $direction = 'ASC');
+
+  /**
+   * Restricts a query to a given range in the result set.
+   *
+   * If this method is called with no parameters, will remove any range
+   * directives that have been set.
+   *
+   * @param $start
+   *   The first record from the result set to return. If NULL, removes any
+   *   range directives that are set.
+   * @param $limit
+   *   The number of records to return from the result set.
+   * @return
+   *   The called object.
+   */
+  public function range($start = NULL, $length = NULL);
+
+  /**
+   * Groups the result set by the specified field.
+   *
+   * @param $field
+   *   The field on which to group. This should be the field as aliased.
+   * @return
+   *   The called object.
+   */
+  public function groupBy($field);
+
+  /**
+   * Get the equivalent COUNT query of this query as a new query object.
+   *
+   * @return
+   *   A new SelectQuery object with no fields or expressions besides COUNT(*).
+   */
+  public function countQuery();
+
+  /**
+   * Clone magic method.
+   *
+   * Select queries have dependent objects that must be deep-cloned.  The
+   * connection object itself, however, should not be cloned as that would
+   * duplicate the connection itself.
+   */
+  public function __clone();
+}
+
+/**
+ * The base extender class for Select queries.
+ */
+class SelectQueryExtender implements SelectQueryInterface {
+
+  /**
+   * The SelectQuery object we are extending/decorating.
+   *
+   * @var SelectQueryInterface
+   */
+  protected $query;
+
+  /**
+   * The connection object on which to run this query.
+   *
+   * @var DatabaseConnection
+   */
+  protected $connection;
+
+
+  public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) {
+    $this->query = $query;
+    $this->connection = $connection;
+  }
+
+  /* Implementations of QueryAlterableInterface. */
+
+  public function addTag($tag) {
+    $this->query->addTag($tag);
+    return $this;
+  }
+
+  public function hasTag($tag) {
+    return $this->query->hasTag($tag);
+  }
+
+  public function hasAllTags() {
+    return call_user_func_array(array($this->query, 'hasAllTags', func_get_args()));
+  }
+
+  public function hasAnyTag() {
+    return call_user_func_array(array($this->query, 'hasAnyTags', func_get_args()));
+  }
+
+  public function addMetaData($key, $object) {
+    $this->query->addMetaData($key, $object);
+    return $this;
+  }
+
+  public function getMetaData($key) {
+    return $this->query->getMetaData($key);
+  }
+
+  /* Implementations of QueryConditionInterface for the WHERE clause. */
+
+  public function condition($field, $value = NULL, $operator = '=') {
+    $this->query->condition($field, $value, $operator);
+    return $this;
+  }
+
+  public function &conditions() {
+    return $this->query->conditions();
+  }
+
+  public function arguments() {
+    return $this->query->arguments();
+  }
+
+  public function where($snippet, $args = array()) {
+    $this->query->where($snippet, $args);
+    return $this;
+  }
+
+  public function compile(DatabaseConnection $connection) {
+    return $this->query->compile($connection);
+  }
+
+  /* Implmeentations of QueryConditionInterface for the HAVING clause. */
+
+  public function havingCondition($field, $value = NULL, $operator = '=') {
+    $this->query->condition($field, $value, $operator, $num_args);
+    return $this;
+  }
+
+  public function &havingConditions() {
+    return $this->having->conditions();
+  }
+
+  public function havingArguments() {
+    return $this->having->arguments();
+  }
+
+  public function having($snippet, $args = array()) {
+    $this->query->where($snippet, $args);
+    return $this;
+  }
+
+  public function havingCompile(DatabaseConnection $connection) {
+    return $this->query->havingCompile($connection);
+  }
+
+  /* Implementations of QueryExtendableInterface. */
+
+  public function extend($extender_name) {
+    $override_class = $this->connection->driver();
+    if (class_exists($override_class)) {
+      $extender_name = $override_class;
+    }
+    return new $extender_name($this, $this->connection);
+  }
+
+  /* Alter accessors to expose the query data to alter hooks. */
+
+  public function &getFields() {
+    return $this->query->getFields();
+  }
+
+  public function &getExpressions() {
+    return $this->query->getExpressions();
+  }
+
+  public function &getOrderBy() {
+    return $this->query->getOrderBy();
+  }
+
+  public function &getTables() {
+    return $this->query->getTables();
+  }
+
+  public function getArguments() {
+    return $this->query->getArguments();
+  }
+
+  public function execute() {
+    return $this->query->execute();
+  }
+
+  public function distinct($distinct = TRUE) {
+    $this->query->distinct($distinct);
+    return $this;
+  }
+
+  public function addField($table_alias, $field, $alias = NULL) {
+    return $this->query->addField($table_alias, $field, $alias);
+  }
+
+  public function fields($table_alias, array $fields = array()) {
+    $this->query->fields($table_alias, $fields);
+    return $this;
+  }
+
+  public function addExpression($expression, $alias = NULL, $arguments = array()) {
+    return $this->query->addExpression($expression, $alias, $arguments);
+  }
+
+  public function join($table, $alias = NULL, $condition = NULL, $arguments = array()) {
+    return $this->query->join($table, $alias, $condition, $arguments);
+  }
+
+  public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
+    return $this->query->innerJoin($table, $alias, $condition, $arguments);
+  }
+
+  public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
+    return $this->query->leftJoin($table, $alias, $condition, $arguments);
+  }
+
+  public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
+    return $this->query->rightJoin($table, $alias, $condition, $arguments);
+  }
+
+  public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) {
+    return $this->query->addJoin($type, $table, $alias, $condition, $arguments);
+  }
+
+  public function orderBy($field, $direction = 'ASC') {
+    $this->query->orderBy($field, $direction);
+    return $this;
+  }
+
+  public function range($start = NULL, $length = NULL) {
+    $this->query->range($start, $length);
+    return $this;
+  }
+
+  public function groupBy($field) {
+    $this->query->groupBy($field);
+    return $this;
+  }
+
+  public function countQuery() {
+    // Create our new query object that we will mutate into a count query.
+    $count = clone($this);
+
+    // Zero-out existing fields and expressions.
+    $fields =& $count->getFields();
+    $fields = array();
+    $expressions =& $count->getExpressions();
+    $expressions = array();
+
+    // Ordering a count query is a waste of cycles, and breaks on some
+    // databases anyway.
+    $orders = &$count->getOrderBy();
+    $orders = array();
+
+    // COUNT() is an expression, so we add that back in.
+    $count->addExpression('COUNT(*)');
+
+    return $count;
+  }
+
+  public function __toString() {
+    return (string)$this->query;
+  }
+
+  public function __clone() {
+    // We need to deep-clone the query we're wrapping, which in turn may
+    // deep-clone other objects.  Exciting!
+    $this->query = clone($this->query);
+  }
+
+  /**
+   * Magic override for undefined methods.
+   *
+   * If one extender extends another extender, then methods in the inner extender
+   * will not be exposed on the outer extender.  That's because we cannot know
+   * in advance what those methods will be, so we cannot provide wrapping
+   * implementations as we do above.  Instead, we use this slower catch-all method
+   * to handle any additional methods.
+   */
+  public function __call($method, $args) {
+    $return = call_user_func_array(array($this->query, $method), $args);
+
+    // Some methods will return the called object as part of a fluent interface.
+    // Others will return some useful value.  If it's a value, then the caller
+    // probably wants that value.  If it's the called object, then we instead
+    // return this object.  That way we don't "lose" an extender layer when
+    // chaining methods together.
+    if ($return instanceof SelectQueryInterface) {
+      return $this;
+    }
+    else {
+      return $return;
+    }
+  }
+}
+
+/**
  * Query builder for SELECT statements.
  */
-class SelectQuery extends Query implements QueryConditionInterface, QueryAlterableInterface {
+class SelectQuery extends Query implements SelectQueryInterface {
 
   /**
    * The fields to SELECT.
@@ -104,6 +713,7 @@ class SelectQuery extends Query implemen
 
   public function addTag($tag) {
     $this->alterTags[$tag] = 1;
+    return $this;
   }
 
   public function hasTag($tag) {
@@ -120,6 +730,7 @@ class SelectQuery extends Query implemen
 
   public function addMetaData($key, $object) {
     $this->alterMetaData[$key] = $object;
+    return $this;
   }
 
   public function getMetaData($key) {
@@ -180,94 +791,34 @@ class SelectQuery extends Query implemen
     return $this->having->compile($connection);
   }
 
+  /* Implementations of QueryExtendableInterface. */
+
+  public function extend($extender_name) {
+    $override_class = __CLASS__ . $this->connection->driver();
+    if (class_exists($override_class)) {
+      $extender_name = $override_class;
+    }
+    return new $extender_name($this, $this->connection);
+  }
+
   /* Alter accessors to expose the query data to alter hooks. */
 
-  /**
-   * Returns a reference to the fields array for this query.
-   *
-   * Because this method returns by reference, alter hooks may edit the fields
-   * array directly to make their changes. If just adding fields, however, the
-   * use of addField() is preferred.
-   *
-   * Note that this method must be called by reference as well:
-   *
-   * @code
-   * $fields =& $query->getFields();
-   * @endcode
-   *
-   * @return
-   *   A reference to the fields array structure.
-   */
   public function &getFields() {
     return $this->fields;
-  }
-
-  /**
-   * Returns a reference to the expressions array for this query.
-   *
-   * Because this method returns by reference, alter hooks may edit the expressions
-   * array directly to make their changes. If just adding expressions, however, the
-   * use of addExpression() is preferred.
-   *
-   * Note that this method must be called by reference as well:
-   *
-   * @code
-   * $fields =& $query->getExpressions();
-   * @endcode
-   *
-   * @return
-   *   A reference to the expression array structure.
-   */
+  }
+
   public function &getExpressions() {
     return $this->expressions;
   }
 
-  /**
-   * Returns a reference to the order by array for this query.
-   *
-   * Because this method returns by reference, alter hooks may edit the order-by
-   * array directly to make their changes. If just adding additional ordering
-   * fields, however, the use of orderBy() is preferred.
-   *
-   * Note that this method must be called by reference as well:
-   *
-   * @code
-   * $fields =& $query->getOrderBy();
-   * @endcode
-   *
-   * @return
-   *   A reference to the expression array structure.
-   */
   public function &getOrderBy() {
     return $this->order;
   }
 
-  /**
-   * Returns a reference to the tables array for this query.
-   *
-   * Because this method returns by reference, alter hooks may edit the tables
-   * array directly to make their changes. If just adding tables, however, the
-   * use of the join() methods is preferred.
-   *
-   * Note that this method must be called by reference as well:
-   *
-   * @code
-   * $fields =& $query->getTables();
-   * @endcode
-   *
-   * @return
-   *   A reference to the tables array structure.
-   */
   public function &getTables() {
     return $this->tables;
   }
 
-  /**
-   * Compiles and returns an associative array of the arguments for this prepared statement.
-   *
-   * @return
-   *   An associative array of all placeholder arguments for this query.
-   */
   public function getArguments() {
     $this->where->compile($this->connection);
     $this->having->compile($this->connection);
@@ -307,36 +858,11 @@ class SelectQuery extends Query implemen
     return $this->connection->query((string)$this, $args, $this->queryOptions);
   }
 
-  /**
-   * Sets this query to be DISTINCT.
-   *
-   * @param $distinct
-   *   TRUE to flag this query DISTINCT, FALSE to disable it.
-   * @return
-   *   The called object.
-   */
   public function distinct($distinct = TRUE) {
     $this->distinct = $distinct;
     return $this;
   }
 
-  /**
-   * Adds a field to the list to be SELECTed.
-   *
-   * @param $table_alias
-   *   The name of the table from which the field comes, as an alias. Generally
-   *   you will want to use the return value of join() here to ensure that it is
-   *   valid.
-   * @param $field
-   *   The name of the field.
-   * @param $alias
-   *   The alias for this field. If not specified, one will be generated
-   *   automatically based on the $table_alias and $field. The alias will be
-   *   checked for uniqueness, so the requested alias may not be the alias
-   *   that is assigned in all cases.
-   * @return
-   *   The unique alias that was assigned for this field.
-   */
   public function addField($table_alias, $field, $alias = NULL) {
     // If no alias is specified, first try the field name itself.
     if (empty($alias)) {
@@ -365,27 +891,6 @@ class SelectQuery extends Query implemen
     return $alias;
   }
 
-  /**
-   * Add multiple fields from the same table to be SELECTed.
-   *
-   * This method does not return the aliases set for the passed fields. In the
-   * majority of cases that is not a problem, as the alias will be the field
-   * name. However, if you do need to know the alias you can call getFields()
-   * and examine the result to determine what alias was created. Alternatively,
-   * simply use addField() for the few fields you care about and this method for
-   * the rest.
-   *
-   * @param $table_alias
-   *   The name of the table from which the field comes, as an alias. Generally
-   *   you will want to use the return value of join() here to ensure that it is
-   *   valid.
-   * @param $fields
-   *   An indexed array of fields present in the specified table that should be
-   *   included in this query. If not specified, $table_alias.* will be generated
-   *   without any aliases.
-   * @return
-   *   The called object.
-   */
   public function fields($table_alias, array $fields = array()) {
 
     if ($fields) {
@@ -402,32 +907,6 @@ class SelectQuery extends Query implemen
     return $this;
   }
 
-  /**
-   * Private list of aliases already attributed to expression fields.
-   *
-   * @var Array
-   */
-  private $expressionAliases = array();
-
-  /**
-   * Adds an expression to the list of "fields" to be SELECTed.
-   *
-   * An expression can be any arbitrary string that is valid SQL. That includes
-   * various functions, which may in some cases be database-dependent. This
-   * method makes no effort to correct for database-specific functions.
-   *
-   * @param $expression
-   *   The expression string. May contain placeholders.
-   * @param $alias
-   *   The alias for this expression. If not specified, one will be generated
-   *   automatically in the form "expression_#". The alias will be checked for
-   *   uniqueness, so the requested alias may not be the alias that is assigned
-   *   in all cases.
-   * @param $arguments
-   *   Any placeholder arguments needed for this expression.
-   * @return
-   *   The unique alias that was assigned for this expression.
-   */
   public function addExpression($expression, $alias = NULL, $arguments = array()) {
     if (empty($alias)) {
       $alias = 'expression';
@@ -449,127 +928,22 @@ class SelectQuery extends Query implemen
     return $alias;
   }
 
-  /**
-   * Default Join against another table in the database.
-   *
-   * This method is a convenience method for innerJoin().
-   *
-   * @param $table
-   *   The table against which to join.
-   * @param $alias
-   *   The alias for the table. In most cases this should be the first letter
-   *   of the table, or the first letter of each "word" in the table.
-   * @param $condition
-   *   The condition on which to join this table. If the join requires values,
-   *   this clause should use a named placeholder and the value or values to
-   *   insert should be passed in the 4th parameter. For the first table joined
-   *   on a query, this value is ignored as the first table is taken as the base
-   *   table.
-   * @param $arguments
-   *   An array of arguments to replace into the $condition of this join.
-   * @return
-   *   The unique alias that was assigned for this table.
-   */
   public function join($table, $alias = NULL, $condition = NULL, $arguments = array()) {
     return $this->addJoin('INNER', $table, $alias, $condition, $arguments);
   }
 
-  /**
-   * Inner Join against another table in the database.
-   *
-   * @param $table
-   *   The table against which to join.
-   * @param $alias
-   *   The alias for the table. In most cases this should be the first letter
-   *   of the table, or the first letter of each "word" in the table.
-   * @param $condition
-   *   The condition on which to join this table. If the join requires values,
-   *   this clause should use a named placeholder and the value or values to
-   *   insert should be passed in the 4th parameter. For the first table joined
-   *   on a query, this value is ignored as the first table is taken as the base
-   *   table.
-   * @param $arguments
-   *   An array of arguments to replace into the $condition of this join.
-   * @return
-   *   The unique alias that was assigned for this table.
-   */
   public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
     return $this->addJoin('INNER', $table, $alias, $condition, $arguments);
   }
 
-  /**
-   * Left Outer Join against another table in the database.
-   *
-   * @param $table
-   *   The table against which to join.
-   * @param $alias
-   *   The alias for the table. In most cases this should be the first letter
-   *   of the table, or the first letter of each "word" in the table.
-   * @param $condition
-   *   The condition on which to join this table. If the join requires values,
-   *   this clause should use a named placeholder and the value or values to
-   *   insert should be passed in the 4th parameter. For the first table joined
-   *   on a query, this value is ignored as the first table is taken as the base
-   *   table.
-   * @param $arguments
-   *   An array of arguments to replace into the $condition of this join.
-   * @return
-   *   The unique alias that was assigned for this table.
-   */
   public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
     return $this->addJoin('LEFT OUTER', $table, $alias, $condition, $arguments);
   }
 
-  /**
-   * Right Outer Join against another table in the database.
-   *
-   * @param $table
-   *   The table against which to join.
-   * @param $alias
-   *   The alias for the table. In most cases this should be the first letter
-   *   of the table, or the first letter of each "word" in the table.
-   * @param $condition
-   *   The condition on which to join this table. If the join requires values,
-   *   this clause should use a named placeholder and the value or values to
-   *   insert should be passed in the 4th parameter. For the first table joined
-   *   on a query, this value is ignored as the first table is taken as the base
-   *   table.
-   * @param $arguments
-   *   An array of arguments to replace into the $condition of this join.
-   * @return
-   *   The unique alias that was assigned for this table.
-   */
   public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
     return $this->addJoin('RIGHT OUTER', $table, $alias, $condition, $arguments);
   }
 
-  /**
-   * Join against another table in the database.
-   *
-   * This method does the "hard" work of queuing up a table to be joined against.
-   * In some cases, that may include dipping into the Schema API to find the necessary
-   * fields on which to join.
-   *
-   * @param $type
-   *   The type of join. Typically one one of INNER, LEFT OUTER, and RIGHT OUTER.
-   * @param $table
-   *   The table against which to join. May be a string or another SelectQuery
-   *   object. If a query object is passed, it will be used as a subselect.
-   * @param $alias
-   *   The alias for the table. In most cases this should be the first letter
-   *   of the table, or the first letter of each "word" in the table. If omitted,
-   *   one will be dynamically generated.
-   * @param $condition
-   *   The condition on which to join this table. If the join requires values,
-   *   this clause should use a named placeholder and the value or values to
-   *   insert should be passed in the 4th parameter. For the first table joined
-   *   on a query, this value is ignored as the first table is taken as the base
-   *   table.
-   * @param $arguments
-   *   An array of arguments to replace into the $condition of this join.
-   * @return
-   *   The unique alias that was assigned for this table.
-   */
   public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) {
 
     if (empty($alias)) {
@@ -599,64 +973,23 @@ class SelectQuery extends Query implemen
     return $alias;
   }
 
-  /**
-   * Orders the result set by a given field.
-   *
-   * If called multiple times, the query will order by each specified field in the
-   * order this method is called.
-   *
-   * @param $field
-   *   The field on which to order.
-   * @param $direction
-   *   The direction to sort. Legal values are "ASC" and "DESC".
-   * @return
-   *   The called object.
-   */
   public function orderBy($field, $direction = 'ASC') {
     $this->order[$field] = $direction;
     return $this;
   }
 
-  /**
-   * Restricts a query to a given range in the result set.
-   *
-   * If this method is called with no parameters, will remove any range
-   * directives that have been set.
-   *
-   * @param $start
-   *   The first record from the result set to return. If NULL, removes any
-   *   range directives that are set.
-   * @param $limit
-   *   The number of records to return from the result set.
-   * @return
-   *   The called object.
-   */
   public function range($start = NULL, $length = NULL) {
     $this->range = func_num_args() ? array('start' => $start, 'length' => $length) : array();
     return $this;
   }
 
-  /**
-   * Groups the result set by the specified field.
-   *
-   * @param $field
-   *   The field on which to group. This should be the field as aliased.
-   * @return
-   *   The called object.
-   */
   public function groupBy($field) {
     $this->group[] = $field;
+    return $this;
   }
 
-  /**
-   * Get the equivalent COUNT query of this query as a new query object.
-   *
-   * @return
-   *   A new SelectQuery object with no fields or expressions besides COUNT(*).
-   */
   public function countQuery() {
-    // Shallow-clone this query. We don't want to duplicate any of the
-    // referenced objects, so a shallow query is all we need.
+    // Create our new query object that we will mutate into a count query.
     $count = clone($this);
 
     // Zero-out existing fields and expressions.
Index: modules/comment/comment.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.admin.inc,v
retrieving revision 1.13
diff -u -p -r1.13 comment.admin.inc
--- modules/comment/comment.admin.inc	28 Jan 2009 07:43:26 -0000	1.13
+++ modules/comment/comment.admin.inc	7 Feb 2009 21:39:42 -0000
@@ -58,6 +58,33 @@ function comment_admin_overview($type = 
 
   // Load the comments that need to be displayed.
   $status = ($arg == 'approval') ? COMMENT_NOT_PUBLISHED : COMMENT_PUBLISHED;
+<<<<<<< comment.admin.inc
+  $form['header'] = array(
+    '#type' => 'value',
+    '#value' => array(
+      theme('table_select_header_cell'),
+      array('data' => t('Subject'), 'field' => 'subject'),
+      array('data' => t('Author'), 'field' => 'name'),
+      array('data' => t('Posted in'), 'field' => 'node_title'),
+      array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
+      array('data' => t('Operations')),
+  ));
+
+  $select = db_select('comment', 'c');
+  $select->join('users', 'u', 'u.uid = c.uid');
+  $select->join('node', 'n', 'n.nid = c.nid');
+  $select->addField('u', 'name', 'registered_name');
+  $select->addField('u', 'uid', 'uid');
+  $select->addField('n', 'title', 'node_title');
+  $select
+    ->fields('c', array('subject', 'nid', 'cid', 'comment', 'timestamp', 'status', 'name', 'homepage'))
+    ->condition('c.status', $status)
+    ->extend('PagerDefault')->extend('TableSort')
+    ->limit(50)
+    ->setHeader($form['header']['#value']);
+
+  $result = $select->execute();
+=======
   $header = array(
     'subject' => array('data' => t('Subject'), 'field' => 'subject'),
     'author' => array('data' => t('Author'), 'field' => 'name'),
@@ -67,10 +94,30 @@ function comment_admin_overview($type = 
   );
 
   $result = pager_query('SELECT c.subject, c.nid, c.cid, c.comment, c.timestamp, c.status, c.name, c.homepage, u.name AS registered_name, u.uid, n.title as node_title FROM {comment} c INNER JOIN {users} u ON u.uid = c.uid INNER JOIN {node} n ON n.nid = c.nid WHERE c.status = %d' . tablesort_sql($header), 50, 0, NULL, $status);
+>>>>>>> 1.13
 
   // Build a table listing the appropriate comments.
   $options = array();
   $destination = drupal_get_destination();
+<<<<<<< comment.admin.inc
+  foreach ($result as $comment) {
+    $comments[$comment->cid] = '';
+    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+    $form['subject'][$comment->cid] = array(
+      '#markup' => l($comment->subject, 'node/' . $comment->nid, array('attributes' => array('title' => truncate_utf8($comment->comment, 128)), 'fragment' => 'comment-' . $comment->cid))
+    );
+    $form['username'][$comment->cid] = array(
+      '#markup' => theme('username', $comment)
+    );
+    $form['node_title'][$comment->cid] = array(
+      '#markup' => l($comment->node_title, 'node/' . $comment->nid)
+    );
+    $form['timestamp'][$comment->cid] = array(
+      '#markup' => format_date($comment->timestamp, 'small')
+    );
+    $form['operations'][$comment->cid] = array(
+      '#markup' => l(t('edit'), 'comment/edit/' . $comment->cid, array('query' => $destination))
+=======
 
   while ($comment = db_fetch_object($result)) {
     $options[$comment->cid] = array(
@@ -79,6 +126,7 @@ function comment_admin_overview($type = 
       'posted_in' => l($comment->node_title, 'node/' . $comment->nid),
       'time' => format_date($comment->timestamp, 'small'),
       'operations' => l(t('edit'), 'comment/edit/' . $comment->cid, array('query' => $destination)),
+>>>>>>> 1.13
     );
   }
 
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1023
diff -u -p -r1.1023 node.module
--- modules/node/node.module	6 Feb 2009 16:25:08 -0000	1.1023
+++ modules/node/node.module	7 Feb 2009 21:39:43 -0000
@@ -2024,7 +2024,18 @@ function node_build_multiple($nodes, $te
  * Menu callback; Generate a listing of promoted nodes.
  */
 function node_page_default() {
-  $nids = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10))->fetchCol();
+  $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->condition('promote', 1)
+    ->condition('status', 1)
+    ->orderBy('sticky', 'DESC')
+    ->orderBy('created', 'DESC')
+    ->extend('PagerDefault')
+    ->limit(variable_get('default_nodes_main', 10))
+    ->addTag('node_access');
+
+  $nids = $select->execute()->fetchCol();
+
   if (!empty($nids)) {
     $nodes = node_load_multiple($nids);
     $build = node_build_multiple($nodes);
Index: modules/simpletest/tests/database_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.module,v
retrieving revision 1.6
diff -u -p -r1.6 database_test.module
--- modules/simpletest/tests/database_test.module	11 Jan 2009 10:57:20 -0000	1.6
+++ modules/simpletest/tests/database_test.module	7 Feb 2009 21:39:43 -0000
@@ -48,6 +48,11 @@ function database_test_menu() {
     'access callback' => TRUE,
     'page callback' => 'database_test_db_query_temporary',
   );
+  $items['database_test/pager_query'] = array(
+    'access callback' => TRUE,
+    'page callback' => 'database_test_pager_query',
+  );
+
   return $items;
 }
 
@@ -66,3 +71,28 @@ function database_test_db_query_temporar
   ));
   exit;
 }
+
+/**
+ * Run a pager query and return the results.
+ *
+ * This function does care about the page GET parameter, as set by the
+ * simpletest HTTP call.
+ */
+function database_test_pager_query($limit) {
+
+	$query = db_select('test', 't');
+  $query
+    ->fields('t', array('name'))
+    ->orderBy('age');
+
+  // This should result in 3 pages of results.
+  $query = $query->extend('PagerDefault')->limit($limit);
+
+  $names = $query->execute()->fetchCol();
+
+  drupal_json(array(
+    'names' => $names,
+  ));
+  exit;
+}
+
Index: modules/simpletest/tests/database_test.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.test,v
retrieving revision 1.39
diff -u -p -r1.39 database_test.test
--- modules/simpletest/tests/database_test.test	25 Jan 2009 12:19:32 -0000	1.39
+++ modules/simpletest/tests/database_test.test	7 Feb 2009 21:39:43 -0000
@@ -1478,6 +1478,49 @@ class DatabaseSelectComplexTestCase exte
   }
 }
 
+class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('Pager query tests'),
+      'description' => t('Test the pager query extender.'),
+      'group' => t('Database'),
+    );
+  }
+
+  /**
+   * Confirm that a pager query returns the correct results.
+   *
+   * Note that we have to make an HTTP request to a test page handler
+   * because the pager depends on GET parameters.
+   */
+  function testPagerQuery() {
+
+  	// To keep the test from being too brittle, we determine up front
+  	// what the page count should be dynamically, and pass the control
+  	// information forward to the actual query on the other side of the
+  	// HTTP request.
+  	$limit = 2;
+  	$count = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
+
+
+  	$correct_number = $limit;
+  	$num_pages = floor($count / $limit);
+  	for ($page = 0; $page <= $num_pages; ++$page) {
+	    $this->drupalGet('database_test/pager_query/' . $limit, array('query' => array('page' => $page)));
+	    $data = json_decode($this->drupalGetContent());
+
+	    if ($page == $num_pages) {
+	      $correct_number = $count - ($limit * $page);
+	    }
+
+	    $this->assertEqual(count($data->names), $correct_number, t('Correct number of records returned by pager.'));
+  	}
+
+  }
+
+}
+
 /**
  * Select tagging tests.
  *
