Mercurial > hg > isophonics-drupal-site
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 } |