? .bzr
? .bzrignore
? bar.patch
? blah.patch
? foo.patch
? sites/pg.sb
? sites/sb
? sites/default/files
? sites/default/private
Index: includes/database/select.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database/select.inc,v
retrieving revision 1.21
diff -u -p -r1.21 select.inc
--- includes/database/select.inc	29 Aug 2009 05:43:35 -0000	1.21
+++ includes/database/select.inc	2 Sep 2009 11:55:39 -0000
@@ -112,6 +112,42 @@ interface SelectQueryInterface extends Q
   public function &getTables();
 
   /**
+   * Returns a reference to the union queries for this query.
+   *
+   * Because this method returns by reference, alter hooks may edit the tables
+   * array directly to make their changes. If just adding union queries,
+   * however, the use of the union() method is preferred.
+   *
+   * Note that this method must be called by reference as well:
+   *
+   * @code
+   * $fields =& $query->getUnion();
+   * @endcode
+   *
+   * @return
+   *   A reference to the union query array structure.
+   */
+  public function &getUnion();
+
+  /**
+   * Returns a reference to the 'union all' queries for this query.
+   *
+   * Because this method returns by reference, alter hooks may edit the tables
+   * array directly to make their changes. If just adding 'union all' queries,
+   * however, the use of the unionAll() method is preferred.
+   *
+   * Note that this method must be called by reference as well:
+   *
+   * @code
+   * $fields =& $query->getUnionAll();
+   * @endcode
+   *
+   * @return
+   *   A reference to the union query array structure.
+   */
+  public function &getUnionAll();
+
+  /**
    * Compiles and returns an associative array of the arguments for this prepared statement.
    *
    * @param $queryPlaceholder
@@ -345,6 +381,47 @@ interface SelectQueryInterface extends Q
   public function range($start = NULL, $length = NULL);
 
   /**
+   * Add another Select query to UNION to this one.
+   *
+   * Union queries consist of two or more queries whose
+   * results are effectively concatenated together. Queries
+   * will be UNIONed in the order they are specified, with
+   * this object's query coming first. Duplicate columns will
+   * be discarded.
+   *
+   * Note: All queries UNIONed together must have the same
+   * field structure, in the same order. It is up to the
+   * caller to ensure that they match properly. If they do
+   * not, an SQL syntax error will result.
+   *
+   * @param $query
+   *   The query to UNION to this query.
+   * @return
+   *   The called object.
+   */
+  public function union(SelectQueryInterface $query);
+
+  /**
+   * Add another Select query to UNION ALL to this one.
+   *
+   * 'Union all' queries consist of two or more queries whose
+   * results are effectively concatenated together. Queries
+   * will be UNIONed in the order they are specified, with
+   * this object's query coming first.
+   *
+   * Note: All queries UNION ALLed together must have the same
+   * field structure, in the same order. It is up to the
+   * caller to ensure that they match properly. If they do
+   * not, an SQL syntax error will result.
+   *
+   * @param $query
+   *   The query to UNION ALL to this query.
+   * @return
+   *   The called object.
+   */
+  public function unionAll(SelectQueryInterface $query);
+
+  /**
    * Groups the result set by the specified field.
    *
    * @param $field
@@ -524,6 +601,14 @@ class SelectQueryExtender implements Sel
     return $this->query->getTables();
   }
 
+  public function &getUnion() {
+    return $this->query->getUnion();
+  }
+
+  public function &getUnionAll() {
+    return $this->query->getUnionAll();
+  }
+
   public function getArguments(QueryPlaceholderInterface $queryPlaceholder = NULL) {
     return $this->query->getArguments($queryPlaceholder);
   }
@@ -600,6 +685,16 @@ class SelectQueryExtender implements Sel
     return $this;
   }
 
+  public function union(SelectQueryInterface $query) {
+    $this->query->union($query);
+    return $this;
+  }
+
+  public function unionAll(SelectQueryInterface $query) {
+    $this->query->unionAll($query);
+    return $this;
+  }
+
   public function groupBy($field) {
     $this->query->groupBy($field);
     return $this;
@@ -766,6 +861,26 @@ class SelectQuery extends Query implemen
   protected $range;
 
   /**
+   * An array of additional Select queries.
+   *
+   * If populated, all queries in this array
+   * and this query will be UNIONed together.
+   *
+   * @var array
+   */
+  protected $union = array();
+
+  /**
+   * An array of additional Select queries.
+   *
+   * If populated, all queries in this array
+   * and this query will be UNION ALLed together.
+   *
+   * @var array
+   */
+  protected $unionAll = array();
+
+  /**
    * Indicates if preExecute() has already been called.
    * @var boolean
    */
@@ -910,6 +1025,14 @@ class SelectQuery extends Query implemen
     return $this->tables;
   }
 
