You are here

class DatabaseStatementPrefetch in Drupal 7

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.

Hierarchy

Expanded class hierarchy of DatabaseStatementPrefetch

Related topics

File

includes/database/prefetch.inc, line 22
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…

View source
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_unshift($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;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DatabaseStatementPrefetch::$columnNames protected property The list of column names in this result set.
DatabaseStatementPrefetch::$currentKey protected property The key of the current row.
DatabaseStatementPrefetch::$currentRow protected property The current row, retrieved in PDO::FETCH_ASSOC format.
DatabaseStatementPrefetch::$data protected property Main data store.
DatabaseStatementPrefetch::$dbh public property Reference to the database connection object for this statement.
DatabaseStatementPrefetch::$defaultFetchOptions protected property Holds supplementary default fetch options.
DatabaseStatementPrefetch::$defaultFetchStyle protected property Holds the default fetch style.
DatabaseStatementPrefetch::$driverOptions protected property Driver-specific options. Can be used by child classes.
DatabaseStatementPrefetch::$fetchOptions protected property Holds supplementary current fetch options (which will be used by the next fetch).
DatabaseStatementPrefetch::$fetchStyle protected property Holds the current fetch style (which will be used by the next fetch).
DatabaseStatementPrefetch::$queryString protected property The query string.
DatabaseStatementPrefetch::$resultRowCount protected property The number of rows in this result set.
DatabaseStatementPrefetch::$rowCount protected property The number of rows affected by the last query.
DatabaseStatementPrefetch::current public function Return the current row formatted according to the current fetch style.
DatabaseStatementPrefetch::execute public function Executes a prepared statement. Overrides DatabaseStatementInterface::execute 1
DatabaseStatementPrefetch::fetch public function
DatabaseStatementPrefetch::fetchAll public function
DatabaseStatementPrefetch::fetchAllAssoc public function Returns the result set as an associative array keyed by the given field. Overrides DatabaseStatementInterface::fetchAllAssoc
DatabaseStatementPrefetch::fetchAllKeyed public function Returns the entire result set as a single associative array. Overrides DatabaseStatementInterface::fetchAllKeyed
DatabaseStatementPrefetch::fetchAssoc public function Fetches the next row and returns it as an associative array. Overrides DatabaseStatementInterface::fetchAssoc
DatabaseStatementPrefetch::fetchCol public function Returns an entire single column of a result set as an indexed array. Overrides DatabaseStatementInterface::fetchCol
DatabaseStatementPrefetch::fetchColumn public function
DatabaseStatementPrefetch::fetchField public function Returns a single field from the next record of a result set. Overrides DatabaseStatementInterface::fetchField
DatabaseStatementPrefetch::fetchObject public function
DatabaseStatementPrefetch::getQueryString public function Return the object's SQL query string. Overrides DatabaseStatementInterface::getQueryString
DatabaseStatementPrefetch::getStatement protected function Grab a PDOStatement object from a given query and its arguments. 1
DatabaseStatementPrefetch::key public function
DatabaseStatementPrefetch::next public function
DatabaseStatementPrefetch::rewind public function
DatabaseStatementPrefetch::rowCount public function Returns the number of rows affected by the last SQL statement. Overrides DatabaseStatementInterface::rowCount
DatabaseStatementPrefetch::setFetchMode public function
DatabaseStatementPrefetch::throwPDOException protected function Throw a PDO Exception based on the last PDO error.
DatabaseStatementPrefetch::valid public function
DatabaseStatementPrefetch::__construct public function