prefetch.inc

Database interface code for engines that need complete control over their result sets. For example, SQLite will prefix some column names by the name of the table. We post-process the data, by renaming the column names using the same convention as MySQL and PostgreSQL.

File

includes/database/prefetch.inc

View source
<?php


/**
 * @file
 * Database interface code for engines that need complete control over their
 * result sets. For example, SQLite will prefix some column names by the name
 * of the table. We post-process the data, by renaming the column names
 * using the same convention as MySQL and PostgreSQL.
 */

/**
 * @addtogroup database
 * @{
 */

/**
 * An implementation of DatabaseStatementInterface that prefetches all data.
 *
 * This class behaves very similar to a PDOStatement but as it always fetches
 * every row it is possible to manipulate those results.
 */
class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
    
    /**
     * The query string.
     *
     * @var string
     */
    protected $queryString;
    
    /**
     * Driver-specific options. Can be used by child classes.
     *
     * @var Array
     */
    protected $driverOptions;
    
    /**
     * Reference to the database connection object for this statement.
     *
     * The name $dbh is inherited from PDOStatement.
     *
     * @var DatabaseConnection
     */
    public $dbh;
    
    /**
     * Main data store.
     *
     * @var Array
     */
    protected $data = array();
    
    /**
     * The current row, retrieved in PDO::FETCH_ASSOC format.
     *
     * @var Array
     */
    protected $currentRow = NULL;
    
    /**
     * The key of the current row.
     *
     * @var int
     */
    protected $currentKey = NULL;
    
    /**
     * The list of column names in this result set.
     *
     * @var Array
     */
    protected $columnNames = NULL;
    
    /**
     * The number of rows affected by the last query.
     *
     * @var int
     */
    protected $rowCount = NULL;
    
    /**
     * The number of rows in this result set.
     *
     * @var int
     */
    protected $resultRowCount = 0;
    
    /**
     * Holds the current fetch style (which will be used by the next fetch).
     * @see PDOStatement::fetch()
     *
     * @var int
     */
    protected $fetchStyle = PDO::FETCH_OBJ;
    
    /**
     * Holds supplementary current fetch options (which will be used by the next fetch).
     *
     * @var Array
     */
    protected $fetchOptions = array(
        'class' => 'stdClass',
        'constructor_args' => array(),
        'object' => NULL,
        'column' => 0,
    );
    
    /**
     * Holds the default fetch style.
     *
     * @var int
     */
    protected $defaultFetchStyle = PDO::FETCH_OBJ;
    
    /**
     * Holds supplementary default fetch options.
     *
     * @var Array
     */
    protected $defaultFetchOptions = array(
        'class' => 'stdClass',
        'constructor_args' => array(),
        'object' => NULL,
        'column' => 0,
    );
    public function __construct(DatabaseConnection $connection, $query, array $driver_options = array()) {
        $this->dbh = $connection;
        $this->queryString = $query;
        $this->driverOptions = $driver_options;
    }
    
    /**
     * 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 = array(), $options = array()) {
        if (isset($options['fetch'])) {
            if (is_string($options['fetch'])) {
                // Default to an object. Note: db fields will be added to the object
                // before the constructor is run. If you need to assign fields after
                // the constructor is run, see http://drupal.org/node/315092.
                $this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']);
            }
            else {
                $this->setFetchMode($options['fetch']);
            }
        }
        $logger = $this->dbh
            ->getLogger();
        if (!empty($logger)) {
            $query_start = microtime(TRUE);
        }
        // Prepare the query.
        $statement = $this->getStatement($this->queryString, $args);
        if (!$statement) {
            $this->throwPDOException();
        }
        $return = $statement->execute($args);
        if (!$return) {
            $this->throwPDOException();
        }
        // Fetch all the data from the reply, in order to release any lock
        // as soon as possible.
        $this->rowCount = $statement->rowCount();
        $this->data = $statement->fetchAll(PDO::FETCH_ASSOC);
        // Destroy the statement as soon as possible. See
        // DatabaseConnection_sqlite::PDOPrepare() for explanation.
        unset($statement);
        $this->resultRowCount = count($this->data);
        if ($this->resultRowCount) {
            $this->columnNames = array_keys($this->data[0]);
        }
        else {
            $this->columnNames = array();
        }
        if (!empty($logger)) {
            $query_end = microtime(TRUE);
            $logger->log($this, $args, $query_end - $query_start);
        }
        // Initialize the first row in $this->currentRow.
        $this->next();
        return $return;
    }
    
    /**
     * Throw a PDO Exception based on the last PDO error.
     */
    protected function throwPDOException() {
        $error_info = $this->dbh
            ->errorInfo();
        // We rebuild a message formatted in the same way as PDO.
        $exception = new PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
        $exception->errorInfo = $error_info;
        throw $exception;
    }
    
    /**
     * Grab a PDOStatement object from a given query and its arguments.
     *
     * Some drivers (including SQLite) will need to perform some preparation
     * themselves to get the statement right.
     *
     * @param $query
     *   The query.
     * @param array $args
     *   An array of arguments.
     * @return PDOStatement
     *   A PDOStatement object.
     */
    protected function getStatement($query, &$args = array()) {
        return $this->dbh
            ->prepare($query);
    }
    
    /**
     * Return the object's SQL query string.
     */
    public function getQueryString() {
        return $this->queryString;
    }
    
    /**
     * @see PDOStatement::setFetchMode()
     */
    public function setFetchMode($fetchStyle, $a2 = NULL, $a3 = NULL) {
        $this->defaultFetchStyle = $fetchStyle;
        switch ($fetchStyle) {
            case PDO::FETCH_CLASS:
                $this->defaultFetchOptions['class'] = $a2;
                if ($a3) {
                    $this->defaultFetchOptions['constructor_args'] = $a3;
                }
                break;
            case PDO::FETCH_COLUMN:
                $this->defaultFetchOptions['column'] = $a2;
                break;
            case PDO::FETCH_INTO:
                $this->defaultFetchOptions['object'] = $a2;
                break;
        }
        // Set the values for the next fetch.
        $this->fetchStyle = $this->defaultFetchStyle;
        $this->fetchOptions = $this->defaultFetchOptions;
    }
    
    /**
     * Return the current row formatted according to the current fetch style.
     *
     * This is the core method of this class. It grabs the value at the current
     * array position in $this->data and format it according to $this->fetchStyle
     * and $this->fetchMode.
     *
     * @return
     *  The current row formatted as requested.
     */
    public function current() {
        if (isset($this->currentRow)) {
            switch ($this->fetchStyle) {
                case PDO::FETCH_ASSOC:
                    return $this->currentRow;
                case PDO::FETCH_BOTH:
                    // PDO::FETCH_BOTH returns an array indexed by both the column name
                    // and the column number.
                    return $this->currentRow + array_values($this->currentRow);
                case PDO::FETCH_NUM:
                    return array_values($this->currentRow);
                case PDO::FETCH_LAZY:
                // We do not do lazy as everything is fetched already. Fallback to
                // PDO::FETCH_OBJ.
                case PDO::FETCH_OBJ:
                    return (object) $this->currentRow;
                case PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE:
                    $class_name = array_shift($this->currentRow);
                // Deliberate no break.
                case PDO::FETCH_CLASS:
                    if (!isset($class_name)) {
                        $class_name = $this->fetchOptions['class'];
                    }
                    if (count($this->fetchOptions['constructor_args'])) {
                        $reflector = new ReflectionClass($class_name);
                        $result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
                    }
                    else {
                        $result = new $class_name();
                    }
                    foreach ($this->currentRow as $k => $v) {
                        $result->{$k} = $v;
                    }
                    return $result;
                case PDO::FETCH_INTO:
                    foreach ($this->currentRow as $k => $v) {
                        $this->fetchOptions['object']->{$k} = $v;
                    }
                    return $this->fetchOptions['object'];
                case PDO::FETCH_COLUMN:
                    if (isset($this->columnNames[$this->fetchOptions['column']])) {
                        return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]];
                    }
                    else {
                        return;
                    }
            }
        }
    }
    
    /* Implementations of Iterator. */
    public function key() {
        return $this->currentKey;
    }
    public function rewind() {
        // Nothing to do: our DatabaseStatement can't be rewound.
    }
    public function next() {
        if (!empty($this->data)) {
            $this->currentRow = reset($this->data);
            $this->currentKey = key($this->data);
            unset($this->data[$this->currentKey]);
        }
        else {
            $this->currentRow = NULL;
        }
    }
    public function valid() {
        return isset($this->currentRow);
    }
    
    /* Implementations of DatabaseStatementInterface. */
    public function rowCount() {
        return $this->rowCount;
    }
    public function fetch($fetch_style = NULL, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
        if (isset($this->currentRow)) {
            // Set the fetch parameter.
            $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
            $this->fetchOptions = $this->defaultFetchOptions;
            // Grab the row in the format specified above.
            $return = $this->current();
            // Advance the cursor.
            $this->next();
            // Reset the fetch parameters to the value stored using setFetchMode().
            $this->fetchStyle = $this->defaultFetchStyle;
            $this->fetchOptions = $this->defaultFetchOptions;
            return $return;
        }
        else {
            return FALSE;
        }
    }
    public function fetchColumn($index = 0) {
        if (isset($this->currentRow) && isset($this->columnNames[$index])) {
            // We grab the value directly from $this->data, and format it.
            $return = $this->currentRow[$this->columnNames[$index]];
            $this->next();
            return $return;
        }
        else {
            return FALSE;
        }
    }
    public function fetchField($index = 0) {
        return $this->fetchColumn($index);
    }
    public function fetchObject($class_name = NULL, $constructor_args = array()) {
        if (isset($this->currentRow)) {
            if (!isset($class_name)) {
                // Directly cast to an object to avoid a function call.
                $result = (object) $this->currentRow;
            }
            else {
                $this->fetchStyle = PDO::FETCH_CLASS;
                $this->fetchOptions = array(
                    'constructor_args' => $constructor_args,
                );
                // Grab the row in the format specified above.
                $result = $this->current();
                // Reset the fetch parameters to the value stored using setFetchMode().
                $this->fetchStyle = $this->defaultFetchStyle;
                $this->fetchOptions = $this->defaultFetchOptions;
            }
            $this->next();
            return $result;
        }
        else {
            return FALSE;
        }
    }
    public function fetchAssoc() {
        if (isset($this->currentRow)) {
            $result = $this->currentRow;
            $this->next();
            return $result;
        }
        else {
            return FALSE;
        }
    }
    public function fetchAll($fetch_style = NULL, $fetch_column = NULL, $constructor_args = NULL) {
        $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
        $this->fetchOptions = $this->defaultFetchOptions;
        if (isset($fetch_column)) {
            $this->fetchOptions['column'] = $fetch_column;
        }
        if (isset($constructor_args)) {
            $this->fetchOptions['constructor_args'] = $constructor_args;
        }
        $result = array();
        // Traverse the array as PHP would have done.
        while (isset($this->currentRow)) {
            // Grab the row in the format specified above.
            $result[] = $this->current();
            $this->next();
        }
        // Reset the fetch parameters to the value stored using setFetchMode().
        $this->fetchStyle = $this->defaultFetchStyle;
        $this->fetchOptions = $this->defaultFetchOptions;
        return $result;
    }
    public function fetchCol($index = 0) {
        if (isset($this->columnNames[$index])) {
            $column = $this->columnNames[$index];
            $result = array();
            // Traverse the array as PHP would have done.
            while (isset($this->currentRow)) {
                $result[] = $this->currentRow[$this->columnNames[$index]];
                $this->next();
            }
            return $result;
        }
        else {
            return array();
        }
    }
    public function fetchAllKeyed($key_index = 0, $value_index = 1) {
        if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index])) {
            return array();
        }
        $key = $this->columnNames[$key_index];
        $value = $this->columnNames[$value_index];
        $result = array();
        // Traverse the array as PHP would have done.
        while (isset($this->currentRow)) {
            $result[$this->currentRow[$key]] = $this->currentRow[$value];
            $this->next();
        }
        return $result;
    }
    public function fetchAllAssoc($key, $fetch_style = NULL) {
        $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
        $this->fetchOptions = $this->defaultFetchOptions;
        $result = array();
        // Traverse the array as PHP would have done.
        while (isset($this->currentRow)) {
            // Grab the row in its raw PDO::FETCH_ASSOC format.
            $row = $this->currentRow;
            // Grab the row in the format specified above.
            $result_row = $this->current();
            $result[$this->currentRow[$key]] = $result_row;
            $this->next();
        }
        // Reset the fetch parameters to the value stored using setFetchMode().
        $this->fetchStyle = $this->defaultFetchStyle;
        $this->fetchOptions = $this->defaultFetchOptions;
        return $result;
    }

}

/**
 * @} End of "addtogroup database".
 */

Classes

Title Deprecated Summary
DatabaseStatementPrefetch An implementation of DatabaseStatementInterface that prefetches all data.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.