diff --git a/includes/database/pgsql/query.inc b/includes/database/pgsql/query.inc index 9005a1a83718e943d1a2e7a198c95fdf95b40f7b..ac5072c76f7152658180cabe7cc5c7081351a321 100644 --- a/includes/database/pgsql/query.inc +++ b/includes/database/pgsql/query.inc @@ -173,3 +173,13 @@ public function execute() { return $stmt->rowCount(); } } + +class SelectQuery_pgsql extends SelectQuery { + + public function orderRandom() { + $alias = $this->addExpression('RANDOM()', 'random_field'); + $this->orderBy($alias); + return $this; + } + +} diff --git a/includes/database/select.inc b/includes/database/select.inc index a4c4ed3e40c74c672e33d8fb2130283d6d2d3104..55eecf5868ccfe2e3801fd210a040325bb40d096 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -347,6 +347,28 @@ public function addJoin($type, $table, $alias = NULL, $condition = NULL, $argume */ public function orderBy($field, $direction = 'ASC'); + /** + * Orders the result set by a random value. + * + * This may be stacked with other orderBy() calls. If so, the query will order + * by each specified field, including this one, in the order called. Although + * this method may be called multiple times on the same query, doing so + * is not particularly useful. + * + * Note: The method used by most drivers may not scale to very large result + * sets. If you need to work with extremely large data sets, you may create + * your own database driver by subclassing off of an existing driver and + * implementing your own randomization mechanism. See + * + * http://jan.kneschke.de/projects/mysql/order-by-rand/ + * + * for an example of such an alternate sorting mechanism. + * + * @return + * The called object + */ + public function orderRandom(); + /** * Restricts a query to a given range in the result set. * @@ -643,6 +665,11 @@ public function orderBy($field, $direction = 'ASC') { return $this; } + public function orderRandom() { + $this->query->orderRandom(); + return $this; + } + public function range($start = NULL, $length = NULL) { $this->query->range($start, $length); return $this; @@ -1182,6 +1209,12 @@ public function orderBy($field, $direction = 'ASC') { return $this; } + public function orderRandom() { + $alias = $this->addExpression('RAND()', 'random_field'); + $this->orderBy($alias); + return $this; + } + public function range($start = NULL, $length = NULL) { $this->range = func_num_args() ? array('start' => $start, 'length' => $length) : array(); return $this; diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 8c1bb1dc57097fa06eabfd91659a758be05e13c6..e12431a30d3962f6f245b780b0d58a663e82b7c4 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -1400,6 +1400,55 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $this->assertEqual($names[1], 'Ringo', t('Second query returned correct second name.')); $this->assertEqual($names[2], 'Ringo', t('Third query returned correct name.')); } + + /** + * Test that random ordering of queries works. + * + * This is a non-deterministic query. That is, it is not guaranteed to pass + * 100% of the time. Random numbers are like that. Instead, we take two sets + * of random selects. By the laws of probability, the average of each set + * should be roughly the average of the data set being selected from and + * roughly the same each time through, provided that the original data set + * is evenly distributed. Because we know that ours is, this test should pass + * correctly 99% of the time. It is not possible to unit test for that other + * 1% successfully, so that will have to do. + */ + function testRandomOrder() { + $sets = 2; + $runs = 50; + + $ids = array(); + $ids = array(); + + $max = db_query("SELECT MAX(id) FROM {test}")->fetchField(); + $min = db_query("SELECT MIN(id) FROM {test}")->fetchField(); + + $ideal_average = ($min + $max) / 2; + $allowed_deviation = ($max - $min) / 4; + + for ($i = 0; $i < $sets; ++$i) { + for ($j = 0; $j < $runs; ++$j) { + $ids[$i][] = db_select('test', 't') + ->fields('t', array('id')) + ->range(0, 1) + ->orderRandom() + ->orderBy('id') + ->execute() + ->fetchField(); + } + } + + for ($i = 0; $i < $sets; ++$i) { + $found_average[$i] = array_sum($ids[$i]) / count($ids[$i]); + $deviation = abs($found_average[$i] - $ideal_average); + $this->assertTrue($deviation <= $allowed_deviation, t('Random ids are within allowed deviation.')); + } + + for ($i = 1; $i < $sets; ++$i) { + $deviation = abs($found_average[$i] - $found_average[$i-1]); + $this->assertTrue($deviation <= $allowed_deviation, t('Random ids are within allowed deviation from each other.')); + } + } } /** @@ -2897,7 +2946,7 @@ class DatabaseExtraTypesTestCase extends DrupalWebTestCase { 'description' => t('Test Time field'), 'type' => 'time', 'not null' => FALSE, - ), + ), ), ); $ret = array();