comparison core/lib/Drupal/Core/Database/StatementPrefetch.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children c2387f117808
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\Core\Database;
4
5 /**
6 * An implementation of StatementInterface that prefetches all data.
7 *
8 * This class behaves very similar to a \PDOStatement but as it always fetches
9 * every row it is possible to manipulate those results.
10 */
11 class StatementPrefetch implements \Iterator, StatementInterface {
12
13 /**
14 * The query string.
15 *
16 * @var string
17 */
18 protected $queryString;
19
20 /**
21 * Driver-specific options. Can be used by child classes.
22 *
23 * @var Array
24 */
25 protected $driverOptions;
26
27 /**
28 * Reference to the Drupal database connection object for this statement.
29 *
30 * @var \Drupal\Core\Database\Connection
31 */
32 public $dbh;
33
34 /**
35 * Reference to the PDO connection object for this statement.
36 *
37 * @var \PDO
38 */
39 protected $pdoConnection;
40
41 /**
42 * Main data store.
43 *
44 * @var Array
45 */
46 protected $data = [];
47
48 /**
49 * The current row, retrieved in \PDO::FETCH_ASSOC format.
50 *
51 * @var Array
52 */
53 protected $currentRow = NULL;
54
55 /**
56 * The key of the current row.
57 *
58 * @var int
59 */
60 protected $currentKey = NULL;
61
62 /**
63 * The list of column names in this result set.
64 *
65 * @var Array
66 */
67 protected $columnNames = NULL;
68
69 /**
70 * The number of rows affected by the last query.
71 *
72 * @var int
73 */
74 protected $rowCount = NULL;
75
76 /**
77 * The number of rows in this result set.
78 *
79 * @var int
80 */
81 protected $resultRowCount = 0;
82
83 /**
84 * Holds the current fetch style (which will be used by the next fetch).
85 * @see \PDOStatement::fetch()
86 *
87 * @var int
88 */
89 protected $fetchStyle = \PDO::FETCH_OBJ;
90
91 /**
92 * Holds supplementary current fetch options (which will be used by the next fetch).
93 *
94 * @var Array
95 */
96 protected $fetchOptions = [
97 'class' => 'stdClass',
98 'constructor_args' => [],
99 'object' => NULL,
100 'column' => 0,
101 ];
102
103 /**
104 * Holds the default fetch style.
105 *
106 * @var int
107 */
108 protected $defaultFetchStyle = \PDO::FETCH_OBJ;
109
110 /**
111 * Holds supplementary default fetch options.
112 *
113 * @var Array
114 */
115 protected $defaultFetchOptions = [
116 'class' => 'stdClass',
117 'constructor_args' => [],
118 'object' => NULL,
119 'column' => 0,
120 ];
121
122 /**
123 * Is rowCount() execution allowed.
124 *
125 * @var bool
126 */
127 public $allowRowCount = FALSE;
128
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;
134 }
135
136 /**
137 * {@inheritdoc}
138 */
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']);
146 }
147 else {
148 $this->setFetchMode($options['fetch']);
149 }
150 }
151
152 $logger = $this->dbh->getLogger();
153 if (!empty($logger)) {
154 $query_start = microtime(TRUE);
155 }
156
157 // Prepare the query.
158 $statement = $this->getStatement($this->queryString, $args);
159 if (!$statement) {
160 $this->throwPDOException();
161 }
162
163 $return = $statement->execute($args);
164 if (!$return) {
165 $this->throwPDOException();
166 }
167
168 if ($options['return'] == Database::RETURN_AFFECTED) {
169 $this->rowCount = $statement->rowCount();
170 }
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.
176 unset($statement);
177
178 $this->resultRowCount = count($this->data);
179
180 if ($this->resultRowCount) {
181 $this->columnNames = array_keys($this->data[0]);
182 }
183 else {
184 $this->columnNames = [];
185 }
186
187 if (!empty($logger)) {
188 $query_end = microtime(TRUE);
189 $logger->log($this, $args, $query_end - $query_start);
190 }
191
192 // Initialize the first row in $this->currentRow.
193 $this->next();
194
195 return $return;
196 }
197
198 /**
199 * Throw a PDO Exception based on the last PDO error.
200 */
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;
206 throw $exception;
207 }
208
209 /**
210 * Grab a PDOStatement object from a given query and its arguments.
211 *
212 * Some drivers (including SQLite) will need to perform some preparation
213 * themselves to get the statement right.
214 *
215 * @param $query
216 * The query.
217 * @param array $args
218 * An array of arguments.
219 * @return \PDOStatement
220 * A PDOStatement object.
221 */
222 protected function getStatement($query, &$args = []) {
223 return $this->dbh->prepare($query);
224 }
225
226 /**
227 * {@inheritdoc}
228 */
229 public function getQueryString() {
230 return $this->queryString;
231 }
232
233 /**
234 * {@inheritdoc}
235 */
236 public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
237 $this->defaultFetchStyle = $mode;
238 switch ($mode) {
239 case \PDO::FETCH_CLASS:
240 $this->defaultFetchOptions['class'] = $a1;
241 if ($a2) {
242 $this->defaultFetchOptions['constructor_args'] = $a2;
243 }
244 break;
245 case \PDO::FETCH_COLUMN:
246 $this->defaultFetchOptions['column'] = $a1;
247 break;
248 case \PDO::FETCH_INTO:
249 $this->defaultFetchOptions['object'] = $a1;
250 break;
251 }
252
253 // Set the values for the next fetch.
254 $this->fetchStyle = $this->defaultFetchStyle;
255 $this->fetchOptions = $this->defaultFetchOptions;
256 }
257
258 /**
259 * Return the current row formatted according to the current fetch style.
260 *
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.
264 *
265 * @return mixed
266 * The current row formatted as requested.
267 */
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
281 // \PDO::FETCH_OBJ.
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'];
290 }
291 if (count($this->fetchOptions['constructor_args'])) {
292 $reflector = new \ReflectionClass($class_name);
293 $result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
294 }
295 else {
296 $result = new $class_name();
297 }
298 foreach ($this->currentRow as $k => $v) {
299 $result->$k = $v;
300 }
301 return $result;
302 case \PDO::FETCH_INTO:
303 foreach ($this->currentRow as $k => $v) {
304 $this->fetchOptions['object']->$k = $v;
305 }
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']]];
310 }
311 else {
312 return;
313 }
314 }
315 }
316 }
317
318 /**
319 * {@inheritdoc}
320 */
321 public function key() {
322 return $this->currentKey;
323 }
324
325 /**
326 * {@inheritdoc}
327 */
328 public function rewind() {
329 // Nothing to do: our DatabaseStatement can't be rewound.
330 }
331
332 /**
333 * {@inheritdoc}
334 */
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]);
340 }
341 else {
342 $this->currentRow = NULL;
343 }
344 }
345
346 /**
347 * {@inheritdoc}
348 */
349 public function valid() {
350 return isset($this->currentRow);
351 }
352
353 /**
354 * {@inheritdoc}
355 */
356 public function rowCount() {
357 // SELECT query should not use the method.
358 if ($this->allowRowCount) {
359 return $this->rowCount;
360 }
361 else {
362 throw new RowCountException();
363 }
364 }
365
366 /**
367 * {@inheritdoc}
368 */
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;
374
375 // Grab the row in the format specified above.
376 $return = $this->current();
377 // Advance the cursor.
378 $this->next();
379
380 // Reset the fetch parameters to the value stored using setFetchMode().
381 $this->fetchStyle = $this->defaultFetchStyle;
382 $this->fetchOptions = $this->defaultFetchOptions;
383 return $return;
384 }
385 else {
386 return FALSE;
387 }
388 }
389
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]];
394 $this->next();
395 return $return;
396 }
397 else {
398 return FALSE;
399 }
400 }
401
402 /**
403 * {@inheritdoc}
404 */
405 public function fetchField($index = 0) {
406 return $this->fetchColumn($index);
407 }
408
409 /**
410 * {@inheritdoc}
411 */
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;
417 }
418 else {
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;
426 }
427
428 $this->next();
429
430 return $result;
431 }
432 else {
433 return FALSE;
434 }
435 }
436
437 /**
438 * {@inheritdoc}
439 */
440 public function fetchAssoc() {
441 if (isset($this->currentRow)) {
442 $result = $this->currentRow;
443 $this->next();
444 return $result;
445 }
446 else {
447 return FALSE;
448 }
449 }
450
451 /**
452 * {@inheritdoc}
453 */
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;
459 }
460 if (isset($constructor_arguments)) {
461 $this->fetchOptions['constructor_args'] = $constructor_arguments;
462 }
463
464 $result = [];
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();
469 $this->next();
470 }
471
472 // Reset the fetch parameters to the value stored using setFetchMode().
473 $this->fetchStyle = $this->defaultFetchStyle;
474 $this->fetchOptions = $this->defaultFetchOptions;
475 return $result;
476 }
477
478 /**
479 * {@inheritdoc}
480 */
481 public function fetchCol($index = 0) {
482 if (isset($this->columnNames[$index])) {
483 $result = [];
484 // Traverse the array as PHP would have done.
485 while (isset($this->currentRow)) {
486 $result[] = $this->currentRow[$this->columnNames[$index]];
487 $this->next();
488 }
489 return $result;
490 }
491 else {
492 return [];
493 }
494 }
495
496 /**
497 * {@inheritdoc}
498 */
499 public function fetchAllKeyed($key_index = 0, $value_index = 1) {
500 if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index])) {
501 return [];
502 }
503
504 $key = $this->columnNames[$key_index];
505 $value = $this->columnNames[$value_index];
506
507 $result = [];
508 // Traverse the array as PHP would have done.
509 while (isset($this->currentRow)) {
510 $result[$this->currentRow[$key]] = $this->currentRow[$value];
511 $this->next();
512 }
513 return $result;
514 }
515
516 /**
517 * {@inheritdoc}
518 */
519 public function fetchAllAssoc($key, $fetch_style = NULL) {
520 $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
521 $this->fetchOptions = $this->defaultFetchOptions;
522
523 $result = [];
524 // Traverse the array as PHP would have done.
525 while (isset($this->currentRow)) {
526 // Grab the row in its raw \PDO::FETCH_ASSOC format.
527 $result_row = $this->current();
528 $result[$this->currentRow[$key]] = $result_row;
529 $this->next();
530 }
531
532 // Reset the fetch parameters to the value stored using setFetchMode().
533 $this->fetchStyle = $this->defaultFetchStyle;
534 $this->fetchOptions = $this->defaultFetchOptions;
535 return $result;
536 }
537
538 }