commit 9cb8579c79b52058578d64349ae523327bce4e7d
Author: Damien Tournoud <damien@tournoud.net>
Date:   Sat Nov 8 19:51:28 2008 +0100

    #331213: refactor the database layer to add a DatabaseStatement.

diff --git includes/database/database.inc includes/database/database.inc
index e2f558a..fab6b3c 100644
--- includes/database/database.inc
+++ includes/database/database.inc
@@ -134,7 +134,7 @@ abstract class DatabaseConnection extends PDO {
    *
    * We only need this for the legacy db_affected_rows() call, which will be removed.
    *
-   * @var DatabaseStatement
+   * @var DatabaseStatementInterface
    * @todo Remove this variable.
    */
   public $lastStatement;
@@ -156,11 +156,17 @@ abstract class DatabaseConnection extends PDO {
   protected $logger = NULL;
 
   function __construct($dsn, $username, $password, $driver_options = array()) {
+    // Merge in defaults.
+    $driver_options += array(
+      'statement_class' => 'DatabaseStatementBase',
+    );
     // Because the other methods don't seem to work right.
     $driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
     // Call PDO::__construct and PDO::setAttribute.
     parent::__construct($dsn, $username, $password, $driver_options);
-    $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DatabaseStatement', array($this)));
+    if (!empty($driver_options['statement_class'])) {
+      $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($driver_options['statement_class'], array($this)));
+    }
   }
 
   /**
@@ -358,8 +364,8 @@ abstract class DatabaseConnection extends PDO {
    * @param $query
    *   The query to execute.  In most cases this will be a string containing
    *   an SQL query with placeholders.  An already-prepared instance of
-   *   DatabaseStatement may also be passed in order to allow calling code
-   *   to manually bind variables to a query.  If a DatabaseStatement object
+   *   DatabaseStatementInterface may also be passed in order to allow calling code
+   *   to manually bind variables to a query.  If a DatabaseStatementInterface
    *   is passed, the $args array will be ignored.
    *
    *   It is extremely rare that module code will need to pass a statement
@@ -389,7 +395,7 @@ abstract class DatabaseConnection extends PDO {
       // We allow either a pre-bound statement object or a literal string.
       // In either case, we want to end up with an executed statement object,
       // which we pass to PDOStatement::execute.
-      if ($query instanceof DatabaseStatement) {
+      if ($query instanceof DatabaseStatementInterface) {
         $stmt = $query;
         $stmt->execute(NULL, $options);
       }
@@ -416,8 +422,8 @@ abstract class DatabaseConnection extends PDO {
     catch (PDOException $e) {
       _db_check_install_needed();
       if ($options['throw_exception']) {
-        if ($query instanceof DatabaseStatement) {
-          $query_string = $stmt->queryString;
+        if ($query instanceof DatabaseStatementInterface) {
+          $query_string = $stmt->getQueryString();
         }
         else {
           $query_string = $query;
@@ -562,7 +568,7 @@ abstract class DatabaseConnection extends PDO {
    *   The sanitized table name string.
    */
   public function escapeTable($table) {
-    return preg_replace('/[^A-Za-z0-9_]+/', '', $string);
+    return preg_replace('/[^A-Za-z0-9_]+/', '', $table);
   }
 
   /**
@@ -1160,7 +1166,162 @@ class DatabaseTransaction {
 }
 
 /**
- * Prepared statement class.
+ * A prepared statement.
+ *
+ * Some methods in that class are purposely commented out. Due to a change in
+ * how PHP defines PDOStatement, we can't define a signature for those methods that
+ * will work the same way between versions older than 5.1.2 and later versions.
+ *
+ * Please refer to http://bugs.php.net/bug.php?id=42452 for more details.
+ */
+interface DatabaseStatementInterface extends Traversable {
+
+  /**
+   * Executes a prepared statement
+   *
+   * @param $args
+   *   An array of values with as many elements as there are bound parameters in the SQL statement being executed.
+   * @param $options
+   *   An array of options for this query.
+   * @return
+   *   TRUE on success, or FALSE on failure.
+   */
+  public function execute($args, $options);
+
+  /**
+   * Get the query string of that statement.
+   *
+   * @return
+   *   The query string, in its form with placeholders.
+   */
+  public function getQueryString();
+
+
+  /**
+   * Fetches the next row from a result set.
+   *
+   * @param $mode
+   *   One of the PDO::FETCH_* constants.
+   *   Default to what was specified by setFetchMode().
+   * @param $cursor_orientation
+   *   Not implemented in all database drivers, don't use.
+   * @param $cursor_offset
+   *   Not implemented in all database drivers, don't use.
+   * @return
+   *   A result, formatted according to $mode.
+   */
+  // public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL);
+
+  /**
+   * Returns an array containing all of the result set rows.
+   *
+   * @param $mode
+   *   One of the PDO::FETCH_* constants.
+   * @param $column_index
+   *   If $mode is PDO::FETCH_COLUMN, the index of the column to fetch.
+   * @param $constructor_arguments
+   *   If $mode is PDO::FETCH_CLASS, the arguments to pass to the constructor.
+   * @return
+   *   An array of results.
+   */
+  // function fetchAll($mode = NULL, $column_index = NULL, Array $constructor_arguments);
+
+  /**
+   * Returns an entire single column of a result set as an indexed array.
+   *
+   * Note that this method will run the result set to the end.
+   *
+   * @param $index
+   *   The index of the column number to fetch.
+   * @return
+   *   An indexed array.
+   */
+  public function fetchCol($index = 0);
+
+  /**
+   * Returns an entire result set as an associative array keyed by the named field.
+   *
+   * If the given key appears multiple times, later records will overwrite
+   * earlier ones.
+   *
+   * Note that this method will run the result set to the end.
+   *
+   * @param $key
+   *   The name of the field on which to index the array.
+   * @param $fetch
+   *   The fetchmode to use.  If set to   PDO::FETCH_ASSOC, PDO::FETCH_NUM, or
+   *   PDO::FETCH_BOTH the returned value with be an array of arrays.  For any
+   *   other value it will be an array of objects.
+   * @return
+   *   An associative array.
+   */
+  public function fetchAllAssoc($key, $fetch = PDO::FETCH_OBJ);
+
+  /**
+   * Returns the entire result set as a single associative array.
+   *
+   * This method is only useful for two-column result sets.  It will return
+   * an associative array where the key is one column from the result set
+   * and the value is another field.  In most cases, the default of the first two
+   * columns is appropriate.
+   *
+   * Note that this method will run the result set to the end.
+   *
+   * @param $key_index
+   *   The numeric index of the field to use as the array key.
+   * @param $value_index
+   *   The numeric index of the field to use as the array value.
+   * @return
+   *   An associative array.
+   */
+  public function fetchAllKeyed($key_index = 0, $value_index = 1);
+
+  /**
+   * Return a single field out of the current
+   *
+   * @param $index
+   *   The numeric index of the field to return.  Defaults to the first field.
+   * @return
+   *   A single field from the next record.
+   */
+  public function fetchField($index = 0);
+
+  /**
+   * Fetches the next row and returns it as an object.
+   */
+  // public function fetchObject();
+
+  /**
+   * Fetches the next row and returns it as an associative array.
+   *
+   * This method corresponds to PDOStatement::fetchObject(),
+   * but for associative arrays.  For some reason PDOStatement does
+   * not have a corresponding array helper method, so one is added.
+   *
+   * @return
+   *   An associative array.
+   */
+  public function fetchAssoc();
+
+  /**
+   * Set the default fetch mode for this statement.
+   *
+   * @param $mode
+   *   One of the PDO::FETCH_* constants.
+   * @param $a1
+   *   An option depending of the fetch mode specified by $mode:
+   *    - for PDO::FETCH_COLUMN, it is the index of the column to fetch,
+   *    - for PDO::FETCH_CLASS, it is the name of the class to create, and
+   *    - for PDO::FETCH_INTO, it is the object to add the data to.
+   * @param $a2
+   *  In case of when mode is PDO::FETCH_CLASS, the optional arguments to
+   *  pass to the constructor.
+   */
+  // public function setFetchMode($mode, $a1 = NULL, $a2 = array());
+}
+
+/**
+ * Default implementation of DatabaseStatementInterface.
  *
  * PDO allows us to extend the PDOStatement class to provide additional
  * functionality beyond that offered by default.  We do need extra
@@ -1169,7 +1330,7 @@ class DatabaseTransaction {
  *
  * @link http://us.php.net/pdostatement
  */
-class DatabaseStatement extends PDOStatement {
+class DatabaseStatementBase extends PDOStatement {
 
   /**
    * Reference to the database connection object for this statement.
@@ -1186,14 +1347,7 @@ class DatabaseStatement extends PDOStatement {
   }
 
   /**
-   * Executes a prepared statement
-   *
-   * @param $args
-   *   An array of values with as many elements as there are bound parameters in the SQL statement being executed.
-   * @param $options
-   *   An array of options for this query.
-   * @return
-   *   TRUE on success, or FALSE on failure.
+   * Implementation of DatabaseStatementInterface::execute().
    */
   public function execute($args, $options) {
     if (isset($options['fetch'])) {
@@ -1225,35 +1379,21 @@ class DatabaseStatement extends PDOStatement {
   }
 
   /**
-   * Returns an entire single column of a result set as an indexed array.
-   *
-   * Note that this method will run the result set to the end.
-   *
-   * @param $index
-   *   The index of the column number to fetch.
-   * @return
-   *   An indexed array.
+   * Implementation of DatabaseStatementInterface::getQueryString();
+   */
+  public function getQueryString() {
+    return $this->queryString;
+  }
+
+  /**
+   * Implementation of DatabaseStatementInterface::fetchCol().
    */
   public function fetchCol($index = 0) {
     return $this->fetchAll(PDO::FETCH_COLUMN, $index);
   }
 
   /**
-   * Returns an entire result set as an associative array keyed by the named field.
-   *
-   * If the given key appears multiple times, later records will overwrite
-   * earlier ones.
-   *
-   * Note that this method will run the result set to the end.
-   *
-   * @param $key
-   *   The name of the field on which to index the array.
-   * @param $fetch
-   *   The fetchmode to use.  If set to   PDO::FETCH_ASSOC, PDO::FETCH_NUM, or
-   *   PDO::FETCH_BOTH the returned value with be an array of arrays.  For any
-   *   other value it will be an array of objects.
-   * @return
-   *   An associative array.
+   * Implementation of DatabaseStatementInterface::fetchAllAssoc().
    */
   public function fetchAllAssoc($key, $fetch = PDO::FETCH_OBJ) {
     $return = array();
@@ -1272,21 +1412,7 @@ class DatabaseStatement extends PDOStatement {
   }
 
   /**
-   * Returns the entire result set as a single associative array.
-   *
-   * This method is only useful for two-column result sets.  It will return
-   * an associative array where the key is one column from the result set
-   * and the value is another field.  In most cases, the default of the first two
-   * columns is appropriate.
-   *
-   * Note that this method will run the result set to the end.
-   *
-   * @param $key_index
-   *   The numeric index of the field to use as the array key.
-   * @param $value_index
-   *   The numeric index of the field to use as the array value.
-   * @return
-   *   An associative array.
+   * Implementation of DatabaseStatementInterface::fetchAllKeyed().
    */
   public function fetchAllKeyed($key_index = 0, $value_index = 1) {
     $return = array();
@@ -1298,12 +1424,7 @@ class DatabaseStatement extends PDOStatement {
   }
 
   /**
-   * Return a single field out of the current
-   *
-   * @param $index
-   *   The numeric index of the field to return.  Defaults to the first field.
-   * @return
-   *   A single field from the next record.
+   * Implementation of DatabaseStatementInterface::fetchField().
    */
   public function fetchField($index = 0) {
     // Call PDOStatement::fetchColumn to fetch the field.
@@ -1311,14 +1432,7 @@ class DatabaseStatement extends PDOStatement {
   }
 
   /**
-   * Fetches the next row and returns it as an associative array.
-   *
-   * This method corresponds to PDOStatement::fetchObject(),
-   * but for associative arrays.  For some reason PDOStatement does
-   * not have a corresponding array helper method, so one is added.
-   *
-   * @return
-   *   An associative array.
+   * Implementation of DatabaseStatementInterface::fetchAssoc().
    */
   public function fetchAssoc() {
     // Call PDOStatement::fetch to fetch the row.
@@ -2009,22 +2123,22 @@ function _db_error_page($error = '') {
 /**
  * @ingroup database-legacy
  *
- * These functions are no longer necessary, as the DatabaseStatement object
+ * These functions are no longer necessary, as the DatabaseStatementInterface interface
  * offers this and much more functionality.  They are kept temporarily for backward
  * compatibility during conversion and should be removed as soon as possible.
  *
  * @{
  */
 
-function db_fetch_object(DatabaseStatement $statement) {
+function db_fetch_object(DatabaseStatementInterface $statement) {
   return $statement->fetch(PDO::FETCH_OBJ);
 }
 
-function db_fetch_array(DatabaseStatement $statement) {
+function db_fetch_array(DatabaseStatementInterface $statement) {
   return $statement->fetch(PDO::FETCH_ASSOC);
 }
 
-function db_result(DatabaseStatement $statement) {
+function db_result(DatabaseStatementInterface $statement) {
   return $statement->fetchField();
 }
 
diff --git includes/database/log.inc includes/database/log.inc
index 6617d81..10498bd 100644
--- includes/database/log.inc
+++ includes/database/log.inc
@@ -116,7 +116,7 @@ class DatabaseLog {
   public function log(DatabaseStatement $statement, $args, $time) {
     foreach (array_keys($this->queryLog) as $key) {
       $this->queryLog[$key][] = array(
-        'query' => $statement->queryString,
+        'query' => $statement->getQueryString(),
         'args' => $args,
         'target' => $statement->dbh->getTarget(),
         'caller' => $this->findCaller(),
diff --git includes/database/pgsql/database.inc includes/database/pgsql/database.inc
index 1aa7697..01e1d16 100644
--- includes/database/pgsql/database.inc
+++ includes/database/pgsql/database.inc
@@ -38,7 +38,7 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
     $options += $this->defaultOptions();
 
     try {
-      if ($query instanceof DatabaseStatement) {
+      if ($query instanceof DatabaseStatementInterface) {
         $stmt = $query;
         $stmt->execute(NULL, $options);
       }
@@ -63,8 +63,8 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
     catch (PDOException $e) {
       _db_check_install_needed();
       if ($options['throw_exception']) {
-        if ($query instanceof DatabaseStatement) {
-          $query_string = $stmt->queryString;
+        if ($query instanceof DatabaseStatementInterface) {
+          $query_string = $stmt->getQueryString();
         }
         else {
           $query_string = $query;
diff --git modules/simpletest/tests/database_test.test modules/simpletest/tests/database_test.test
index db43fb1..3d2aa1a 100644
--- modules/simpletest/tests/database_test.test
+++ modules/simpletest/tests/database_test.test
@@ -212,7 +212,7 @@ class DatabaseFetchTestCase extends DatabaseTestCase {
 
     $records = array();
     $result = db_query("SELECT name FROM {test} WHERE age = :age", array(':age' => 25));
-    $this->assertTrue($result instanceof DatabaseStatement, t('Result set is a Drupal statement object.'));
+    $this->assertTrue($result instanceof DatabaseStatementInterface, t('Result set is a Drupal statement object.'));
     foreach ($result as $record) {
       $records[] = $record;
       $this->assertTrue(is_object($record), t('Record is an object.'));
