|
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 |