summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2009-02-22 16:53:41 +0000
committerDries Buytaert <dries@buytaert.net>2009-02-22 16:53:41 +0000
commitb3e36d655c831c63c26a710eb3c8bd82ca3b6fc5 (patch)
treeaf24f8462bd347de36cd7375fc78dffb48e7d0b9
parente1652e99b61577ac42d6d6618420c872c0e42435 (diff)
downloadbrdo-b3e36d655c831c63c26a710eb3c8bd82ca3b6fc5.tar.gz
brdo-b3e36d655c831c63c26a710eb3c8bd82ca3b6fc5.tar.bz2
- Patch #299267 by Crell: add extender support to the SELECT query builder.
-rw-r--r--includes/database/query.inc12
-rw-r--r--includes/database/select.inc915
-rw-r--r--includes/pager.inc144
-rw-r--r--includes/tablesort.inc114
-rw-r--r--modules/comment/comment.admin.inc17
-rw-r--r--modules/node/node.module13
-rw-r--r--modules/simpletest/tests/database_test.module90
-rw-r--r--modules/simpletest/tests/database_test.test119
8 files changed, 1128 insertions, 296 deletions
diff --git a/includes/database/query.inc b/includes/database/query.inc
index 7186c5a6a..dd6a4da9a 100644
--- a/includes/database/query.inc
+++ b/includes/database/query.inc
@@ -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();
}
diff --git a/includes/database/select.inc b/includes/database/select.inc
index 532b2e5a1..bef03f756 100644
--- a/includes/database/select.inc
+++ b/includes/database/select.inc
@@ -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 implements QueryConditionInterface, QueryAlterab
public function addTag($tag) {
$this->alterTags[$tag] = 1;
+ return $this;
}
public function hasTag($tag) {
@@ -120,6 +730,7 @@ class SelectQuery extends Query implements QueryConditionInterface, QueryAlterab
public function addMetaData($key, $object) {
$this->alterMetaData[$key] = $object;
+ return $this;
}
public function getMetaData($key) {
@@ -180,94 +791,34 @@ class SelectQuery extends Query implements QueryConditionInterface, QueryAlterab
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 implements QueryConditionInterface, QueryAlterab
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 implements QueryConditionInterface, QueryAlterab
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 implements QueryConditionInterface, QueryAlterab
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 implements QueryConditionInterface, QueryAlterab
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 implements QueryConditionInterface, QueryAlterab
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.
diff --git a/includes/pager.inc b/includes/pager.inc
index 4b219a8ca..a3c80ae5e 100644
--- a/includes/pager.inc
+++ b/includes/pager.inc
@@ -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.
*
diff --git a/includes/tablesort.inc b/includes/tablesort.inc
index 36f01607f..382014fa8 100644
--- a/includes/tablesort.inc
+++ b/includes/tablesort.inc
@@ -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.
*/
diff --git a/modules/comment/comment.admin.inc b/modules/comment/comment.admin.inc
index 9a35014b9..8d88ec77e 100644
--- a/modules/comment/comment.admin.inc
+++ b/modules/comment/comment.admin.inc
@@ -66,13 +66,26 @@ function comment_admin_overview($type = 'new', $arg) {
'operations' => array('data' => t('Operations')),
);
- $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 {user} 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);
+ $query = db_select('comment', 'c');
+ $query->join('user', 'u', 'u.uid = c.uid');
+ $query->join('node', 'n', 'n.nid = c.nid');
+ $query->addField('u', 'name', 'registered_name');
+ $query->addField('n', 'title', 'node_title');
+ $query
+ ->fields('c', array('subject', 'nid', 'cid', 'comment', 'timestamp', 'status', 'name', 'homepage'))
+ ->fields('u', array('uid'))
+ ->condition('c.status', $status)
+ ->extend('PagerDefault')->extend('TableSort')
+ ->limit(50)
+ ->setHeader($header);
+ $result = $query->execute();
+
// Build a table listing the appropriate comments.
$options = array();
$destination = drupal_get_destination();
- while ($comment = db_fetch_object($result)) {
+ foreach ($result as $comment) {
$options[$comment->cid] = array(
'subject' => l($comment->subject, 'node/' . $comment->nid, array('attributes' => array('title' => truncate_utf8($comment->comment, 128)), 'fragment' => 'comment-' . $comment->cid)),
'author' => theme('username', $comment),
diff --git a/modules/node/node.module b/modules/node/node.module
index 2f1d38bec..e601f2260 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -2024,7 +2024,18 @@ function node_build_multiple($nodes, $teaser = TRUE, $weight = 0) {
* 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);
diff --git a/modules/simpletest/tests/database_test.module b/modules/simpletest/tests/database_test.module
index 573075078..2c1d22597 100644
--- a/modules/simpletest/tests/database_test.module
+++ b/modules/simpletest/tests/database_test.module
@@ -48,6 +48,19 @@ function database_test_menu() {
'access callback' => TRUE,
'page callback' => 'database_test_db_query_temporary',
);
+ $items['database_test/pager_query_even'] = array(
+ 'access callback' => TRUE,
+ 'page callback' => 'database_test_even_pager_query',
+ );
+ $items['database_test/pager_query_odd'] = array(
+ 'access callback' => TRUE,
+ 'page callback' => 'database_test_odd_pager_query',
+ );
+ $items['database_test/tablesort'] = array(
+ 'access callback' => TRUE,
+ 'page callback' => 'database_test_tablesort',
+ );
+
return $items;
}
@@ -66,3 +79,80 @@ function database_test_db_query_temporary() {
));
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_even_pager_query($limit) {
+
+ $query = db_select('test', 't');
+ $query
+ ->fields('t', array('name'))
+ ->orderBy('age');
+
+ // This should result in 2 pages of results.
+ $query = $query->extend('PagerDefault')->limit($limit);
+
+ $names = $query->execute()->fetchCol();
+
+ drupal_json(array(
+ 'names' => $names,
+ ));
+ 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_odd_pager_query($limit) {
+
+ $query = db_select('test_task', 't');
+ $query
+ ->fields('t', array('task'))
+ ->orderBy('pid');
+
+ // This should result in 4 pages of results.
+ $query = $query->extend('PagerDefault')->limit($limit);
+
+ $names = $query->execute()->fetchCol();
+
+ drupal_json(array(
+ 'names' => $names,
+ ));
+ exit;
+}
+
+/**
+ * Run a tablesort query and return the results.
+ *
+ * This function does care about the page GET parameter, as set by the
+ * simpletest HTTP call.
+ */
+function database_test_tablesort() {
+ $header = array(
+ 'tid' => array('data' => t('Task ID'), 'field' => 'tid', 'sort' => 'desc'),
+ 'pid' => array('data' => t('Person ID'), 'field' => 'pid'),
+ 'task' => array('data' => t('Task'), 'field' => 'task'),
+ 'priority' => array('data' => t('Priority'), 'field' => 'priority', ),
+ );
+
+ $query = db_select('test_task', 't');
+ $query
+ ->fields('t', array('tid', 'pid', 'task', 'priority'));
+
+ $query = $query->extend('TableSort')->setHeader($header);
+
+ // We need all the results at once to check the sort.
+ $tasks = $query->execute()->fetchAll();
+
+ drupal_json(array(
+ 'tasks' => $tasks,
+ ));
+ exit;
+} \ No newline at end of file
diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test
index faaeb01ab..09d209bc4 100644
--- a/modules/simpletest/tests/database_test.test
+++ b/modules/simpletest/tests/database_test.test
@@ -1530,6 +1530,125 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase {
}
}
+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 testEvenPagerQuery() {
+ // 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);
+
+ // If there is no remainder from rounding, subtract 1 since we index from 0.
+ if (!($num_pages * $limit < $count)) {
+ $num_pages--;
+ }
+
+ for ($page = 0; $page <= $num_pages; ++$page) {
+ $this->drupalGet('database_test/pager_query_even/' . $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: @number', array('@number' => $correct_number)));
+ }
+ }
+
+ /**
+ * 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 testOddPagerQuery() {
+ // 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_task}")->fetchField();
+
+ $correct_number = $limit;
+ $num_pages = floor($count / $limit);
+
+ // If there is no remainder from rounding, subtract 1 since we index from 0.
+ if (!($num_pages * $limit < $count)) {
+ $num_pages--;
+ }
+
+ for ($page = 0; $page <= $num_pages; ++$page) {
+ $this->drupalGet('database_test/pager_query_odd/' . $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: @number', array('@number' => $correct_number)));
+ }
+ }
+}
+
+
+class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase {
+
+ function getInfo() {
+ return array(
+ 'name' => t('Tablesort query tests'),
+ 'description' => t('Test the tablesort query extender.'),
+ 'group' => t('Database'),
+ );
+ }
+
+ /**
+ * Confirm that a tablesort 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 testTableSortQuery() {
+ $sorts = array(
+ array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
+ array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
+ array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
+ array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
+ // more elements here
+
+ );
+
+ foreach ($sorts as $sort) {
+ $this->drupalGet('database_test/tablesort/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
+ $data = json_decode($this->drupalGetContent());
+
+ $first = array_shift($data->tasks);
+ $last = array_pop($data->tasks);
+
+ $this->assertEqual($first->task, $sort['first'], t('Items appear in the correct order.'));
+ $this->assertEqual($last->task, $sort['last'], t('Items appear in the correct order.'));
+ }
+ }
+}
+
/**
* Select tagging tests.
*