3 namespace Drupal\Core\Database;
6 * An implementation of StatementInterface that prefetches all data.
8 * This class behaves very similar to a \PDOStatement but as it always fetches
9 * every row it is possible to manipulate those results.
11 class StatementPrefetch implements \Iterator, StatementInterface {
18 protected $queryString;
21 * Driver-specific options. Can be used by child classes.
25 protected $driverOptions;
28 * Reference to the Drupal database connection object for this statement.
30 * @var \Drupal\Core\Database\Connection
35 * Reference to the PDO connection object for this statement.
39 protected $pdoConnection;
49 * The current row, retrieved in \PDO::FETCH_ASSOC format.
53 protected $currentRow = NULL;
56 * The key of the current row.
60 protected $currentKey = NULL;
63 * The list of column names in this result set.
67 protected $columnNames = NULL;
70 * The number of rows affected by the last query.
74 protected $rowCount = NULL;
77 * The number of rows in this result set.
81 protected $resultRowCount = 0;
84 * Holds the current fetch style (which will be used by the next fetch).
85 * @see \PDOStatement::fetch()
89 protected $fetchStyle = \PDO::FETCH_OBJ;
92 * Holds supplementary current fetch options (which will be used by the next fetch).
96 protected $fetchOptions = [
97 'class' => 'stdClass',
98 'constructor_args' => [],
104 * Holds the default fetch style.
108 protected $defaultFetchStyle = \PDO::FETCH_OBJ;
111 * Holds supplementary default fetch options.
115 protected $defaultFetchOptions = [
116 'class' => 'stdClass',
117 'constructor_args' => [],
123 * Is rowCount() execution allowed.
127 public $allowRowCount = FALSE;
129 public function __construct(\PDO $pdo_connection, Connection $connection, $query, array $driver_options = []) {
130 $this->pdoConnection = $pdo_connection;
131 $this->dbh = $connection;
132 $this->queryString = $query;
133 $this->driverOptions = $driver_options;
139 public function execute($args = [], $options = []) {
140 if (isset($options['fetch'])) {
141 if (is_string($options['fetch'])) {
142 // Default to an object. Note: db fields will be added to the object
143 // before the constructor is run. If you need to assign fields after
144 // the constructor is run. See https://www.drupal.org/node/315092.
145 $this->setFetchMode(\PDO::FETCH_CLASS, $options['fetch']);
148 $this->setFetchMode($options['fetch']);
152 $logger = $this->dbh->getLogger();
153 if (!empty($logger)) {
154 $query_start = microtime(TRUE);
157 // Prepare the query.
158 $statement = $this->getStatement($this->queryString, $args);
160 $this->throwPDOException();
163 $return = $statement->execute($args);
165 $this->throwPDOException();
168 if ($options['return'] == Database::RETURN_AFFECTED) {
169 $this->rowCount = $statement->rowCount();
171 // Fetch all the data from the reply, in order to release any lock
172 // as soon as possible.
173 $this->data = $statement->fetchAll(\PDO::FETCH_ASSOC);
174 // Destroy the statement as soon as possible. See the documentation of
175 // \Drupal\Core\Database\Driver\sqlite\Statement for an explanation.
178 $this->resultRowCount = count($this->data);
180 if ($this->resultRowCount) {
181 $this->columnNames = array_keys($this->data[0]);
184 $this->columnNames = [];
187 if (!empty($logger)) {
188 $query_end = microtime(TRUE);
189 $logger->log($this, $args, $query_end - $query_start);
192 // Initialize the first row in $this->currentRow.
199 * Throw a PDO Exception based on the last PDO error.
201 protected function throwPDOException() {
202 $error_info = $this->dbh->errorInfo();
203 // We rebuild a message formatted in the same way as PDO.
204 $exception = new \PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
205 $exception->errorInfo = $error_info;
210 * Grab a PDOStatement object from a given query and its arguments.
212 * Some drivers (including SQLite) will need to perform some preparation
213 * themselves to get the statement right.
218 * An array of arguments.
219 * @return \PDOStatement
220 * A PDOStatement object.
222 protected function getStatement($query, &$args = []) {
223 return $this->dbh->prepare($query);
229 public function getQueryString() {
230 return $this->queryString;
236 public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
237 $this->defaultFetchStyle = $mode;
239 case \PDO::FETCH_CLASS:
240 $this->defaultFetchOptions['class'] = $a1;
242 $this->defaultFetchOptions['constructor_args'] = $a2;
245 case \PDO::FETCH_COLUMN:
246 $this->defaultFetchOptions['column'] = $a1;
248 case \PDO::FETCH_INTO:
249 $this->defaultFetchOptions['object'] = $a1;
253 // Set the values for the next fetch.
254 $this->fetchStyle = $this->defaultFetchStyle;
255 $this->fetchOptions = $this->defaultFetchOptions;
259 * Return the current row formatted according to the current fetch style.
261 * This is the core method of this class. It grabs the value at the current
262 * array position in $this->data and format it according to $this->fetchStyle
263 * and $this->fetchMode.
266 * The current row formatted as requested.
268 public function current() {
269 if (isset($this->currentRow)) {
270 switch ($this->fetchStyle) {
271 case \PDO::FETCH_ASSOC:
272 return $this->currentRow;
273 case \PDO::FETCH_BOTH:
274 // \PDO::FETCH_BOTH returns an array indexed by both the column name
275 // and the column number.
276 return $this->currentRow + array_values($this->currentRow);
277 case \PDO::FETCH_NUM:
278 return array_values($this->currentRow);
279 case \PDO::FETCH_LAZY:
280 // We do not do lazy as everything is fetched already. Fallback to
282 case \PDO::FETCH_OBJ:
283 return (object) $this->currentRow;
284 case \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE:
285 $class_name = array_unshift($this->currentRow);
286 // Deliberate no break.
287 case \PDO::FETCH_CLASS:
288 if (!isset($class_name)) {
289 $class_name = $this->fetchOptions['class'];
291 if (count($this->fetchOptions['constructor_args'])) {
292 $reflector = new \ReflectionClass($class_name);
293 $result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
296 $result = new $class_name();
298 foreach ($this->currentRow as $k => $v) {
302 case \PDO::FETCH_INTO:
303 foreach ($this->currentRow as $k => $v) {
304 $this->fetchOptions['object']->$k = $v;
306 return $this->fetchOptions['object'];
307 case \PDO::FETCH_COLUMN:
308 if (isset($this->columnNames[$this->fetchOptions['column']])) {
309 return $this->currentRow[$this->columnNames[$this->fetchOptions['column']]];
321 public function key() {
322 return $this->currentKey;
328 public function rewind() {
329 // Nothing to do: our DatabaseStatement can't be rewound.
335 public function next() {
336 if (!empty($this->data)) {
337 $this->currentRow = reset($this->data);
338 $this->currentKey = key($this->data);
339 unset($this->data[$this->currentKey]);
342 $this->currentRow = NULL;
349 public function valid() {
350 return isset($this->currentRow);
356 public function rowCount() {
357 // SELECT query should not use the method.
358 if ($this->allowRowCount) {
359 return $this->rowCount;
362 throw new RowCountException();
369 public function fetch($fetch_style = NULL, $cursor_orientation = \PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
370 if (isset($this->currentRow)) {
371 // Set the fetch parameter.
372 $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
373 $this->fetchOptions = $this->defaultFetchOptions;
375 // Grab the row in the format specified above.
376 $return = $this->current();
377 // Advance the cursor.
380 // Reset the fetch parameters to the value stored using setFetchMode().
381 $this->fetchStyle = $this->defaultFetchStyle;
382 $this->fetchOptions = $this->defaultFetchOptions;
390 public function fetchColumn($index = 0) {
391 if (isset($this->currentRow) && isset($this->columnNames[$index])) {
392 // We grab the value directly from $this->data, and format it.
393 $return = $this->currentRow[$this->columnNames[$index]];
405 public function fetchField($index = 0) {
406 return $this->fetchColumn($index);
412 public function fetchObject($class_name = NULL, $constructor_args = []) {
413 if (isset($this->currentRow)) {
414 if (!isset($class_name)) {
415 // Directly cast to an object to avoid a function call.
416 $result = (object) $this->currentRow;
419 $this->fetchStyle = \PDO::FETCH_CLASS;
420 $this->fetchOptions = ['constructor_args' => $constructor_args];
421 // Grab the row in the format specified above.
422 $result = $this->current();
423 // Reset the fetch parameters to the value stored using setFetchMode().
424 $this->fetchStyle = $this->defaultFetchStyle;
425 $this->fetchOptions = $this->defaultFetchOptions;
440 public function fetchAssoc() {
441 if (isset($this->currentRow)) {
442 $result = $this->currentRow;
454 public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) {
455 $this->fetchStyle = isset($mode) ? $mode : $this->defaultFetchStyle;
456 $this->fetchOptions = $this->defaultFetchOptions;
457 if (isset($column_index)) {
458 $this->fetchOptions['column'] = $column_index;
460 if (isset($constructor_arguments)) {
461 $this->fetchOptions['constructor_args'] = $constructor_arguments;
465 // Traverse the array as PHP would have done.
466 while (isset($this->currentRow)) {
467 // Grab the row in the format specified above.
468 $result[] = $this->current();
472 // Reset the fetch parameters to the value stored using setFetchMode().
473 $this->fetchStyle = $this->defaultFetchStyle;
474 $this->fetchOptions = $this->defaultFetchOptions;
481 public function fetchCol($index = 0) {
482 if (isset($this->columnNames[$index])) {
484 // Traverse the array as PHP would have done.
485 while (isset($this->currentRow)) {
486 $result[] = $this->currentRow[$this->columnNames[$index]];
499 public function fetchAllKeyed($key_index = 0, $value_index = 1) {
500 if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index]))
503 $key = $this->columnNames[$key_index];
504 $value = $this->columnNames[$value_index];
507 // Traverse the array as PHP would have done.
508 while (isset($this->currentRow)) {
509 $result[$this->currentRow[$key]] = $this->currentRow[$value];
518 public function fetchAllAssoc($key, $fetch_style = NULL) {
519 $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
520 $this->fetchOptions = $this->defaultFetchOptions;
523 // Traverse the array as PHP would have done.
524 while (isset($this->currentRow)) {
525 // Grab the row in its raw \PDO::FETCH_ASSOC format.
526 $result_row = $this->current();
527 $result[$this->currentRow[$key]] = $result_row;
531 // Reset the fetch parameters to the value stored using setFetchMode().
532 $this->fetchStyle = $this->defaultFetchStyle;
533 $this->fetchOptions = $this->defaultFetchOptions;