+  public function &getUnion() {
+    return $this->union;
+  }
+
+  public function &getUnionAll() {
+    return $this->union;
+  }
+
   public function getArguments(QueryPlaceholderInterface $queryPlaceholder = NULL) {
     if (!isset($queryPlaceholder)) {
       $queryPlaceholder = $this;
@@ -917,6 +1040,7 @@ class SelectQuery extends Query implemen
     $this->where->compile($this->connection, $queryPlaceholder);
     $this->having->compile($this->connection, $queryPlaceholder);
     $args = $this->where->arguments() + $this->having->arguments();
+
     foreach ($this->tables as $table) {
       if ($table['arguments']) {
         $args += $table['arguments'];
@@ -926,12 +1050,22 @@ class SelectQuery extends Query implemen
         $args += $table['table']->getArguments($queryPlaceholder);
       }
     }
+
     foreach ($this->expressions as $expression) {
       if ($expression['arguments']) {
         $args += $expression['arguments'];
       }
     }
 
+    // If there are any dependent queries to UNION,
+    // incorporate their arguments recursively.
+    foreach ($this->union as $union) {
+      $args += $union->getArguments($queryPlaceholder);
+    }
+    foreach ($this->unionAll as $unionAll) {
+      $args += $unionAll->getArguments($queryPlaceholder);
+    }
+    
     return $args;
   }
 
@@ -1109,6 +1243,16 @@ class SelectQuery extends Query implemen
     return $this;
   }
 
+  public function union(SelectQueryInterface $query) {
+    $this->union[] = $query;
+    return $this;
+  }
+
+  public function unionAll(SelectQueryInterface $query) {
+    $this->unionAll[] = $query;
+    return $this;
+  }
+
   public function groupBy($field) {
     $this->group[] = $field;
     return $this;
@@ -1223,16 +1367,41 @@ class SelectQuery extends Query implemen
     }
 
     // RANGE is database specific, so we can't do it here.
+
+    // UNION and UNION ALL is a little odd, as the select queries to
+    // combine are passed into this query but syntactically
+    // they all end up on the same level.
+    if ($this->union) {
+      $unions[] = $query;
+      foreach ($this->union as $union) {
+        $unions[] = (string)$union;
+      }
+      $query = implode(' UNION DISTINCT ', $unions);
+    }
+    if ($this->unionAll) {
+      $unionAlls[] = $query;
+      foreach ($this->unionAll as $unionAll) {
+        $unionAlls[] = (string)$unionAll;
+      }
+      $query = implode(' UNION ALL ', $unionAlls);
+    }
+    
     return $query;
   }
 
   public function __clone() {
-    // On cloning, also clone the conditional objects. However, we do not
+    // On cloning, also clone the dependent objects. However, we do not
     // want to clone the database connection object as that would duplicate the
     // connection itself.
 
     $this->where = clone($this->where);
     $this->having = clone($this->having);
+    foreach ($this->union as $key => $query) {
+      $this->union[$key] = clone($query);
+    }
+    foreach ($this->unionAll as $key => $query) {
+      $this->unionAll[$key] = clone($query);
+    }
   }
 }
 
Index: modules/simpletest/tests/database_test.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.test,v
retrieving revision 1.64
diff -u -p -r1.64 database_test.test
--- modules/simpletest/tests/database_test.test	22 Aug 2009 19:43:12 -0000	1.64
+++ modules/simpletest/tests/database_test.test	2 Sep 2009 11:55:39 -0000
@@ -1352,6 +1352,53 @@ class DatabaseSelectTestCase extends Dat
     $this->assertEqual($names[0], 'Gonzo', t('Correct record returned for NOT NULL age.'));
     $this->assertEqual($names[1], 'Kermit', t('Correct record returned for NOT NULL age.'));
   }
+
+  /**
+   * Test that we can UNION multiple Select queries together.
+   */
+  function testUnion() {
+    $query_1 = db_select('test', 't')
+      ->fields('t', array('name'))
+      ->condition('age', array(27, 28), 'IN');
+
+    $query_2 = db_select('test', 't')
+      ->fields('t', array('name'))
+      ->condition('age', 28);
+
+    $query_1->union($query_2);
+
+    $names = $query_1->execute()->fetchCol();
+    
+    // Ensure we only get 2 records.
+    $this->assertEqual(count($names), 2, t('UNION correctly discarded duplicates.'));
+
+    $this->assertEqual($names[0], 'George', t('First query returned correct name.'));
+    $this->assertEqual($names[1], 'Ringo', t('Second query returned correct name.'));
+  }
+  
+  /**
+   * Test that we can UNION ALL multiple Select queries together.
+   */
+  function testUnionAll() {
+    $query_1 = db_select('test', 't')
+      ->fields('t', array('name'))
+      ->condition('age', array(27, 28), 'IN');
+      
+    $query_2 = db_select('test', 't')
+      ->fields('t', array('name'))
+      ->condition('age', 28);
+
+    $query_1->unionAll($query_2);
+
+    $names = $query_1->execute()->fetchCol();
+
+    // Ensure we get all 3 records.
+    $this->assertEqual(count($names), 3, t('UNION ALL correctly preserved duplicates.'));
+
+    $this->assertEqual($names[0], 'George', t('First query returned correct first name.'));
+    $this->assertEqual($names[1], 'Ringo', t('Second query returned correct second name.'));
+    $this->assertEqual($names[2], 'Ringo', t('Third query returned correct name.'));
+  }
 }
 
 /**
