1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Database
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 /**
13 * Joomla Platform PDO Database Driver Class
14 *
15 * @link https://secure.php.net/pdo
16 * @since 12.1
17 */
18 abstract class JDatabaseDriverPdo extends JDatabaseDriver
19 {
20 /**
21 * The name of the database driver.
22 *
23 * @var string
24 * @since 12.1
25 */
26 public $name = 'pdo';
27
28 /**
29 * @var PDO The database connection resource.
30 * @since 12.1
31 */
32 protected $connection;
33
34 /**
35 * The character(s) used to quote SQL statement names such as table names or field names,
36 * etc. The child classes should define this as necessary. If a single character string the
37 * same character is used for both sides of the quoted name, else the first character will be
38 * used for the opening quote and the second for the closing quote.
39 *
40 * @var string
41 * @since 12.1
42 */
43 protected $nameQuote = "'";
44
45 /**
46 * The null or zero representation of a timestamp for the database driver. This should be
47 * defined in child classes to hold the appropriate value for the engine.
48 *
49 * @var string
50 * @since 12.1
51 */
52 protected $nullDate = '0000-00-00 00:00:00';
53
54 /**
55 * @var resource The prepared statement.
56 * @since 12.1
57 */
58 protected $prepared;
59
60 /**
61 * Contains the current query execution status
62 *
63 * @var array
64 * @since 12.1
65 */
66 protected $executed = false;
67
68 /**
69 * Constructor.
70 *
71 * @param array $options List of options used to configure the connection
72 *
73 * @since 12.1
74 */
75 public function __construct($options)
76 {
77 // Get some basic values from the options.
78 $options['driver'] = (isset($options['driver'])) ? $options['driver'] : 'odbc';
79 $options['dsn'] = (isset($options['dsn'])) ? $options['dsn'] : '';
80 $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
81 $options['database'] = (isset($options['database'])) ? $options['database'] : '';
82 $options['user'] = (isset($options['user'])) ? $options['user'] : '';
83 $options['password'] = (isset($options['password'])) ? $options['password'] : '';
84 $options['driverOptions'] = (isset($options['driverOptions'])) ? $options['driverOptions'] : array();
85
86 $hostParts = explode(':', $options['host']);
87
88 if (!empty($hostParts[1]))
89 {
90 $options['host'] = $hostParts[0];
91 $options['port'] = $hostParts[1];
92 }
93
94 // Finalize initialisation
95 parent::__construct($options);
96 }
97
98 /**
99 * Destructor.
100 *
101 * @since 12.1
102 */
103 public function __destruct()
104 {
105 $this->disconnect();
106 }
107
108 /**
109 * Connects to the database if needed.
110 *
111 * @return void Returns void if the database connected successfully.
112 *
113 * @since 12.1
114 * @throws RuntimeException
115 */
116 public function connect()
117 {
118 if ($this->connection)
119 {
120 return;
121 }
122
123 // Make sure the PDO extension for PHP is installed and enabled.
124 if (!self::isSupported())
125 {
126 throw new JDatabaseExceptionUnsupported('PDO Extension is not available.', 1);
127 }
128
129 $replace = array();
130 $with = array();
131
132 // Find the correct PDO DSN Format to use:
133 switch ($this->options['driver'])
134 {
135 case 'cubrid':
136 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 33000;
137
138 $format = 'cubrid:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
139
140 $replace = array('#HOST#', '#PORT#', '#DBNAME#');
141 $with = array($this->options['host'], $this->options['port'], $this->options['database']);
142
143 break;
144
145 case 'dblib':
146 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;
147
148 $format = 'dblib:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
149
150 $replace = array('#HOST#', '#PORT#', '#DBNAME#');
151 $with = array($this->options['host'], $this->options['port'], $this->options['database']);
152
153 break;
154
155 case 'firebird':
156 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 3050;
157
158 $format = 'firebird:dbname=#DBNAME#';
159
160 $replace = array('#DBNAME#');
161 $with = array($this->options['database']);
162
163 break;
164
165 case 'ibm':
166 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 56789;
167
168 if (!empty($this->options['dsn']))
169 {
170 $format = 'ibm:DSN=#DSN#';
171
172 $replace = array('#DSN#');
173 $with = array($this->options['dsn']);
174 }
175 else
176 {
177 $format = 'ibm:hostname=#HOST#;port=#PORT#;database=#DBNAME#';
178
179 $replace = array('#HOST#', '#PORT#', '#DBNAME#');
180 $with = array($this->options['host'], $this->options['port'], $this->options['database']);
181 }
182
183 break;
184
185 case 'informix':
186 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1526;
187 $this->options['protocol'] = (isset($this->options['protocol'])) ? $this->options['protocol'] : 'onsoctcp';
188
189 if (!empty($this->options['dsn']))
190 {
191 $format = 'informix:DSN=#DSN#';
192
193 $replace = array('#DSN#');
194 $with = array($this->options['dsn']);
195 }
196 else
197 {
198 $format = 'informix:host=#HOST#;service=#PORT#;database=#DBNAME#;server=#SERVER#;protocol=#PROTOCOL#';
199
200 $replace = array('#HOST#', '#PORT#', '#DBNAME#', '#SERVER#', '#PROTOCOL#');
201 $with = array($this->options['host'], $this->options['port'], $this->options['database'], $this->options['server'], $this->options['protocol']);
202 }
203
204 break;
205
206 case 'mssql':
207 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;
208
209 $format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
210
211 $replace = array('#HOST#', '#PORT#', '#DBNAME#');
212 $with = array($this->options['host'], $this->options['port'], $this->options['database']);
213
214 break;
215
216 // The pdomysql case is a special case within the CMS environment
217 case 'pdomysql':
218 case 'mysql':
219 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 3306;
220
221 $format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#';
222
223 $replace = array('#HOST#', '#PORT#', '#DBNAME#', '#CHARSET#');
224 $with = array($this->options['host'], $this->options['port'], $this->options['database'], $this->options['charset']);
225
226 break;
227
228 case 'oci':
229 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1521;
230 $this->options['charset'] = (isset($this->options['charset'])) ? $this->options['charset'] : 'AL32UTF8';
231
232 if (!empty($this->options['dsn']))
233 {
234 $format = 'oci:dbname=#DSN#';
235
236 $replace = array('#DSN#');
237 $with = array($this->options['dsn']);
238 }
239 else
240 {
241 $format = 'oci:dbname=//#HOST#:#PORT#/#DBNAME#';
242
243 $replace = array('#HOST#', '#PORT#', '#DBNAME#');
244 $with = array($this->options['host'], $this->options['port'], $this->options['database']);
245 }
246
247 $format .= ';charset=' . $this->options['charset'];
248
249 break;
250
251 case 'odbc':
252 $format = 'odbc:DSN=#DSN#;UID:#USER#;PWD=#PASSWORD#';
253
254 $replace = array('#DSN#', '#USER#', '#PASSWORD#');
255 $with = array($this->options['dsn'], $this->options['user'], $this->options['password']);
256
257 break;
258
259 case 'pgsql':
260 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 5432;
261
262 $format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
263
264 $replace = array('#HOST#', '#PORT#', '#DBNAME#');
265 $with = array($this->options['host'], $this->options['port'], $this->options['database']);
266
267 break;
268
269 case 'sqlite':
270 if (isset($this->options['version']) && $this->options['version'] == 2)
271 {
272 $format = 'sqlite2:#DBNAME#';
273 }
274 else
275 {
276 $format = 'sqlite:#DBNAME#';
277 }
278
279 $replace = array('#DBNAME#');
280 $with = array($this->options['database']);
281
282 break;
283
284 case 'sybase':
285 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;
286
287 $format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
288
289 $replace = array('#HOST#', '#PORT#', '#DBNAME#');
290 $with = array($this->options['host'], $this->options['port'], $this->options['database']);
291
292 break;
293 }
294
295 // Create the connection string:
296 $connectionString = str_replace($replace, $with, $format);
297
298 try
299 {
300 $this->connection = new PDO(
301 $connectionString,
302 $this->options['user'],
303 $this->options['password'],
304 $this->options['driverOptions']
305 );
306 }
307 catch (PDOException $e)
308 {
309 throw new JDatabaseExceptionConnecting('Could not connect to PDO: ' . $e->getMessage(), 2, $e);
310 }
311 }
312
313 /**
314 * Disconnects the database.
315 *
316 * @return void
317 *
318 * @since 12.1
319 */
320 public function disconnect()
321 {
322 foreach ($this->disconnectHandlers as $h)
323 {
324 call_user_func_array($h, array( &$this));
325 }
326
327 $this->freeResult();
328 $this->connection = null;
329 }
330
331 /**
332 * Method to escape a string for usage in an SQL statement.
333 *
334 * Oracle escaping reference:
335 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
336 *
337 * SQLite escaping notes:
338 * http://www.sqlite.org/faq.html#q14
339 *
340 * Method body is as implemented by the Zend Framework
341 *
342 * Note: Using query objects with bound variables is
343 * preferable to the below.
344 *
345 * @param string $text The string to be escaped.
346 * @param boolean $extra Unused optional parameter to provide extra escaping.
347 *
348 * @return string The escaped string.
349 *
350 * @since 12.1
351 */
352 public function escape($text, $extra = false)
353 {
354 if (is_int($text) || is_float($text))
355 {
356 return $text;
357 }
358
359 $text = str_replace("'", "''", $text);
360
361 return addcslashes($text, "\000\n\r\\\032");
362 }
363
364 /**
365 * Execute the SQL statement.
366 *
367 * @return mixed A database cursor resource on success, boolean false on failure.
368 *
369 * @since 12.1
370 * @throws RuntimeException
371 * @throws Exception
372 */
373 public function execute()
374 {
375 $this->connect();
376
377 // Take a local copy so that we don't modify the original query and cause issues later
378 $query = $this->replacePrefix((string) $this->sql);
379
380 if (!is_object($this->connection))
381 {
382 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
383 throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
384 }
385
386 // Increment the query counter.
387 $this->count++;
388
389 // Reset the error values.
390 $this->errorNum = 0;
391 $this->errorMsg = '';
392
393 // If debugging is enabled then let's log the query.
394 if ($this->debug)
395 {
396 // Add the query to the object queue.
397 $this->log[] = $query;
398
399 JLog::add($query, JLog::DEBUG, 'databasequery');
400
401 $this->timings[] = microtime(true);
402 }
403
404 // Execute the query.
405 $this->executed = false;
406
407 if ($this->prepared instanceof PDOStatement)
408 {
409 // Bind the variables:
410 if ($this->sql instanceof JDatabaseQueryPreparable)
411 {
412 $bounded = $this->sql->getBounded();
413
414 foreach ($bounded as $key => $obj)
415 {
416 $this->prepared->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions);
417 }
418 }
419
420 $this->executed = $this->prepared->execute();
421 }
422
423 if ($this->debug)
424 {
425 $this->timings[] = microtime(true);
426
427 if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
428 {
429 $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
430 }
431 else
432 {
433 $this->callStacks[] = debug_backtrace();
434 }
435 }
436
437 // If an error occurred handle it.
438 if (!$this->executed)
439 {
440 // Get the error number and message before we execute any more queries.
441 $errorNum = $this->getErrorNumber();
442 $errorMsg = $this->getErrorMessage();
443
444 // Check if the server was disconnected.
445 if (!$this->connected())
446 {
447 try
448 {
449 // Attempt to reconnect.
450 $this->connection = null;
451 $this->connect();
452 }
453 // If connect fails, ignore that exception and throw the normal exception.
454 catch (RuntimeException $e)
455 {
456 // Get the error number and message.
457 $this->errorNum = $this->getErrorNumber();
458 $this->errorMsg = $this->getErrorMessage();
459
460 // Throw the normal query exception.
461 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
462
463 throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum, $e);
464 }
465
466 // Since we were able to reconnect, run the query again.
467 return $this->execute();
468 }
469 // The server was not disconnected.
470 else
471 {
472 // Get the error number and message from before we tried to reconnect.
473 $this->errorNum = $errorNum;
474 $this->errorMsg = $errorMsg;
475
476 // Throw the normal query exception.
477 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
478
479 throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
480 }
481 }
482
483 return $this->prepared;
484 }
485
486 /**
487 * Retrieve a PDO database connection attribute
488 *
489 * Usage: $db->getOption(PDO::ATTR_CASE);
490 *
491 * @param mixed $key One of the PDO::ATTR_* Constants
492 *
493 * @return mixed
494 *
495 * @link https://secure.php.net/manual/en/pdo.getattribute.php
496 * @since 12.1
497 */
498 public function getOption($key)
499 {
500 $this->connect();
501
502 return $this->connection->getAttribute($key);
503 }
504
505 /**
506 * Get a query to run and verify the database is operational.
507 *
508 * @return string The query to check the health of the DB.
509 *
510 * @since 12.2
511 */
512 public function getConnectedQuery()
513 {
514 return 'SELECT 1';
515 }
516
517 /**
518 * Sets an attribute on the PDO database handle.
519 *
520 * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
521 *
522 * @param integer $key One of the PDO::ATTR_* Constants
523 * @param mixed $value One of the associated PDO Constants related to the particular attribute key.
524 *
525 * @return boolean
526 *
527 * @link https://secure.php.net/manual/en/pdo.setattribute.php
528 * @since 12.1
529 */
530 public function setOption($key, $value)
531 {
532 $this->connect();
533
534 return $this->connection->setAttribute($key, $value);
535 }
536
537 /**
538 * Test to see if the PDO extension is available.
539 * Override as needed to check for specific PDO Drivers.
540 *
541 * @return boolean True on success, false otherwise.
542 *
543 * @since 12.1
544 */
545 public static function isSupported()
546 {
547 return defined('PDO::ATTR_DRIVER_NAME');
548 }
549
550 /**
551 * Determines if the connection to the server is active.
552 *
553 * @return boolean True if connected to the database engine.
554 *
555 * @since 12.1
556 */
557 public function connected()
558 {
559 // Flag to prevent recursion into this function.
560 static $checkingConnected = false;
561
562 if ($checkingConnected)
563 {
564 // Reset this flag and throw an exception.
565 $checkingConnected = true;
566 die('Recursion trying to check if connected.');
567 }
568
569 // Backup the query state.
570 $query = $this->sql;
571 $limit = $this->limit;
572 $offset = $this->offset;
573 $prepared = $this->prepared;
574
575 try
576 {
577 // Set the checking connection flag.
578 $checkingConnected = true;
579
580 // Run a simple query to check the connection.
581 $this->setQuery($this->getConnectedQuery());
582 $status = (bool) $this->loadResult();
583 }
584 // If we catch an exception here, we must not be connected.
585 catch (Exception $e)
586 {
587 $status = false;
588 }
589
590 // Restore the query state.
591 $this->sql = $query;
592 $this->limit = $limit;
593 $this->offset = $offset;
594 $this->prepared = $prepared;
595 $checkingConnected = false;
596
597 return $status;
598 }
599
600 /**
601 * Get the number of affected rows for the previous executed SQL statement.
602 * Only applicable for DELETE, INSERT, or UPDATE statements.
603 *
604 * @return integer The number of affected rows.
605 *
606 * @since 12.1
607 */
608 public function getAffectedRows()
609 {
610 $this->connect();
611
612 if ($this->prepared instanceof PDOStatement)
613 {
614 return $this->prepared->rowCount();
615 }
616 else
617 {
618 return 0;
619 }
620 }
621
622 /**
623 * Get the number of returned rows for the previous executed SQL statement.
624 * Only applicable for DELETE, INSERT, or UPDATE statements.
625 *
626 * @param resource $cursor An optional database cursor resource to extract the row count from.
627 *
628 * @return integer The number of returned rows.
629 *
630 * @since 12.1
631 */
632 public function getNumRows($cursor = null)
633 {
634 $this->connect();
635
636 if ($cursor instanceof PDOStatement)
637 {
638 return $cursor->rowCount();
639 }
640 elseif ($this->prepared instanceof PDOStatement)
641 {
642 return $this->prepared->rowCount();
643 }
644 else
645 {
646 return 0;
647 }
648 }
649
650 /**
651 * Method to get the auto-incremented value from the last INSERT statement.
652 *
653 * @return string The value of the auto-increment field from the last inserted row.
654 *
655 * @since 12.1
656 */
657 public function insertid()
658 {
659 $this->connect();
660
661 // Error suppress this to prevent PDO warning us that the driver doesn't support this operation.
662 return @$this->connection->lastInsertId();
663 }
664
665 /**
666 * Select a database for use.
667 *
668 * @param string $database The name of the database to select for use.
669 *
670 * @return boolean True if the database was successfully selected.
671 *
672 * @since 12.1
673 * @throws RuntimeException
674 */
675 public function select($database)
676 {
677 $this->connect();
678
679 return true;
680 }
681
682 /**
683 * Sets the SQL statement string for later execution.
684 *
685 * @param mixed $query The SQL statement to set either as a JDatabaseQuery object or a string.
686 * @param integer $offset The affected row offset to set.
687 * @param integer $limit The maximum affected rows to set.
688 * @param array $driverOptions The optional PDO driver options.
689 *
690 * @return JDatabaseDriver This object to support method chaining.
691 *
692 * @since 12.1
693 */
694 public function setQuery($query, $offset = null, $limit = null, $driverOptions = array())
695 {
696 $this->connect();
697
698 $this->freeResult();
699
700 if (is_string($query))
701 {
702 // Allows taking advantage of bound variables in a direct query:
703 $query = $this->getQuery(true)->setQuery($query);
704 }
705
706 if ($query instanceof JDatabaseQueryLimitable && !is_null($offset) && !is_null($limit))
707 {
708 $query = $query->processLimit($query, $limit, $offset);
709 }
710
711 // Create a stringified version of the query (with prefixes replaced):
712 $sql = $this->replacePrefix((string) $query);
713
714 // Use the stringified version in the prepare call:
715 $this->prepared = $this->connection->prepare($sql, $driverOptions);
716
717 // Store reference to the original JDatabaseQuery instance within the class.
718 // This is important since binding variables depends on it within execute():
719 parent::setQuery($query, $offset, $limit);
720
721 return $this;
722 }
723
724 /**
725 * Set the connection to use UTF-8 character encoding.
726 *
727 * @return boolean True on success.
728 *
729 * @since 12.1
730 */
731 public function setUtf()
732 {
733 return false;
734 }
735
736 /**
737 * Method to commit a transaction.
738 *
739 * @param boolean $toSavepoint If true, commit to the last savepoint.
740 *
741 * @return void
742 *
743 * @since 12.1
744 * @throws RuntimeException
745 */
746 public function transactionCommit($toSavepoint = false)
747 {
748 $this->connect();
749
750 if (!$toSavepoint || $this->transactionDepth == 1)
751 {
752 $this->connection->commit();
753 }
754
755 $this->transactionDepth--;
756 }
757
758 /**
759 * Method to roll back a transaction.
760 *
761 * @param boolean $toSavepoint If true, rollback to the last savepoint.
762 *
763 * @return void
764 *
765 * @since 12.1
766 * @throws RuntimeException
767 */
768 public function transactionRollback($toSavepoint = false)
769 {
770 $this->connect();
771
772 if (!$toSavepoint || $this->transactionDepth == 1)
773 {
774 $this->connection->rollBack();
775 }
776
777 $this->transactionDepth--;
778 }
779
780 /**
781 * Method to initialize a transaction.
782 *
783 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
784 *
785 * @return void
786 *
787 * @since 12.1
788 * @throws RuntimeException
789 */
790 public function transactionStart($asSavepoint = false)
791 {
792 $this->connect();
793
794 if (!$asSavepoint || !$this->transactionDepth)
795 {
796 $this->connection->beginTransaction();
797 }
798
799 $this->transactionDepth++;
800 }
801
802 /**
803 * Method to fetch a row from the result set cursor as an array.
804 *
805 * @param mixed $cursor The optional result set cursor from which to fetch the row.
806 *
807 * @return mixed Either the next row from the result set or false if there are no more rows.
808 *
809 * @since 12.1
810 */
811 protected function fetchArray($cursor = null)
812 {
813 if (!empty($cursor) && $cursor instanceof PDOStatement)
814 {
815 return $cursor->fetch(PDO::FETCH_NUM);
816 }
817
818 if ($this->prepared instanceof PDOStatement)
819 {
820 return $this->prepared->fetch(PDO::FETCH_NUM);
821 }
822 }
823
824 /**
825 * Method to fetch a row from the result set cursor as an associative array.
826 *
827 * @param mixed $cursor The optional result set cursor from which to fetch the row.
828 *
829 * @return mixed Either the next row from the result set or false if there are no more rows.
830 *
831 * @since 12.1
832 */
833 protected function fetchAssoc($cursor = null)
834 {
835 if (!empty($cursor) && $cursor instanceof PDOStatement)
836 {
837 return $cursor->fetch(PDO::FETCH_ASSOC);
838 }
839
840 if ($this->prepared instanceof PDOStatement)
841 {
842 return $this->prepared->fetch(PDO::FETCH_ASSOC);
843 }
844 }
845
846 /**
847 * Method to fetch a row from the result set cursor as an object.
848 *
849 * @param mixed $cursor The optional result set cursor from which to fetch the row.
850 * @param string $class Unused, only necessary so method signature will be the same as parent.
851 *
852 * @return mixed Either the next row from the result set or false if there are no more rows.
853 *
854 * @since 12.1
855 */
856 protected function fetchObject($cursor = null, $class = 'stdClass')
857 {
858 if (!empty($cursor) && $cursor instanceof PDOStatement)
859 {
860 return $cursor->fetchObject($class);
861 }
862
863 if ($this->prepared instanceof PDOStatement)
864 {
865 return $this->prepared->fetchObject($class);
866 }
867 }
868
869 /**
870 * Method to free up the memory used for the result set.
871 *
872 * @param mixed $cursor The optional result set cursor from which to fetch the row.
873 *
874 * @return void
875 *
876 * @since 12.1
877 */
878 protected function freeResult($cursor = null)
879 {
880 $this->executed = false;
881
882 if ($cursor instanceof PDOStatement)
883 {
884 $cursor->closeCursor();
885 $cursor = null;
886 }
887
888 if ($this->prepared instanceof PDOStatement)
889 {
890 $this->prepared->closeCursor();
891 $this->prepared = null;
892 }
893 }
894
895 /**
896 * Method to get the next row in the result set from the database query as an object.
897 *
898 * @param string $class The class name to use for the returned row object.
899 *
900 * @return mixed The result of the query as an array, false if there are no more rows.
901 *
902 * @since 12.1
903 * @throws RuntimeException
904 * @deprecated 4.0 (CMS) Use getIterator() instead
905 */
906 public function loadNextObject($class = 'stdClass')
907 {
908 JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
909 $this->connect();
910
911 // Execute the query and get the result set cursor.
912 if (!$this->executed)
913 {
914 if (!($this->execute()))
915 {
916 return $this->errorNum ? null : false;
917 }
918 }
919
920 // Get the next row from the result set as an object of type $class.
921 if ($row = $this->fetchObject(null, $class))
922 {
923 return $row;
924 }
925
926 // Free up system resources and return.
927 $this->freeResult();
928
929 return false;
930 }
931
932 /**
933 * Method to get the next row in the result set from the database query as an array.
934 *
935 * @return mixed The result of the query as an array, false if there are no more rows.
936 *
937 * @since 12.1
938 * @throws RuntimeException
939 */
940 public function loadNextAssoc()
941 {
942 $this->connect();
943
944 // Execute the query and get the result set cursor.
945 if (!$this->executed)
946 {
947 if (!($this->execute()))
948 {
949 return $this->errorNum ? null : false;
950 }
951 }
952
953 // Get the next row from the result set as an object of type $class.
954 if ($row = $this->fetchAssoc())
955 {
956 return $row;
957 }
958
959 // Free up system resources and return.
960 $this->freeResult();
961
962 return false;
963 }
964
965 /**
966 * Method to get the next row in the result set from the database query as an array.
967 *
968 * @return mixed The result of the query as an array, false if there are no more rows.
969 *
970 * @since 12.1
971 * @throws RuntimeException
972 * @deprecated 4.0 (CMS) Use getIterator() instead
973 */
974 public function loadNextRow()
975 {
976 JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
977 $this->connect();
978
979 // Execute the query and get the result set cursor.
980 if (!$this->executed)
981 {
982 if (!($this->execute()))
983 {
984 return $this->errorNum ? null : false;
985 }
986 }
987
988 // Get the next row from the result set as an object of type $class.
989 if ($row = $this->fetchArray())
990 {
991 return $row;
992 }
993
994 // Free up system resources and return.
995 $this->freeResult();
996
997 return false;
998 }
999
1000 /**
1001 * PDO does not support serialize
1002 *
1003 * @return array
1004 *
1005 * @since 12.3
1006 */
1007 public function __sleep()
1008 {
1009 $serializedProperties = array();
1010
1011 $reflect = new ReflectionClass($this);
1012
1013 // Get properties of the current class
1014 $properties = $reflect->getProperties();
1015
1016 foreach ($properties as $property)
1017 {
1018 // Do not serialize properties that are PDO
1019 if ($property->isStatic() == false && !($this->{$property->name} instanceof PDO))
1020 {
1021 $serializedProperties[] = $property->name;
1022 }
1023 }
1024
1025 return $serializedProperties;
1026 }
1027
1028 /**
1029 * Wake up after serialization
1030 *
1031 * @return array
1032 *
1033 * @since 12.3
1034 */
1035 public function __wakeup()
1036 {
1037 // Get connection back
1038 $this->__construct($this->options);
1039 }
1040
1041 /**
1042 * Return the actual SQL Error number
1043 *
1044 * @return integer The SQL Error number
1045 *
1046 * @since 3.4.6
1047 */
1048 protected function getErrorNumber()
1049 {
1050 return (int) $this->connection->errorCode();
1051 }
1052
1053 /**
1054 * Return the actual SQL Error message
1055 *
1056 * @return string The SQL Error message
1057 *
1058 * @since 3.4.6
1059 */
1060 protected function getErrorMessage()
1061 {
1062 // The SQL Error Information
1063 $errorInfo = implode(', ', $this->connection->errorInfo());
1064
1065 // Replace the Databaseprefix with `#__` if we are not in Debug
1066 if (!$this->debug)
1067 {
1068 $errorInfo = str_replace($this->tablePrefix, '#__', $errorInfo);
1069 }
1070
1071 return $errorInfo;
1072 }
1073 }
1074