cms/drupal/includes/database/prefetch.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Database interface code for engines that need complete control over their
       
     6  * result sets. For example, SQLite will prefix some column names by the name
       
     7  * of the table. We post-process the data, by renaming the column names
       
     8  * using the same convention as MySQL and PostgreSQL.
       
     9  */
       
    10 
       
    11 /**
       
    12  * @addtogroup database
       
    13  * @{
       
    14  */
       
    15 
       
    16 /**
       
    17  * An implementation of DatabaseStatementInterface that prefetches all data.
       
    18  *
       
    19  * This class behaves very similar to a PDOStatement but as it always fetches
       
    20  * every row it is possible to manipulate those results.
       
    21  */
       
    22 class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
       
    23 
       
    24   /**
       
    25    * The query string.
       
    26    *
       
    27    * @var string
       
    28    */
       
    29   protected $queryString;
       
    30 
       
    31   /**
       
    32    * Driver-specific options. Can be used by child classes.
       
    33    *
       
    34    * @var Array
       
    35    */
       
    36   protected $driverOptions;
       
    37 
       
    38   /**
       
    39    * Reference to the database connection object for this statement.
       
    40    *
       
    41    * The name $dbh is inherited from PDOStatement.
       
    42    *
       
    43    * @var DatabaseConnection
       
    44    */
       
    45   public $dbh;
       
    46 
       
    47   /**
       
    48    * Main data store.
       
    49    *
       
    50    * @var Array
       
    51    */
       
    52   protected $data = array();
       
    53 
       
    54   /**
       
    55    * The current row, retrieved in PDO::FETCH_ASSOC format.
       
    56    *
       
    57    * @var Array
       
    58    */
       
    59   protected $currentRow = NULL;
       
    60 
       
    61   /**
       
    62    * The key of the current row.
       
    63    *
       
    64    * @var int
       
    65    */
       
    66   protected $currentKey = NULL;
       
    67 
       
    68   /**
       
    69    * The list of column names in this result set.
       
    70    *
       
    71    * @var Array
       
    72    */
       
    73   protected $columnNames = NULL;
       
    74 
       
    75   /**
       
    76    * The number of rows affected by the last query.
       
    77    *
       
    78    * @var int
       
    79    */
       
    80   protected $rowCount = NULL;
       
    81 
       
    82   /**
       
    83    * The number of rows in this result set.
       
    84    *
       
    85    * @var int
       
    86    */
       
    87   protected $resultRowCount = 0;
       
    88 
       
    89   /**
       
    90    * Holds the current fetch style (which will be used by the next fetch).
       
    91    * @see PDOStatement::fetch()
       
    92    *
       
    93    * @var int
       
    94    */
       
    95   protected $fetchStyle = PDO::FETCH_OBJ;
       
    96 
       
    97   /**
       
    98    * Holds supplementary current fetch options (which will be used by the next fetch).
       
    99    *
       
   100    * @var Array
       
   101    */
       
   102   protected $fetchOptions = array(
       
   103     'class' => 'stdClass',
       
   104     'constructor_args' => array(),
       
   105     'object' => NULL,
       
   106     'column' => 0,
       
   107   );
       
   108 
       
   109   /**
       
   110    * Holds the default fetch style.
       
   111    *
       
   112    * @var int
       
   113    */
       
   114   protected $defaultFetchStyle = PDO::FETCH_OBJ;
       
   115 
       
   116   /**
       
   117    * Holds supplementary default fetch options.
       
   118    *
       
   119    * @var Array
       
   120    */
       
   121   protected $defaultFetchOptions = array(
       
   122     'class' => 'stdClass',
       
   123     'constructor_args' => array(),
       
   124     'object' => NULL,
       
   125     'column' => 0,
       
   126   );
       
   127 
       
   128   public function __construct(DatabaseConnection $connection, $query, array $driver_options = array()) {
       
   129     $this->dbh = $connection;
       
   130     $this->queryString = $query;
       
   131     $this->driverOptions = $driver_options;
       
   132   }
       
   133 
       
   134   /**
       
   135    * Executes a prepared statement.
       
   136    *
       
   137    * @param $args
       
   138    *   An array of values with as many elements as there are bound parameters in the SQL statement being executed.
       
   139    * @param $options
       
   140    *   An array of options for this query.
       
   141    * @return
       
   142    *   TRUE on success, or FALSE on failure.
       
   143    */
       
   144   public function execute($args = array(), $options = array()) {
       
   145     if (isset($options['fetch'])) {
       
   146       if (is_string($options['fetch'])) {
       
   147         // Default to an object. Note: db fields will be added to the object
       
   148         // before the constructor is run. If you need to assign fields after
       
   149         // the constructor is run, see http://drupal.org/node/315092.
       
   150         $this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']);
       
   151       }
       
   152       else {
       
   153         $this->setFetchMode($options['fetch']);
       
   154       }
       
   155     }
       
   156 
       
   157     $logger = $this->dbh->getLogger();
       
   158     if (!empty($logger)) {
       
   159       $query_start = microtime(TRUE);
       
   160     }
       
   161 
       
   162     // Prepare the query.
       
   163     $statement = $this->getStatement($this->queryString, $args);
       
   164     if (!$statement) {
       
   165       $this->throwPDOException();
       
   166     }
       
   167 
       
   168     $return = $statement->execute($args);
       
   169     if (!$return) {
       
   170       $this->throwPDOException();
       
   171     }
       
   172 
       
   173     // Fetch all the data from the reply, in order to release any lock
       
   174     // as soon as possible.
       
   175     $this->rowCount = $statement->rowCount();
       
   176     $this->data = $statement->fetchAll(PDO::FETCH_ASSOC);
       
   177     // Destroy the statement as soon as possible. See
       
   178     // DatabaseConnection_sqlite::PDOPrepare() for explanation.
       
   179     unset($statement);
       
   180 
       
   181     $this->resultRowCount = count($this->data);
       
   182 
       
   183     if ($this->resultRowCount) {
       
   184       $this->columnNames = array_keys($this->data[0]);
       
   185     }
       
   186     else {
       
   187       $this->columnNames = array();
       
   188     }
       
   189 
       
   190     if (!empty($logger)) {
       
   191       $query_end = microtime(TRUE);
       
   192       $logger->log($this, $args, $query_end - $query_start);
       
   193     }
       
   194 
       
   195     // Initialize the first row in $this->currentRow.
       
   196     $this->next();
       
   197 
       
   198     return $return;
       
   199   }
       
   200 
       
   201   /**
       
   202    * Throw a PDO Exception based on the last PDO error.
       
   203    */
       
   204   protected function throwPDOException() {
       
   205     $error_info = $this->dbh->errorInfo();
       
   206     // We rebuild a message formatted in the same way as PDO.
       
   207     $exception = new PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
       
   208     $exception->errorInfo = $error_info;
       
   209     throw $exception;
       
   210   }
       
   211 
       
   212   /**
       
   213    * Grab a PDOStatement object from a given query and its arguments.
       
   214    *
       
   215    * Some drivers (including SQLite) will need to perform some preparation
       
   216    * themselves to get the statement right.
       
   217    *
       
   218    * @param $query
       
   219    *   The query.
       
   220    * @param array $args
       
   221    *   An array of arguments.
       
   222    * @return PDOStatement
       
   223    *   A PDOStatement object.
       
   224    */
       
   225   protected function getStatement($query, &$args = array()) {
       
   226     return $this->dbh->prepare($query);
       
   227   }
       
   228 
       
   229   /**
       
   230    * Return the object's SQL query string.
       
   231    */
       
   232   public function getQueryString() {
       
   233     return $this->queryString;
       
   234   }
       
   235 
       
   236   /**
       
   237    * @see PDOStatement::setFetchMode()
       
   238    */
       
   239   public function setFetchMode($fetchStyle, $a2 = NULL, $a3 = NULL) {
       
   240     $this->defaultFetchStyle = $fetchStyle;
       
   241     switch ($fetchStyle) {
       
   242       case PDO::FETCH_CLASS:
       
   243         $this->defaultFetchOptions['class'] = $a2;
       
   244         if ($a3) {
       
   245           $this->defaultFetchOptions['constructor_args'] = $a3;
       
   246         }
       
   247         break;
       
   248       case PDO::FETCH_COLUMN:
       
   249         $this->defaultFetchOptions['column'] = $a2;
       
   250         break;
       
   251       case PDO::FETCH_INTO:
       
   252         $this->defaultFetchOptions['object'] = $a2;
       
   253         break;
       
   254     }
       
   255 
       
   256     // Set the values for the next fetch.
       
   257     $this->fetchStyle = $this->defaultFetchStyle;
       
   258     $this->fetchOptions = $this->defaultFetchOptions;
       
   259   }
       
   260 
       
   261   /**
       
   262    * Return the current row formatted according to the current fetch style.
       
   263    *
       
   264    * This is the core method of this class. It grabs the value at the current
       
   265    * array position in $this->data and format it according to $this->fetchStyle
       
   266    * and $this->fetchMode.
       
   267    *
       
   268    * @return
       
   269    *  The current row formatted as requested.
       
   270    */
       
   271   public function current() {
       
   272     if (isset($this->currentRow)) {
       
   273       switch ($this->fetchStyle) {
       
   274         case PDO::FETCH_ASSOC:
       
   275           return $this->currentRow;
       
   276         case PDO::FETCH_BOTH:
       
   277           // PDO::FETCH_BOTH returns an array indexed by both the column name
       
   278           // and the column number.
       
   279           return $this->currentRow + array_values($this->currentRow);
       
   280         case PDO::FETCH_NUM:
       
   281           return array_values($this->currentRow);
       
   282         case PDO::FETCH_LAZY:
       
   283           // We do not do lazy as everything is fetched already. Fallback to
       
   284           // PDO::FETCH_OBJ.
       
   285         case PDO::FETCH_OBJ:
       
   286           return (object) $this->currentRow;
       
   287         case PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE:
       
   288           $class_name = array_unshift($this->currentRow);
       
   289           // Deliberate no break.
       
   290         case PDO::FETCH_CLASS:
       
   291           if (!isset($class_name)) {
       
   292             $class_name = $this->fetchOptions['class'];
       
   293           }
       
   294           if (count($this->fetchOptions['constructor_args'])) {
       
   295             $reflector = new ReflectionClass($class_name);
       
   296             $result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
       
   297           }
       
   298           else {
       
   299             $result = new $class_name();
       
   300           }
       
   301           foreach ($this->currentRow as $k => $v) {
       
   302             $result->$k = $v;
       
   303           }
       
   304           return $result;
       
   305         case PDO::FETCH_INTO:
       
   306           foreach ($this->currentRow as $k => $v) {
       
   307             $this->fetchOptions['object']->$k = $v;
       
   308           }
       
   309           return $this->fetchOptions['object'];
       
   310         case PDO::FETCH_COLUMN:
       
   311           if (isset($this->columnNames[$this->fetchOptions['column']])) {
       
   312             return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]];
       
   313           }
       
   314           else {
       
   315             return;
       
   316           }
       
   317       }
       
   318     }
       
   319   }
       
   320 
       
   321   /* Implementations of Iterator. */
       
   322 
       
   323   public function key() {
       
   324     return $this->currentKey;
       
   325   }
       
   326 
       
   327   public function rewind() {
       
   328     // Nothing to do: our DatabaseStatement can't be rewound.
       
   329   }
       
   330 
       
   331   public function next() {
       
   332     if (!empty($this->data)) {
       
   333       $this->currentRow = reset($this->data);
       
   334       $this->currentKey = key($this->data);
       
   335       unset($this->data[$this->currentKey]);
       
   336     }
       
   337     else {
       
   338       $this->currentRow = NULL;
       
   339     }
       
   340   }
       
   341 
       
   342   public function valid() {
       
   343     return isset($this->currentRow);
       
   344   }
       
   345 
       
   346   /* Implementations of DatabaseStatementInterface. */
       
   347 
       
   348   public function rowCount() {
       
   349     return $this->rowCount;
       
   350   }
       
   351 
       
   352   public function fetch($fetch_style = NULL, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
       
   353     if (isset($this->currentRow)) {
       
   354       // Set the fetch parameter.
       
   355       $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
       
   356       $this->fetchOptions = $this->defaultFetchOptions;
       
   357 
       
   358       // Grab the row in the format specified above.
       
   359       $return = $this->current();
       
   360       // Advance the cursor.
       
   361       $this->next();
       
   362 
       
   363       // Reset the fetch parameters to the value stored using setFetchMode().
       
   364       $this->fetchStyle = $this->defaultFetchStyle;
       
   365       $this->fetchOptions = $this->defaultFetchOptions;
       
   366       return $return;
       
   367     }
       
   368     else {
       
   369       return FALSE;
       
   370     }
       
   371   }
       
   372 
       
   373   public function fetchColumn($index = 0) {
       
   374     if (isset($this->currentRow) && isset($this->columnNames[$index])) {
       
   375       // We grab the value directly from $this->data, and format it.
       
   376       $return = $this->currentRow[$this->columnNames[$index]];
       
   377       $this->next();
       
   378       return $return;
       
   379     }
       
   380     else {
       
   381       return FALSE;
       
   382     }
       
   383   }
       
   384 
       
   385   public function fetchField($index = 0) {
       
   386     return $this->fetchColumn($index);
       
   387   }
       
   388 
       
   389   public function fetchObject($class_name = NULL, $constructor_args = array()) {
       
   390     if (isset($this->currentRow)) {
       
   391       if (!isset($class_name)) {
       
   392         // Directly cast to an object to avoid a function call.
       
   393         $result = (object) $this->currentRow;
       
   394       }
       
   395       else {
       
   396         $this->fetchStyle = PDO::FETCH_CLASS;
       
   397         $this->fetchOptions = array('constructor_args' => $constructor_args);
       
   398         // Grab the row in the format specified above.
       
   399         $result = $this->current();
       
   400         // Reset the fetch parameters to the value stored using setFetchMode().
       
   401         $this->fetchStyle = $this->defaultFetchStyle;
       
   402         $this->fetchOptions = $this->defaultFetchOptions;
       
   403       }
       
   404 
       
   405       $this->next();
       
   406 
       
   407       return $result;
       
   408     }
       
   409     else {
       
   410       return FALSE;
       
   411     }
       
   412   }
       
   413 
       
   414   public function fetchAssoc() {
       
   415     if (isset($this->currentRow)) {
       
   416       $result = $this->currentRow;
       
   417       $this->next();
       
   418       return $result;
       
   419     }
       
   420     else {
       
   421       return FALSE;
       
   422     }
       
   423   }
       
   424 
       
   425   public function fetchAll($fetch_style = NULL, $fetch_column = NULL, $constructor_args = NULL) {
       
   426     $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
       
   427     $this->fetchOptions = $this->defaultFetchOptions;
       
   428     if (isset($fetch_column)) {
       
   429       $this->fetchOptions['column'] = $fetch_column;
       
   430     }
       
   431     if (isset($constructor_args)) {
       
   432       $this->fetchOptions['constructor_args'] = $constructor_args;
       
   433     }
       
   434 
       
   435     $result = array();
       
   436     // Traverse the array as PHP would have done.
       
   437     while (isset($this->currentRow)) {
       
   438       // Grab the row in the format specified above.
       
   439       $result[] = $this->current();
       
   440       $this->next();
       
   441     }
       
   442 
       
   443     // Reset the fetch parameters to the value stored using setFetchMode().
       
   444     $this->fetchStyle = $this->defaultFetchStyle;
       
   445     $this->fetchOptions = $this->defaultFetchOptions;
       
   446     return $result;
       
   447   }
       
   448 
       
   449   public function fetchCol($index = 0) {
       
   450     if (isset($this->columnNames[$index])) {
       
   451       $column = $this->columnNames[$index];
       
   452       $result = array();
       
   453       // Traverse the array as PHP would have done.
       
   454       while (isset($this->currentRow)) {
       
   455         $result[] = $this->currentRow[$this->columnNames[$index]];
       
   456         $this->next();
       
   457       }
       
   458       return $result;
       
   459     }
       
   460     else {
       
   461       return array();
       
   462     }
       
   463   }
       
   464 
       
   465   public function fetchAllKeyed($key_index = 0, $value_index = 1) {
       
   466     if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index]))
       
   467       return array();
       
   468 
       
   469     $key = $this->columnNames[$key_index];
       
   470     $value = $this->columnNames[$value_index];
       
   471 
       
   472     $result = array();
       
   473     // Traverse the array as PHP would have done.
       
   474     while (isset($this->currentRow)) {
       
   475       $result[$this->currentRow[$key]] = $this->currentRow[$value];
       
   476       $this->next();
       
   477     }
       
   478     return $result;
       
   479   }
       
   480 
       
   481   public function fetchAllAssoc($key, $fetch_style = NULL) {
       
   482     $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
       
   483     $this->fetchOptions = $this->defaultFetchOptions;
       
   484 
       
   485     $result = array();
       
   486     // Traverse the array as PHP would have done.
       
   487     while (isset($this->currentRow)) {
       
   488       // Grab the row in its raw PDO::FETCH_ASSOC format.
       
   489       $row = $this->currentRow;
       
   490       // Grab the row in the format specified above.
       
   491       $result_row = $this->current();
       
   492       $result[$this->currentRow[$key]] = $result_row;
       
   493       $this->next();
       
   494     }
       
   495 
       
   496     // Reset the fetch parameters to the value stored using setFetchMode().
       
   497     $this->fetchStyle = $this->defaultFetchStyle;
       
   498     $this->fetchOptions = $this->defaultFetchOptions;
       
   499     return $result;
       
   500   }
       
   501 
       
   502 }
       
   503 
       
   504 /**
       
   505  * @} End of "addtogroup database".
       
   506  */
       
   507