annotate core/lib/Drupal/Core/Database/StatementPrefetch.php @ 19:fa3358dc1485 tip

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