Index: includes/database/query.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database/query.inc,v
retrieving revision 1.49
diff -u -r1.49 query.inc
--- includes/database/query.inc	15 May 2010 07:04:21 -0000	1.49
+++ includes/database/query.inc	31 May 2010 01:13:21 -0000
@@ -247,6 +247,13 @@
    */
   protected $comments = array();
 
+  /**
+   * Query hints or flags that can be set on a query.
+   *
+   * @var string
+   */
+  protected $hints = '';
+
   public function __construct(DatabaseConnection $connection, $options) {
     $this->connection = $connection;
     $this->queryOptions = $options;
@@ -308,6 +315,20 @@
   public function &getComments() {
     return $this->comments;
   }
+
+  /**
+   * Sets the hints or flags on a query.
+   *
+   * Hints and flags directly affect the way in which the query is run
+   * on the database server. Their syntax is database specific, so they
+   * should be used internally in database-specific classes only.
+   *
+   * @param $hints
+   *   The hint string to be inserted into the query.
+   */
+  public function setHints($hints) {
+    $this->hints = $hints;
+  }
 }
 
 /**
Index: includes/database/mysql/query.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database/mysql/query.inc,v
retrieving revision 1.17
diff -u -r1.17 query.inc
--- includes/database/mysql/query.inc	15 May 2010 07:04:21 -0000	1.17
+++ includes/database/mysql/query.inc	31 May 2010 01:13:21 -0000
@@ -46,16 +46,19 @@
     // Create a comments string to prepend to the query.
     $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
 
+    // Create a hints string to include in the query.
+    $hints = (!empty($this->hints)) ? $this->hints . ' ' : '';
+
     // Default fields are always placed first for consistency.
     $insert_fields = array_merge($this->defaultFields, $this->insertFields);
 
     // If we're selecting from a SelectQuery, finish building the query and
     // pass it back, as any remaining options are irrelevant.
     if (!empty($this->fromQuery)) {
-      return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery;
+      return $comments . 'INSERT ' . $hints . 'INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery;
     }
 
-    $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
+    $query = $comments . 'INSERT ' . $hints . 'INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
 
     $max_placeholder = 0;
     $values = array();
@@ -148,6 +151,9 @@
     // Create a comments string to prepend to the query.
     $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
 
+    // Create a hints string to include in the query.
+    $hints = (!empty($this->hints)) ? $this->hints . ' ' : '';
+
     // Set defaults.
     if ($this->updateFields) {
       $update_fields = $this->updateFields;
@@ -168,7 +174,7 @@
 
     $insert_fields = $this->insertFields + $this->keyFields;
 
-    $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', array_keys($insert_fields)) . ') VALUES ';
+    $query = $comments . 'INSERT ' . $hints . 'INTO {' . $this->table . '} (' . implode(', ', array_keys($insert_fields)) . ') VALUES ';
 
     $max_placeholder = 0;
     $values = array();
@@ -205,6 +211,121 @@
   }
 }
 
+class SelectQuery_mysql extends SelectQuery {
+
+  /**
+   * Override this for SelectQuery_mysql to include support for query hints.
+   */
+  public function __toString() {
+    // Create a comments string to prepend to the query.
+    $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
+
+    // Create a hints string to include in the query.
+    $hints = (!empty($this->hints)) ? $this->hints . ' ' : '';
+
+    // SELECT
+    $query = $comments . 'SELECT ' . $hints;
+
+    if ($this->distinct) {
+      $query .= 'DISTINCT ';
+    }
+
+    // FIELDS and EXPRESSIONS
+    $fields = array();
+    foreach ($this->tables as $alias => $table) {
+      if (!empty($table['all_fields'])) {
+        $fields[] = $alias . '.*';
+      }
+    }
+    foreach ($this->fields as $alias => $field) {
+      // Always use the AS keyword for field aliases, as some
+      // databases require it (e.g., PostgreSQL).
+      $fields[] = (isset($field['table']) ? $field['table'] . '.' : '') . $field['field'] . ' AS ' . $field['alias'];
+    }
+    foreach ($this->expressions as $alias => $expression) {
+      $fields[] = $expression['expression'] . ' AS ' . $expression['alias'];
+    }
+    $query .= implode(', ', $fields);
+
+
+    // FROM - We presume all queries have a FROM, as any query that doesn't won't need the query builder anyway.
+    $query .= "\nFROM ";
+    foreach ($this->tables as $alias => $table) {
+      $query .= "\n";
+      if (isset($table['join type'])) {
+        $query .= $table['join type'] . ' JOIN ';
+      }
+
+      // If the table is a subquery, compile it and integrate it into this query.
+      if ($table['table'] instanceof SelectQueryInterface) {
+        // Run preparation steps on this sub-query before converting to string.
+        $subquery = $table['table'];
+        $subquery->preExecute();
+        $table_string = '(' . (string) $subquery . ')';
+      }
+      else {
+        $table_string = '{' . $this->connection->escapeTable($table['table']) . '}';
+      }
+
+      // Don't use the AS keyword for table aliases, as some
+      // databases don't support it (e.g., Oracle).
+      $query .=  $table_string . ' ' . $table['alias'];
+
+      if (!empty($table['condition'])) {
+        $query .= ' ON ' . $table['condition'];
+      }
+    }
+
+    // WHERE
+    if (count($this->where)) {
+      $this->where->compile($this->connection, $this);
+      // There is an implicit string cast on $this->condition.
+      $query .= "\nWHERE " . $this->where;
+    }
+
+    // GROUP BY
+    if ($this->group) {
+      $query .= "\nGROUP BY " . implode(', ', $this->group);
+    }
+
+    // HAVING
+    if (count($this->having)) {
+      $this->having->compile($this->connection, $this);
+      // There is an implicit string cast on $this->having.
+      $query .= "\nHAVING " . $this->having;
+    }
+
+    // ORDER BY
+    if ($this->order) {
+      $query .= "\nORDER BY ";
+      $fields = array();
+      foreach ($this->order as $field => $direction) {
+        $fields[] = $field . ' ' . $direction;
+      }
+      $query .= implode(', ', $fields);
+    }
+
+    // RANGE
+    // There is no universal SQL standard for handling range or limit clauses.
+    // Fortunately, all core-supported databases use the same range syntax.
+    // Databases that need a different syntax can override this method and
+    // do whatever alternate logic they need to.
+    if (!empty($this->range)) {
+      $query .= "\nLIMIT " . $this->range['length'] . " OFFSET " . $this->range['start'];
+    }
+
+    // UNION 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) {
+      foreach ($this->union as $union) {
+        $query .= ' ' . $union['type'] . ' ' . (string) $union['query'];
+      }
+    }
+
+    return $query;
+  }
+}
+
 /**
  * @} End of "ingroup database".
  */
Index: includes/database/pgsql/query.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database/pgsql/query.inc,v
retrieving revision 1.18
diff -u -r1.18 query.inc
--- includes/database/pgsql/query.inc	15 May 2010 07:04:21 -0000	1.18
+++ includes/database/pgsql/query.inc	31 May 2010 01:13:21 -0000
@@ -74,16 +74,19 @@
     // Create a comments string to prepend to the query.
     $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
 
+    // Create a hints string to include in the query.
+    $hints = (!empty($this->hints)) ? . '/*+ ' $this->hints . ' */ ' : '';
+
     // Default fields are always placed first for consistency.
     $insert_fields = array_merge($this->defaultFields, $this->insertFields);
 
     // If we're selecting from a SelectQuery, finish building the query and
     // pass it back, as any remaining options are irrelevant.
     if (!empty($this->fromQuery)) {
-      return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery;
+      return $comments . 'INSERT ' . $hints . ' INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery;
     }
 
-    $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
+    $query = $comments . 'INSERT ' . $hints . ' INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
 
     $max_placeholder = 0;
     $values = array();
