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 Database Driver Class
14 *
15 * @since 12.1
16 *
17 * @method string q() q($text, $escape = true) Alias for quote method
18 * @method string qn() qn($name, $as = null) Alias for quoteName method
19 */
20 abstract class JDatabaseDriver extends JDatabase implements JDatabaseInterface
21 {
22 /**
23 * The name of the database.
24 *
25 * @var string
26 * @since 11.4
27 */
28 private $_database;
29
30 /**
31 * The name of the database driver.
32 *
33 * @var string
34 * @since 11.1
35 */
36 public $name;
37
38 /**
39 * The type of the database server family supported by this driver. Examples: mysql, oracle, postgresql, mssql,
40 * sqlite.
41 *
42 * @var string
43 * @since CMS 3.5.0
44 */
45 public $serverType;
46
47 /**
48 * @var resource The database connection resource.
49 * @since 11.1
50 */
51 protected $connection;
52
53 /**
54 * @var integer The number of SQL statements executed by the database driver.
55 * @since 11.1
56 */
57 protected $count = 0;
58
59 /**
60 * @var resource The database connection cursor from the last query.
61 * @since 11.1
62 */
63 protected $cursor;
64
65 /**
66 * @var boolean The database driver debugging state.
67 * @since 11.1
68 */
69 protected $debug = false;
70
71 /**
72 * @var integer The affected row limit for the current SQL statement.
73 * @since 11.1
74 */
75 protected $limit = 0;
76
77 /**
78 * @var array The log of executed SQL statements by the database driver.
79 * @since 11.1
80 */
81 protected $log = array();
82
83 /**
84 * @var array The log of executed SQL statements timings (start and stop microtimes) by the database driver.
85 * @since CMS 3.1.2
86 */
87 protected $timings = array();
88
89 /**
90 * @var array The log of executed SQL statements timings (start and stop microtimes) by the database driver.
91 * @since CMS 3.1.2
92 */
93 protected $callStacks = array();
94
95 /**
96 * @var string The character(s) used to quote SQL statement names such as table names or field names,
97 * etc. The child classes should define this as necessary. If a single character string the
98 * same character is used for both sides of the quoted name, else the first character will be
99 * used for the opening quote and the second for the closing quote.
100 * @since 11.1
101 */
102 protected $nameQuote;
103
104 /**
105 * @var string The null or zero representation of a timestamp for the database driver. This should be
106 * defined in child classes to hold the appropriate value for the engine.
107 * @since 11.1
108 */
109 protected $nullDate;
110
111 /**
112 * @var integer The affected row offset to apply for the current SQL statement.
113 * @since 11.1
114 */
115 protected $offset = 0;
116
117 /**
118 * @var array Passed in upon instantiation and saved.
119 * @since 11.1
120 */
121 protected $options;
122
123 /**
124 * @var mixed The current SQL statement to execute.
125 * @since 11.1
126 */
127 protected $sql;
128
129 /**
130 * @var string The common database table prefix.
131 * @since 11.1
132 */
133 protected $tablePrefix;
134
135 /**
136 * @var boolean True if the database engine supports UTF-8 character encoding.
137 * @since 11.1
138 */
139 protected $utf = true;
140
141 /**
142 * @var boolean True if the database engine supports UTF-8 Multibyte (utf8mb4) character encoding.
143 * @since CMS 3.5.0
144 */
145 protected $utf8mb4 = false;
146
147 /**
148 * @var integer The database error number
149 * @since 11.1
150 * @deprecated 12.1
151 */
152 protected $errorNum = 0;
153
154 /**
155 * @var string The database error message
156 * @since 11.1
157 * @deprecated 12.1
158 */
159 protected $errorMsg;
160
161 /**
162 * @var array JDatabaseDriver instances container.
163 * @since 11.1
164 */
165 protected static $instances = array();
166
167 /**
168 * @var string The minimum supported database version.
169 * @since 12.1
170 */
171 protected static $dbMinimum;
172
173 /**
174 * @var integer The depth of the current transaction.
175 * @since 12.3
176 */
177 protected $transactionDepth = 0;
178
179 /**
180 * @var callable[] List of callables to call just before disconnecting database
181 * @since CMS 3.1.2
182 */
183 protected $disconnectHandlers = array();
184
185 /**
186 * Get a list of available database connectors. The list will only be populated with connectors that both
187 * the class exists and the static test method returns true. This gives us the ability to have a multitude
188 * of connector classes that are self-aware as to whether or not they are able to be used on a given system.
189 *
190 * @return array An array of available database connectors.
191 *
192 * @since 11.1
193 */
194 public static function getConnectors()
195 {
196 $connectors = array();
197
198 // Get an iterator and loop trough the driver classes.
199 $iterator = new DirectoryIterator(__DIR__ . '/driver');
200
201 /* @type $file DirectoryIterator */
202 foreach ($iterator as $file)
203 {
204 $fileName = $file->getFilename();
205
206 // Only load for php files.
207 if (!$file->isFile() || $file->getExtension() != 'php')
208 {
209 continue;
210 }
211
212 // Derive the class name from the type.
213 $class = str_ireplace('.php', '', 'JDatabaseDriver' . ucfirst(trim($fileName)));
214
215 // If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
216 if (!class_exists($class))
217 {
218 continue;
219 }
220
221 // Sweet! Our class exists, so now we just need to know if it passes its test method.
222 if ($class::isSupported())
223 {
224 // Connector names should not have file extensions.
225 $connectors[] = str_ireplace('.php', '', $fileName);
226 }
227 }
228
229 return $connectors;
230 }
231
232 /**
233 * Method to return a JDatabaseDriver instance based on the given options. There are three global options and then
234 * the rest are specific to the database driver. The 'driver' option defines which JDatabaseDriver class is
235 * used for the connection -- the default is 'mysqli'. The 'database' option determines which database is to
236 * be used for the connection. The 'select' option determines whether the connector should automatically select
237 * the chosen database.
238 *
239 * Instances are unique to the given options and new objects are only created when a unique options array is
240 * passed into the method. This ensures that we don't end up with unnecessary database connection resources.
241 *
242 * @param array $options Parameters to be passed to the database driver.
243 *
244 * @return JDatabaseDriver A database object.
245 *
246 * @since 11.1
247 * @throws RuntimeException
248 */
249 public static function getInstance($options = array())
250 {
251 // Sanitize the database connector options.
252 $options['driver'] = (isset($options['driver'])) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli';
253 $options['database'] = (isset($options['database'])) ? $options['database'] : null;
254 $options['select'] = (isset($options['select'])) ? $options['select'] : true;
255
256 // If the selected driver is `mysql` and we are on PHP 7 or greater, switch to the `mysqli` driver.
257 if ($options['driver'] === 'mysql' && PHP_MAJOR_VERSION >= 7)
258 {
259 // Check if we have support for the other MySQL drivers
260 $mysqliSupported = JDatabaseDriverMysqli::isSupported();
261 $pdoMysqlSupported = JDatabaseDriverPdomysql::isSupported();
262
263 // If neither is supported, then the user cannot use MySQL; throw an exception
264 if (!$mysqliSupported && !$pdoMysqlSupported)
265 {
266 throw new JDatabaseExceptionUnsupported(
267 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.'
268 . ' Also, this system does not support MySQLi or PDO MySQL. Cannot instantiate database driver.'
269 );
270 }
271
272 // Prefer MySQLi as it is a closer replacement for the removed MySQL driver, otherwise use the PDO driver
273 if ($mysqliSupported)
274 {
275 JLog::add(
276 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver. Trying `mysqli` instead.',
277 JLog::WARNING,
278 'deprecated'
279 );
280
281 $options['driver'] = 'mysqli';
282 }
283 else
284 {
285 JLog::add(
286 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver. Trying `pdomysql` instead.',
287 JLog::WARNING,
288 'deprecated'
289 );
290
291 $options['driver'] = 'pdomysql';
292 }
293 }
294
295 // Get the options signature for the database connector.
296 $signature = md5(serialize($options));
297
298 // If we already have a database connector instance for these options then just use that.
299 if (empty(self::$instances[$signature]))
300 {
301 // Derive the class name from the driver.
302 $class = 'JDatabaseDriver' . ucfirst(strtolower($options['driver']));
303
304 // If the class still doesn't exist we have nothing left to do but throw an exception. We did our best.
305 if (!class_exists($class))
306 {
307 throw new JDatabaseExceptionUnsupported(sprintf('Unable to load Database Driver: %s', $options['driver']));
308 }
309
310 // Create our new JDatabaseDriver connector based on the options given.
311 try
312 {
313 $instance = new $class($options);
314 }
315 catch (RuntimeException $e)
316 {
317 throw new JDatabaseExceptionConnecting(sprintf('Unable to connect to the Database: %s', $e->getMessage()), $e->getCode(), $e);
318 }
319
320 // Set the new connector to the global instances based on signature.
321 self::$instances[$signature] = $instance;
322 }
323
324 return self::$instances[$signature];
325 }
326
327 /**
328 * Splits a string of multiple queries into an array of individual queries.
329 * Single line or line end comments and multi line comments are stripped off.
330 *
331 * @param string $sql Input SQL string with which to split into individual queries.
332 *
333 * @return array The queries from the input string separated into an array.
334 *
335 * @since 11.1
336 */
337 public static function splitSql($sql)
338 {
339 $start = 0;
340 $open = false;
341 $comment = false;
342 $endString = '';
343 $end = strlen($sql);
344 $queries = array();
345 $query = '';
346
347 for ($i = 0; $i < $end; $i++)
348 {
349 $current = substr($sql, $i, 1);
350 $current2 = substr($sql, $i, 2);
351 $current3 = substr($sql, $i, 3);
352 $lenEndString = strlen($endString);
353 $testEnd = substr($sql, $i, $lenEndString);
354
355 if ($current == '"' || $current == "'" || $current2 == '--'
356 || ($current2 == '/*' && $current3 != '/*!' && $current3 != '/*+')
357 || ($current == '#' && $current3 != '#__')
358 || ($comment && $testEnd == $endString))
359 {
360 // Check if quoted with previous backslash
361 $n = 2;
362
363 while (substr($sql, $i - $n + 1, 1) == '\\' && $n < $i)
364 {
365 $n++;
366 }
367
368 // Not quoted
369 if ($n % 2 == 0)
370 {
371 if ($open)
372 {
373 if ($testEnd == $endString)
374 {
375 if ($comment)
376 {
377 $comment = false;
378 if ($lenEndString > 1)
379 {
380 $i += ($lenEndString - 1);
381 $current = substr($sql, $i, 1);
382 }
383 $start = $i + 1;
384 }
385 $open = false;
386 $endString = '';
387 }
388 }
389 else
390 {
391 $open = true;
392 if ($current2 == '--')
393 {
394 $endString = "\n";
395 $comment = true;
396 }
397 elseif ($current2 == '/*')
398 {
399 $endString = '*/';
400 $comment = true;
401 }
402 elseif ($current == '#')
403 {
404 $endString = "\n";
405 $comment = true;
406 }
407 else
408 {
409 $endString = $current;
410 }
411 if ($comment && $start < $i)
412 {
413 $query = $query . substr($sql, $start, ($i - $start));
414 }
415 }
416 }
417 }
418
419 if ($comment)
420 {
421 $start = $i + 1;
422 }
423
424 if (($current == ';' && !$open) || $i == $end - 1)
425 {
426 if ($start <= $i)
427 {
428 $query = $query . substr($sql, $start, ($i - $start + 1));
429 }
430 $query = trim($query);
431
432 if ($query)
433 {
434 if (($i == $end - 1) && ($current != ';'))
435 {
436 $query = $query . ';';
437 }
438 $queries[] = $query;
439 }
440
441 $query = '';
442 $start = $i + 1;
443 }
444 }
445
446 return $queries;
447 }
448
449 /**
450 * Magic method to provide method alias support for quote() and quoteName().
451 *
452 * @param string $method The called method.
453 * @param array $args The array of arguments passed to the method.
454 *
455 * @return mixed The aliased method's return value or null.
456 *
457 * @since 11.1
458 */
459 public function __call($method, $args)
460 {
461 if (empty($args))
462 {
463 return;
464 }
465
466 switch ($method)
467 {
468 case 'q':
469 return $this->quote($args[0], isset($args[1]) ? $args[1] : true);
470 break;
471 case 'qn':
472 return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null);
473 break;
474 }
475 }
476
477 /**
478 * Constructor.
479 *
480 * @param array $options List of options used to configure the connection
481 *
482 * @since 11.1
483 */
484 public function __construct($options)
485 {
486 // Initialise object variables.
487 $this->_database = (isset($options['database'])) ? $options['database'] : '';
488
489 $this->tablePrefix = (isset($options['prefix'])) ? $options['prefix'] : 'jos_';
490 $this->count = 0;
491 $this->errorNum = 0;
492 $this->log = array();
493
494 // Set class options.
495 $this->options = $options;
496 }
497
498 /**
499 * Alter database's character set, obtaining query string from protected member.
500 *
501 * @param string $dbName The database name that will be altered
502 *
503 * @return string The query that alter the database query string
504 *
505 * @since 12.2
506 * @throws RuntimeException
507 */
508 public function alterDbCharacterSet($dbName)
509 {
510 if (is_null($dbName))
511 {
512 throw new RuntimeException('Database name must not be null.');
513 }
514
515 $this->setQuery($this->getAlterDbCharacterSet($dbName));
516
517 return $this->execute();
518 }
519
520 /**
521 * Alter a table's character set, obtaining an array of queries to do so from a protected method. The conversion is
522 * wrapped in a transaction, if supported by the database driver. Otherwise the table will be locked before the
523 * conversion. This prevents data corruption.
524 *
525 * @param string $tableName The name of the table to alter
526 * @param boolean $rethrow True to rethrow database exceptions. Default: false (exceptions are suppressed)
527 *
528 * @return boolean True if successful
529 *
530 * @since CMS 3.5.0
531 * @throws RuntimeException If the table name is empty
532 * @throws Exception Relayed from the database layer if a database error occurs and $rethrow == true
533 */
534 public function alterTableCharacterSet($tableName, $rethrow = false)
535 {
536 if (is_null($tableName))
537 {
538 throw new RuntimeException('Table name must not be null.');
539 }
540
541 $queries = $this->getAlterTableCharacterSet($tableName);
542
543 if (empty($queries))
544 {
545 return false;
546 }
547
548 $hasTransaction = true;
549
550 try
551 {
552 $this->transactionStart();
553 }
554 catch (Exception $e)
555 {
556 $hasTransaction = false;
557 $this->lockTable($tableName);
558 }
559
560 foreach ($queries as $query)
561 {
562 try
563 {
564 $this->setQuery($query)->execute();
565 }
566 catch (Exception $e)
567 {
568 if ($hasTransaction)
569 {
570 $this->transactionRollback();
571 }
572 else
573 {
574 $this->unlockTables();
575 }
576
577 if ($rethrow)
578 {
579 throw $e;
580 }
581
582 return false;
583 }
584 }
585
586 if ($hasTransaction)
587 {
588 try
589 {
590 $this->transactionCommit();
591 }
592 catch (Exception $e)
593 {
594 $this->transactionRollback();
595
596 if ($rethrow)
597 {
598 throw $e;
599 }
600
601 return false;
602 }
603 }
604 else
605 {
606 $this->unlockTables();
607 }
608
609 return true;
610 }
611
612 /**
613 * Connects to the database if needed.
614 *
615 * @return void Returns void if the database connected successfully.
616 *
617 * @since 12.1
618 * @throws RuntimeException
619 */
620 abstract public function connect();
621
622 /**
623 * Determines if the connection to the server is active.
624 *
625 * @return boolean True if connected to the database engine.
626 *
627 * @since 11.1
628 */
629 abstract public function connected();
630
631 /**
632 * Create a new database using information from $options object, obtaining query string
633 * from protected member.
634 *
635 * @param stdClass $options Object used to pass user and database name to database driver.
636 * This object must have "db_name" and "db_user" set.
637 * @param boolean $utf True if the database supports the UTF-8 character set.
638 *
639 * @return string The query that creates database
640 *
641 * @since 12.2
642 * @throws RuntimeException
643 */
644 public function createDatabase($options, $utf = true)
645 {
646 if (is_null($options))
647 {
648 throw new RuntimeException('$options object must not be null.');
649 }
650 elseif (empty($options->db_name))
651 {
652 throw new RuntimeException('$options object must have db_name set.');
653 }
654 elseif (empty($options->db_user))
655 {
656 throw new RuntimeException('$options object must have db_user set.');
657 }
658
659 $this->setQuery($this->getCreateDatabaseQuery($options, $utf));
660
661 return $this->execute();
662 }
663
664 /**
665 * Disconnects the database.
666 *
667 * @return void
668 *
669 * @since 12.1
670 */
671 abstract public function disconnect();
672
673 /**
674 * Adds a function callable just before disconnecting the database. Parameter of the callable is $this JDatabaseDriver
675 *
676 * @param callable $callable Function to call in disconnect() method just before disconnecting from database
677 *
678 * @return void
679 *
680 * @since CMS 3.1.2
681 */
682 public function addDisconnectHandler($callable)
683 {
684 $this->disconnectHandlers[] = $callable;
685 }
686
687 /**
688 * Drops a table from the database.
689 *
690 * @param string $table The name of the database table to drop.
691 * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
692 *
693 * @return JDatabaseDriver Returns this object to support chaining.
694 *
695 * @since 11.4
696 * @throws RuntimeException
697 */
698 abstract public function dropTable($table, $ifExists = true);
699
700 /**
701 * Escapes a string for usage in an SQL statement.
702 *
703 * @param string $text The string to be escaped.
704 * @param boolean $extra Optional parameter to provide extra escaping.
705 *
706 * @return string The escaped string.
707 *
708 * @since 11.1
709 */
710 abstract public function escape($text, $extra = false);
711
712 /**
713 * Method to fetch a row from the result set cursor as an array.
714 *
715 * @param mixed $cursor The optional result set cursor from which to fetch the row.
716 *
717 * @return mixed Either the next row from the result set or false if there are no more rows.
718 *
719 * @since 11.1
720 */
721 abstract protected function fetchArray($cursor = null);
722
723 /**
724 * Method to fetch a row from the result set cursor as an associative array.
725 *
726 * @param mixed $cursor The optional result set cursor from which to fetch the row.
727 *
728 * @return mixed Either the next row from the result set or false if there are no more rows.
729 *
730 * @since 11.1
731 */
732 abstract protected function fetchAssoc($cursor = null);
733
734 /**
735 * Method to fetch a row from the result set cursor as an object.
736 *
737 * @param mixed $cursor The optional result set cursor from which to fetch the row.
738 * @param string $class The class name to use for the returned row object.
739 *
740 * @return mixed Either the next row from the result set or false if there are no more rows.
741 *
742 * @since 11.1
743 */
744 abstract protected function fetchObject($cursor = null, $class = 'stdClass');
745
746 /**
747 * Method to free up the memory used for the result set.
748 *
749 * @param mixed $cursor The optional result set cursor from which to fetch the row.
750 *
751 * @return void
752 *
753 * @since 11.1
754 */
755 abstract protected function freeResult($cursor = null);
756
757 /**
758 * Get the number of affected rows for the previous executed SQL statement.
759 *
760 * @return integer The number of affected rows.
761 *
762 * @since 11.1
763 */
764 abstract public function getAffectedRows();
765
766 /**
767 * Return the query string to alter the database character set.
768 *
769 * @param string $dbName The database name
770 *
771 * @return string The query that alter the database query string
772 *
773 * @since 12.2
774 */
775 public function getAlterDbCharacterSet($dbName)
776 {
777 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
778
779 return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`';
780 }
781
782 /**
783 * Get the query strings to alter the character set and collation of a table.
784 *
785 * @param string $tableName The name of the table
786 *
787 * @return string[] The queries required to alter the table's character set and collation
788 *
789 * @since CMS 3.5.0
790 */
791 public function getAlterTableCharacterSet($tableName)
792 {
793 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
794 $collation = $charset . '_unicode_ci';
795
796 $quotedTableName = $this->quoteName($tableName);
797
798 $queries = array();
799 $queries[] = "ALTER TABLE $quotedTableName CONVERT TO CHARACTER SET $charset COLLATE $collation";
800
801 /**
802 * We also need to convert each text column, modifying their character set and collation. This allows us to
803 * change, for example, a utf8_bin collated column to a utf8mb4_bin collated column.
804 */
805 $sql = "SHOW FULL COLUMNS FROM $quotedTableName";
806 $this->setQuery($sql);
807 $columns = $this->loadAssocList();
808 $columnMods = array();
809
810 if (is_array($columns))
811 {
812 foreach ($columns as $column)
813 {
814 // Make sure we are redefining only columns which do support a collation
815 $col = (object) $column;
816
817 if (empty($col->Collation))
818 {
819 continue;
820 }
821
822 // Default new collation: utf8_unicode_ci or utf8mb4_unicode_ci
823 $newCollation = $charset . '_unicode_ci';
824 $collationParts = explode('_', $col->Collation);
825
826 /**
827 * If the collation is in the form charset_collationType_ci or charset_collationType we have to change
828 * the charset but leave the collationType intact (e.g. utf8_bin must become utf8mb4_bin, NOT
829 * utf8mb4_general_ci).
830 */
831 if (count($collationParts) >= 2)
832 {
833 $ci = array_pop($collationParts);
834 $collationType = array_pop($collationParts);
835 $newCollation = $charset . '_' . $collationType . '_' . $ci;
836
837 /**
838 * When the last part of the old collation is not _ci we have a charset_collationType format,
839 * something like utf8_bin. Therefore the new collation only has *two* parts.
840 */
841 if ($ci != 'ci')
842 {
843 $newCollation = $charset . '_' . $ci;
844 }
845 }
846
847 // If the old and new collation is the same we don't have to change the collation type
848 if (strtolower($newCollation) == strtolower($col->Collation))
849 {
850 continue;
851 }
852
853 $null = $col->Null == 'YES' ? 'NULL' : 'NOT NULL';
854 $default = is_null($col->Default) ? '' : "DEFAULT '" . $this->q($col->Default) . "'";
855 $columnMods[] = "MODIFY COLUMN `{$col->Field}` {$col->Type} CHARACTER SET $charset COLLATE $newCollation $null $default";
856 }
857 }
858
859 if (count($columnMods))
860 {
861 $queries[] = "ALTER TABLE $quotedTableName " .
862 implode(',', $columnMods) .
863 " CHARACTER SET $charset COLLATE $collation";
864 }
865
866 return $queries;
867 }
868
869 /**
870 * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. Used
871 * when the server doesn't support UTF-8 Multibyte.
872 *
873 * @param string $query The query to convert
874 *
875 * @return string The converted query
876 */
877 public function convertUtf8mb4QueryToUtf8($query)
878 {
879 if ($this->hasUTF8mb4Support())
880 {
881 return $query;
882 }
883
884 // If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert
885 if (!preg_match('/^(ALTER|CREATE)\s+TABLE\s+/i', $query))
886 {
887 return $query;
888 }
889
890 // Replace utf8mb4 with utf8 if not within single or double quotes or name quotes
891 return preg_replace('/[`"\'][^`"\']*[`"\'](*SKIP)(*FAIL)|utf8mb4/i', 'utf8', $query);
892 }
893
894 /**
895 * Return the query string to create new Database.
896 * Each database driver, other than MySQL, need to override this member to return correct string.
897 *
898 * @param stdClass $options Object used to pass user and database name to database driver.
899 * This object must have "db_name" and "db_user" set.
900 * @param boolean $utf True if the database supports the UTF-8 character set.
901 *
902 * @return string The query that creates database
903 *
904 * @since 12.2
905 */
906 protected function getCreateDatabaseQuery($options, $utf)
907 {
908 if ($utf)
909 {
910 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
911 $collation = $charset . '_unicode_ci';
912
913 return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '` COLLATE `' . $collation . '`';
914 }
915
916 return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
917 }
918
919 /**
920 * Method to get the database collation in use by sampling a text field of a table in the database.
921 *
922 * @return mixed The collation in use by the database or boolean false if not supported.
923 *
924 * @since 11.1
925 */
926 abstract public function getCollation();
927
928 /**
929 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
930 * reporting this value please return an empty string.
931 *
932 * @return string
933 */
934 public function getConnectionCollation()
935 {
936 return '';
937 }
938
939 /**
940 * Method that provides access to the underlying database connection. Useful for when you need to call a
941 * proprietary method such as postgresql's lo_* methods.
942 *
943 * @return resource The underlying database connection resource.
944 *
945 * @since 11.1
946 */
947 public function getConnection()
948 {
949 return $this->connection;
950 }
951
952 /**
953 * Get the total number of SQL statements executed by the database driver.
954 *
955 * @return integer
956 *
957 * @since 11.1
958 */
959 public function getCount()
960 {
961 return $this->count;
962 }
963
964 /**
965 * Gets the name of the database used by this conneciton.
966 *
967 * @return string
968 *
969 * @since 11.4
970 */
971 protected function getDatabase()
972 {
973 return $this->_database;
974 }
975
976 /**
977 * Returns a PHP date() function compliant date format for the database driver.
978 *
979 * @return string The format string.
980 *
981 * @since 11.1
982 */
983 public function getDateFormat()
984 {
985 return 'Y-m-d H:i:s';
986 }
987
988 /**
989 * Get the database driver SQL statement log.
990 *
991 * @return array SQL statements executed by the database driver.
992 *
993 * @since 11.1
994 */
995 public function getLog()
996 {
997 return $this->log;
998 }
999
1000 /**
1001 * Get the database driver SQL statement log.
1002 *
1003 * @return array SQL statements executed by the database driver.
1004 *
1005 * @since CMS 3.1.2
1006 */
1007 public function getTimings()
1008 {
1009 return $this->timings;
1010 }
1011
1012 /**
1013 * Get the database driver SQL statement log.
1014 *
1015 * @return array SQL statements executed by the database driver.
1016 *
1017 * @since CMS 3.1.2
1018 */
1019 public function getCallStacks()
1020 {
1021 return $this->callStacks;
1022 }
1023
1024 /**
1025 * Get the minimum supported database version.
1026 *
1027 * @return string The minimum version number for the database driver.
1028 *
1029 * @since 12.1
1030 */
1031 public function getMinimum()
1032 {
1033 return static::$dbMinimum;
1034 }
1035
1036 /**
1037 * Get the null or zero representation of a timestamp for the database driver.
1038 *
1039 * @return string Null or zero representation of a timestamp.
1040 *
1041 * @since 11.1
1042 */
1043 public function getNullDate()
1044 {
1045 return $this->nullDate;
1046 }
1047
1048 /**
1049 * Get the number of returned rows for the previous executed SQL statement.
1050 *
1051 * @param resource $cursor An optional database cursor resource to extract the row count from.
1052 *
1053 * @return integer The number of returned rows.
1054 *
1055 * @since 11.1
1056 */
1057 abstract public function getNumRows($cursor = null);
1058
1059 /**
1060 * Get the common table prefix for the database driver.
1061 *
1062 * @return string The common database table prefix.
1063 *
1064 * @since 11.1
1065 */
1066 public function getPrefix()
1067 {
1068 return $this->tablePrefix;
1069 }
1070
1071 /**
1072 * Gets an exporter class object.
1073 *
1074 * @return JDatabaseExporter An exporter object.
1075 *
1076 * @since 12.1
1077 * @throws RuntimeException
1078 */
1079 public function getExporter()
1080 {
1081 // Derive the class name from the driver.
1082 $class = 'JDatabaseExporter' . ucfirst($this->name);
1083
1084 // Make sure we have an exporter class for this driver.
1085 if (!class_exists($class))
1086 {
1087 // If it doesn't exist we are at an impasse so throw an exception.
1088 throw new JDatabaseExceptionUnsupported('Database Exporter not found.');
1089 }
1090
1091 $o = new $class;
1092 $o->setDbo($this);
1093
1094 return $o;
1095 }
1096
1097 /**
1098 * Gets an importer class object.
1099 *
1100 * @return JDatabaseImporter An importer object.
1101 *
1102 * @since 12.1
1103 * @throws RuntimeException
1104 */
1105 public function getImporter()
1106 {
1107 // Derive the class name from the driver.
1108 $class = 'JDatabaseImporter' . ucfirst($this->name);
1109
1110 // Make sure we have an importer class for this driver.
1111 if (!class_exists($class))
1112 {
1113 // If it doesn't exist we are at an impasse so throw an exception.
1114 throw new JDatabaseExceptionUnsupported('Database Importer not found');
1115 }
1116
1117 $o = new $class;
1118 $o->setDbo($this);
1119
1120 return $o;
1121 }
1122
1123 /**
1124 * Get the name of the database driver. If $this->name is not set it will try guessing the driver name from the
1125 * class name.
1126 *
1127 * @return string
1128 *
1129 * @since CMS 3.5.0
1130 */
1131 public function getName()
1132 {
1133 if (empty($this->name))
1134 {
1135 $className = get_class($this);
1136 $className = str_replace('JDatabaseDriver', '', $className);
1137 $this->name = strtolower($className);
1138 }
1139
1140 return $this->name;
1141 }
1142
1143 /**
1144 * Get the server family type, e.g. mysql, postgresql, oracle, sqlite, mssql. If $this->serverType is not set it
1145 * will attempt guessing the server family type from the driver name. If this is not possible the driver name will
1146 * be returned instead.
1147 *
1148 * @return string
1149 *
1150 * @since CMS 3.5.0
1151 */
1152 public function getServerType()
1153 {
1154 if (empty($this->serverType))
1155 {
1156 $name = $this->getName();
1157
1158 if (stristr($name, 'mysql') !== false)
1159 {
1160 $this->serverType = 'mysql';
1161 }
1162 elseif (stristr($name, 'postgre') !== false)
1163 {
1164 $this->serverType = 'postgresql';
1165 }
1166 elseif (stristr($name, 'oracle') !== false)
1167 {
1168 $this->serverType = 'oracle';
1169 }
1170 elseif (stristr($name, 'sqlite') !== false)
1171 {
1172 $this->serverType = 'sqlite';
1173 }
1174 elseif (stristr($name, 'sqlsrv') !== false)
1175 {
1176 $this->serverType = 'mssql';
1177 }
1178 elseif (stristr($name, 'mssql') !== false)
1179 {
1180 $this->serverType = 'mssql';
1181 }
1182 else
1183 {
1184 $this->serverType = $name;
1185 }
1186 }
1187
1188 return $this->serverType;
1189 }
1190
1191 /**
1192 * Get the current query object or a new JDatabaseQuery object.
1193 *
1194 * @param boolean $new False to return the current query object, True to return a new JDatabaseQuery object.
1195 *
1196 * @return JDatabaseQuery The current query object or a new object extending the JDatabaseQuery class.
1197 *
1198 * @since 11.1
1199 * @throws RuntimeException
1200 */
1201 public function getQuery($new = false)
1202 {
1203 if ($new)
1204 {
1205 // Derive the class name from the driver.
1206 $class = 'JDatabaseQuery' . ucfirst($this->name);
1207
1208 // Make sure we have a query class for this driver.
1209 if (!class_exists($class))
1210 {
1211 // If it doesn't exist we are at an impasse so throw an exception.
1212 throw new JDatabaseExceptionUnsupported('Database Query Class not found.');
1213 }
1214
1215 return new $class($this);
1216 }
1217 else
1218 {
1219 return $this->sql;
1220 }
1221 }
1222
1223 /**
1224 * Get a new iterator on the current query.
1225 *
1226 * @param string $column An option column to use as the iterator key.
1227 * @param string $class The class of object that is returned.
1228 *
1229 * @return JDatabaseIterator A new database iterator.
1230 *
1231 * @since 12.1
1232 * @throws RuntimeException
1233 */
1234 public function getIterator($column = null, $class = 'stdClass')
1235 {
1236 // Derive the class name from the driver.
1237 $iteratorClass = 'JDatabaseIterator' . ucfirst($this->name);
1238
1239 // Make sure we have an iterator class for this driver.
1240 if (!class_exists($iteratorClass))
1241 {
1242 // If it doesn't exist we are at an impasse so throw an exception.
1243 throw new JDatabaseExceptionUnsupported(sprintf('class *%s* is not defined', $iteratorClass));
1244 }
1245
1246 // Return a new iterator
1247 return new $iteratorClass($this->execute(), $column, $class);
1248 }
1249
1250 /**
1251 * Retrieves field information about the given tables.
1252 *
1253 * @param string $table The name of the database table.
1254 * @param boolean $typeOnly True (default) to only return field types.
1255 *
1256 * @return array An array of fields by table.
1257 *
1258 * @since 11.1
1259 * @throws RuntimeException
1260 */
1261 abstract public function getTableColumns($table, $typeOnly = true);
1262
1263 /**
1264 * Shows the table CREATE statement that creates the given tables.
1265 *
1266 * @param mixed $tables A table name or a list of table names.
1267 *
1268 * @return array A list of the create SQL for the tables.
1269 *
1270 * @since 11.1
1271 * @throws RuntimeException
1272 */
1273 abstract public function getTableCreate($tables);
1274
1275 /**
1276 * Retrieves field information about the given tables.
1277 *
1278 * @param mixed $tables A table name or a list of table names.
1279 *
1280 * @return array An array of keys for the table(s).
1281 *
1282 * @since 11.1
1283 * @throws RuntimeException
1284 */
1285 abstract public function getTableKeys($tables);
1286
1287 /**
1288 * Method to get an array of all tables in the database.
1289 *
1290 * @return array An array of all the tables in the database.
1291 *
1292 * @since 11.1
1293 * @throws RuntimeException
1294 */
1295 abstract public function getTableList();
1296
1297 /**
1298 * Determine whether or not the database engine supports UTF-8 character encoding.
1299 *
1300 * @return boolean True if the database engine supports UTF-8 character encoding.
1301 *
1302 * @since 11.1
1303 * @deprecated 12.3 (Platform) & 4.0 (CMS) - Use hasUTFSupport() instead
1304 */
1305 public function getUTFSupport()
1306 {
1307 JLog::add('JDatabaseDriver::getUTFSupport() is deprecated. Use JDatabaseDriver::hasUTFSupport() instead.', JLog::WARNING, 'deprecated');
1308
1309 return $this->hasUTFSupport();
1310 }
1311
1312 /**
1313 * Determine whether or not the database engine supports UTF-8 character encoding.
1314 *
1315 * @return boolean True if the database engine supports UTF-8 character encoding.
1316 *
1317 * @since 12.1
1318 */
1319 public function hasUTFSupport()
1320 {
1321 return $this->utf;
1322 }
1323
1324 /**
1325 * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. This applies to
1326 * MySQL databases.
1327 *
1328 * @return boolean True if the database engine supports UTF-8 Multibyte.
1329 *
1330 * @since CMS 3.5.0
1331 */
1332 public function hasUTF8mb4Support()
1333 {
1334 return $this->utf8mb4;
1335 }
1336
1337 /**
1338 * Get the version of the database connector
1339 *
1340 * @return string The database connector version.
1341 *
1342 * @since 11.1
1343 */
1344 abstract public function getVersion();
1345
1346 /**
1347 * Method to get the auto-incremented value from the last INSERT statement.
1348 *
1349 * @return mixed The value of the auto-increment field from the last inserted row.
1350 *
1351 * @since 11.1
1352 */
1353 abstract public function insertid();
1354
1355 /**
1356 * Inserts a row into a table based on an object's properties.
1357 *
1358 * @param string $table The name of the database table to insert into.
1359 * @param object &$object A reference to an object whose public properties match the table fields.
1360 * @param string $key The name of the primary key. If provided the object property is updated.
1361 *
1362 * @return boolean True on success.
1363 *
1364 * @since 11.1
1365 * @throws RuntimeException
1366 */
1367 public function insertObject($table, &$object, $key = null)
1368 {
1369 $fields = array();
1370 $values = array();
1371
1372 // Iterate over the object variables to build the query fields and values.
1373 foreach (get_object_vars($object) as $k => $v)
1374 {
1375 // Only process non-null scalars.
1376 if (is_array($v) or is_object($v) or $v === null)
1377 {
1378 continue;
1379 }
1380
1381 // Ignore any internal fields.
1382 if ($k[0] == '_')
1383 {
1384 continue;
1385 }
1386
1387 // Prepare and sanitize the fields and values for the database query.
1388 $fields[] = $this->quoteName($k);
1389 $values[] = $this->quote($v);
1390 }
1391
1392 // Create the base insert statement.
1393 $query = $this->getQuery(true)
1394 ->insert($this->quoteName($table))
1395 ->columns($fields)
1396 ->values(implode(',', $values));
1397
1398 // Set the query and execute the insert.
1399 $this->setQuery($query);
1400
1401 if (!$this->execute())
1402 {
1403 return false;
1404 }
1405
1406 // Update the primary key if it exists.
1407 $id = $this->insertid();
1408
1409 if ($key && $id && is_string($key))
1410 {
1411 $object->$key = $id;
1412 }
1413
1414 return true;
1415 }
1416
1417 /**
1418 * Method to check whether the installed database version is supported by the database driver
1419 *
1420 * @return boolean True if the database version is supported
1421 *
1422 * @since 12.1
1423 */
1424 public function isMinimumVersion()
1425 {
1426 return version_compare($this->getVersion(), static::$dbMinimum) >= 0;
1427 }
1428
1429 /**
1430 * Method to get the first row of the result set from the database query as an associative array
1431 * of ['field_name' => 'row_value'].
1432 *
1433 * @return mixed The return value or null if the query failed.
1434 *
1435 * @since 11.1
1436 * @throws RuntimeException
1437 */
1438 public function loadAssoc()
1439 {
1440 $this->connect();
1441
1442 $ret = null;
1443
1444 // Execute the query and get the result set cursor.
1445 if (!($cursor = $this->execute()))
1446 {
1447 return;
1448 }
1449
1450 // Get the first row from the result set as an associative array.
1451 if ($array = $this->fetchAssoc($cursor))
1452 {
1453 $ret = $array;
1454 }
1455
1456 // Free up system resources and return.
1457 $this->freeResult($cursor);
1458
1459 return $ret;
1460 }
1461
1462 /**
1463 * Method to get an array of the result set rows from the database query where each row is an associative array
1464 * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to
1465 * a sequential numeric array.
1466 *
1467 * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted
1468 * behavior and should be avoided.
1469 *
1470 * @param string $key The name of a field on which to key the result array.
1471 * @param string $column An optional column name. Instead of the whole row, only this column value will be in
1472 * the result array.
1473 *
1474 * @return mixed The return value or null if the query failed.
1475 *
1476 * @since 11.1
1477 * @throws RuntimeException
1478 */
1479 public function loadAssocList($key = null, $column = null)
1480 {
1481 $this->connect();
1482
1483 $array = array();
1484
1485 // Execute the query and get the result set cursor.
1486 if (!($cursor = $this->execute()))
1487 {
1488 return;
1489 }
1490
1491 // Get all of the rows from the result set.
1492 while ($row = $this->fetchAssoc($cursor))
1493 {
1494 $value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row;
1495
1496 if ($key)
1497 {
1498 $array[$row[$key]] = $value;
1499 }
1500 else
1501 {
1502 $array[] = $value;
1503 }
1504 }
1505
1506 // Free up system resources and return.
1507 $this->freeResult($cursor);
1508
1509 return $array;
1510 }
1511
1512 /**
1513 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from
1514 * the database query.
1515 *
1516 * @param integer $offset The row offset to use to build the result array.
1517 *
1518 * @return mixed The return value or null if the query failed.
1519 *
1520 * @since 11.1
1521 * @throws RuntimeException
1522 */
1523 public function loadColumn($offset = 0)
1524 {
1525 $this->connect();
1526
1527 $array = array();
1528
1529 // Execute the query and get the result set cursor.
1530 if (!($cursor = $this->execute()))
1531 {
1532 return;
1533 }
1534
1535 // Get all of the rows from the result set as arrays.
1536 while ($row = $this->fetchArray($cursor))
1537 {
1538 $array[] = $row[$offset];
1539 }
1540
1541 // Free up system resources and return.
1542 $this->freeResult($cursor);
1543
1544 return $array;
1545 }
1546
1547 /**
1548 * Method to get the next row in the result set from the database query as an object.
1549 *
1550 * @param string $class The class name to use for the returned row object.
1551 *
1552 * @return mixed The result of the query as an array, false if there are no more rows.
1553 *
1554 * @since 11.1
1555 * @throws RuntimeException
1556 * @deprecated 12.3 (Platform) & 4.0 (CMS) - Use getIterator() instead
1557 */
1558 public function loadNextObject($class = 'stdClass')
1559 {
1560 JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
1561 $this->connect();
1562
1563 static $cursor = null;
1564
1565 // Execute the query and get the result set cursor.
1566 if (is_null($cursor))
1567 {
1568 if (!($cursor = $this->execute()))
1569 {
1570 return $this->errorNum ? null : false;
1571 }
1572 }
1573
1574 // Get the next row from the result set as an object of type $class.
1575 if ($row = $this->fetchObject($cursor, $class))
1576 {
1577 return $row;
1578 }
1579
1580 // Free up system resources and return.
1581 $this->freeResult($cursor);
1582 $cursor = null;
1583
1584 return false;
1585 }
1586
1587 /**
1588 * Method to get the next row in the result set from the database query as an array.
1589 *
1590 * @return mixed The result of the query as an array, false if there are no more rows.
1591 *
1592 * @since 11.1
1593 * @throws RuntimeException
1594 * @deprecated 4.0 (CMS) Use JDatabaseDriver::getIterator() instead
1595 */
1596 public function loadNextRow()
1597 {
1598 JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
1599 $this->connect();
1600
1601 static $cursor = null;
1602
1603 // Execute the query and get the result set cursor.
1604 if (is_null($cursor))
1605 {
1606 if (!($cursor = $this->execute()))
1607 {
1608 return $this->errorNum ? null : false;
1609 }
1610 }
1611
1612 // Get the next row from the result set as an object of type $class.
1613 if ($row = $this->fetchArray($cursor))
1614 {
1615 return $row;
1616 }
1617
1618 // Free up system resources and return.
1619 $this->freeResult($cursor);
1620 $cursor = null;
1621
1622 return false;
1623 }
1624
1625 /**
1626 * Method to get the first row of the result set from the database query as an object.
1627 *
1628 * @param string $class The class name to use for the returned row object.
1629 *
1630 * @return mixed The return value or null if the query failed.
1631 *
1632 * @since 11.1
1633 * @throws RuntimeException
1634 */
1635 public function loadObject($class = 'stdClass')
1636 {
1637 $this->connect();
1638
1639 $ret = null;
1640
1641 // Execute the query and get the result set cursor.
1642 if (!($cursor = $this->execute()))
1643 {
1644 return;
1645 }
1646
1647 // Get the first row from the result set as an object of type $class.
1648 if ($object = $this->fetchObject($cursor, $class))
1649 {
1650 $ret = $object;
1651 }
1652
1653 // Free up system resources and return.
1654 $this->freeResult($cursor);
1655
1656 return $ret;
1657 }
1658
1659 /**
1660 * Method to get an array of the result set rows from the database query where each row is an object. The array
1661 * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
1662 *
1663 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
1664 * behavior and should be avoided.
1665 *
1666 * @param string $key The name of a field on which to key the result array.
1667 * @param string $class The class name to use for the returned row objects.
1668 *
1669 * @return mixed The return value or null if the query failed.
1670 *
1671 * @since 11.1
1672 * @throws RuntimeException
1673 */
1674 public function loadObjectList($key = '', $class = 'stdClass')
1675 {
1676 $this->connect();
1677
1678 $array = array();
1679
1680 // Execute the query and get the result set cursor.
1681 if (!($cursor = $this->execute()))
1682 {
1683 return;
1684 }
1685
1686 // Get all of the rows from the result set as objects of type $class.
1687 while ($row = $this->fetchObject($cursor, $class))
1688 {
1689 if ($key)
1690 {
1691 $array[$row->$key] = $row;
1692 }
1693 else
1694 {
1695 $array[] = $row;
1696 }
1697 }
1698
1699 // Free up system resources and return.
1700 $this->freeResult($cursor);
1701
1702 return $array;
1703 }
1704
1705 /**
1706 * Method to get the first field of the first row of the result set from the database query.
1707 *
1708 * @return mixed The return value or null if the query failed.
1709 *
1710 * @since 11.1
1711 * @throws RuntimeException
1712 */
1713 public function loadResult()
1714 {
1715 $this->connect();
1716
1717 $ret = null;
1718
1719 // Execute the query and get the result set cursor.
1720 if (!($cursor = $this->execute()))
1721 {
1722 return;
1723 }
1724
1725 // Get the first row from the result set as an array.
1726 if ($row = $this->fetchArray($cursor))
1727 {
1728 $ret = $row[0];
1729 }
1730
1731 // Free up system resources and return.
1732 $this->freeResult($cursor);
1733
1734 return $ret;
1735 }
1736
1737 /**
1738 * Method to get the first row of the result set from the database query as an array. Columns are indexed
1739 * numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc.
1740 *
1741 * @return mixed The return value or null if the query failed.
1742 *
1743 * @since 11.1
1744 * @throws RuntimeException
1745 */
1746 public function loadRow()
1747 {
1748 $this->connect();
1749
1750 $ret = null;
1751
1752 // Execute the query and get the result set cursor.
1753 if (!($cursor = $this->execute()))
1754 {
1755 return;
1756 }
1757
1758 // Get the first row from the result set as an array.
1759 if ($row = $this->fetchArray($cursor))
1760 {
1761 $ret = $row;
1762 }
1763
1764 // Free up system resources and return.
1765 $this->freeResult($cursor);
1766
1767 return $ret;
1768 }
1769
1770 /**
1771 * Method to get an array of the result set rows from the database query where each row is an array. The array
1772 * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array.
1773 *
1774 * NOTE: Choosing to key the result array by a non-unique field can result in unwanted
1775 * behavior and should be avoided.
1776 *
1777 * @param integer $index The index of a field on which to key the result array.
1778 *
1779 * @return mixed The return value or null if the query failed.
1780 *
1781 * @since 11.1
1782 * @throws RuntimeException
1783 */
1784 public function loadRowList($index = null)
1785 {
1786 $this->connect();
1787
1788 $array = array();
1789
1790 // Execute the query and get the result set cursor.
1791 if (!($cursor = $this->execute()))
1792 {
1793 return;
1794 }
1795
1796 // Get all of the rows from the result set as arrays.
1797 while ($row = $this->fetchArray($cursor))
1798 {
1799 if ($index !== null)
1800 {
1801 $array[$row[$index]] = $row;
1802 }
1803 else
1804 {
1805 $array[] = $row;
1806 }
1807 }
1808
1809 // Free up system resources and return.
1810 $this->freeResult($cursor);
1811
1812 return $array;
1813 }
1814
1815 /**
1816 * Locks a table in the database.
1817 *
1818 * @param string $tableName The name of the table to unlock.
1819 *
1820 * @return JDatabaseDriver Returns this object to support chaining.
1821 *
1822 * @since 11.4
1823 * @throws RuntimeException
1824 */
1825 abstract public function lockTable($tableName);
1826
1827 /**
1828 * Quotes and optionally escapes a string to database requirements for use in database queries.
1829 *
1830 * @param mixed $text A string or an array of strings to quote.
1831 * @param boolean $escape True (default) to escape the string, false to leave it unchanged.
1832 *
1833 * @return string|array The quoted input.
1834 *
1835 * @note Accepting an array of strings was added in 12.3.
1836 * @since 11.1
1837 */
1838 public function quote($text, $escape = true)
1839 {
1840 if (is_array($text))
1841 {
1842 foreach ($text as $k => $v)
1843 {
1844 $text[$k] = $this->quote($v, $escape);
1845 }
1846
1847 return $text;
1848 }
1849 else
1850 {
1851 return '\'' . ($escape ? $this->escape($text) : $text) . '\'';
1852 }
1853 }
1854
1855 /**
1856 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
1857 * risks and reserved word conflicts.
1858 *
1859 * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
1860 * Each type supports dot-notation name.
1861 * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has to be
1862 * same length of $name; if is null there will not be any AS part for string or array element.
1863 *
1864 * @return mixed The quote wrapped name, same type of $name.
1865 *
1866 * @since 11.1
1867 */
1868 public function quoteName($name, $as = null)
1869 {
1870 if (is_string($name))
1871 {
1872 $quotedName = $this->quoteNameStr(explode('.', $name));
1873
1874 $quotedAs = '';
1875
1876 if (!is_null($as))
1877 {
1878 settype($as, 'array');
1879 $quotedAs .= ' AS ' . $this->quoteNameStr($as);
1880 }
1881
1882 return $quotedName . $quotedAs;
1883 }
1884 else
1885 {
1886 $fin = array();
1887
1888 if (is_null($as))
1889 {
1890 foreach ($name as $str)
1891 {
1892 $fin[] = $this->quoteName($str);
1893 }
1894 }
1895 elseif (is_array($name) && (count($name) == count($as)))
1896 {
1897 $count = count($name);
1898
1899 for ($i = 0; $i < $count; $i++)
1900 {
1901 $fin[] = $this->quoteName($name[$i], $as[$i]);
1902 }
1903 }
1904
1905 return $fin;
1906 }
1907 }
1908
1909 /**
1910 * Quote strings coming from quoteName call.
1911 *
1912 * @param array $strArr Array of strings coming from quoteName dot-explosion.
1913 *
1914 * @return string Dot-imploded string of quoted parts.
1915 *
1916 * @since 11.3
1917 */
1918 protected function quoteNameStr($strArr)
1919 {
1920 $parts = array();
1921 $q = $this->nameQuote;
1922
1923 foreach ($strArr as $part)
1924 {
1925 if (is_null($part))
1926 {
1927 continue;
1928 }
1929
1930 if (strlen($q) == 1)
1931 {
1932 $parts[] = $q . str_replace($q, $q . $q, $part) . $q;
1933 }
1934 else
1935 {
1936 $parts[] = $q{0} . str_replace($q{1}, $q{1} . $q{1}, $part) . $q{1};
1937 }
1938 }
1939
1940 return implode('.', $parts);
1941 }
1942
1943 /**
1944 * This function replaces a string identifier <var>$prefix</var> with the string held is the
1945 * <var>tablePrefix</var> class variable.
1946 *
1947 * @param string $sql The SQL statement to prepare.
1948 * @param string $prefix The common table prefix.
1949 *
1950 * @return string The processed SQL statement.
1951 *
1952 * @since 11.1
1953 */
1954 public function replacePrefix($sql, $prefix = '#__')
1955 {
1956 $startPos = 0;
1957 $literal = '';
1958
1959 $sql = trim($sql);
1960 $n = strlen($sql);
1961
1962 while ($startPos < $n)
1963 {
1964 $ip = strpos($sql, $prefix, $startPos);
1965
1966 if ($ip === false)
1967 {
1968 break;
1969 }
1970
1971 $j = strpos($sql, "'", $startPos);
1972 $k = strpos($sql, '"', $startPos);
1973
1974 if (($k !== false) && (($k < $j) || ($j === false)))
1975 {
1976 $quoteChar = '"';
1977 $j = $k;
1978 }
1979 else
1980 {
1981 $quoteChar = "'";
1982 }
1983
1984 if ($j === false)
1985 {
1986 $j = $n;
1987 }
1988
1989 $literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos));
1990 $startPos = $j;
1991
1992 $j = $startPos + 1;
1993
1994 if ($j >= $n)
1995 {
1996 break;
1997 }
1998
1999 // Quote comes first, find end of quote
2000 while (true)
2001 {
2002 $k = strpos($sql, $quoteChar, $j);
2003 $escaped = false;
2004
2005 if ($k === false)
2006 {
2007 break;
2008 }
2009
2010 $l = $k - 1;
2011
2012 while ($l >= 0 && $sql{$l} == '\\')
2013 {
2014 $l--;
2015 $escaped = !$escaped;
2016 }
2017
2018 if ($escaped)
2019 {
2020 $j = $k + 1;
2021 continue;
2022 }
2023
2024 break;
2025 }
2026
2027 if ($k === false)
2028 {
2029 // Error in the query - no end quote; ignore it
2030 break;
2031 }
2032
2033 $literal .= substr($sql, $startPos, $k - $startPos + 1);
2034 $startPos = $k + 1;
2035 }
2036
2037 if ($startPos < $n)
2038 {
2039 $literal .= substr($sql, $startPos, $n - $startPos);
2040 }
2041
2042 return $literal;
2043 }
2044
2045 /**
2046 * Renames a table in the database.
2047 *
2048 * @param string $oldTable The name of the table to be renamed
2049 * @param string $newTable The new name for the table.
2050 * @param string $backup Table prefix
2051 * @param string $prefix For the table - used to rename constraints in non-mysql databases
2052 *
2053 * @return JDatabaseDriver Returns this object to support chaining.
2054 *
2055 * @since 11.4
2056 * @throws RuntimeException
2057 */
2058 abstract public function renameTable($oldTable, $newTable, $backup = null, $prefix = null);
2059
2060 /**
2061 * Select a database for use.
2062 *
2063 * @param string $database The name of the database to select for use.
2064 *
2065 * @return boolean True if the database was successfully selected.
2066 *
2067 * @since 11.1
2068 * @throws RuntimeException
2069 */
2070 abstract public function select($database);
2071
2072 /**
2073 * Sets the database debugging state for the driver.
2074 *
2075 * @param boolean $level True to enable debugging.
2076 *
2077 * @return boolean The old debugging level.
2078 *
2079 * @since 11.1
2080 */
2081 public function setDebug($level)
2082 {
2083 $previous = $this->debug;
2084 $this->debug = (bool) $level;
2085
2086 return $previous;
2087 }
2088
2089 /**
2090 * Sets the SQL statement string for later execution.
2091 *
2092 * @param mixed $query The SQL statement to set either as a JDatabaseQuery object or a string.
2093 * @param integer $offset The affected row offset to set.
2094 * @param integer $limit The maximum affected rows to set.
2095 *
2096 * @return JDatabaseDriver This object to support method chaining.
2097 *
2098 * @since 11.1
2099 */
2100 public function setQuery($query, $offset = 0, $limit = 0)
2101 {
2102 $this->sql = $query;
2103
2104 if ($query instanceof JDatabaseQueryLimitable)
2105 {
2106 if (!$limit && $query->limit)
2107 {
2108 $limit = $query->limit;
2109 }
2110
2111 if (!$offset && $query->offset)
2112 {
2113 $offset = $query->offset;
2114 }
2115
2116 $query->setLimit($limit, $offset);
2117 }
2118 else
2119 {
2120 $this->limit = (int) max(0, $limit);
2121 $this->offset = (int) max(0, $offset);
2122 }
2123
2124 return $this;
2125 }
2126
2127 /**
2128 * Set the connection to use UTF-8 character encoding.
2129 *
2130 * @return boolean True on success.
2131 *
2132 * @since 11.1
2133 */
2134 abstract public function setUtf();
2135
2136 /**
2137 * Method to commit a transaction.
2138 *
2139 * @param boolean $toSavepoint If true, commit to the last savepoint.
2140 *
2141 * @return void
2142 *
2143 * @since 11.1
2144 * @throws RuntimeException
2145 */
2146 abstract public function transactionCommit($toSavepoint = false);
2147
2148 /**
2149 * Method to roll back a transaction.
2150 *
2151 * @param boolean $toSavepoint If true, rollback to the last savepoint.
2152 *
2153 * @return void
2154 *
2155 * @since 11.1
2156 * @throws RuntimeException
2157 */
2158 abstract public function transactionRollback($toSavepoint = false);
2159
2160 /**
2161 * Method to initialize a transaction.
2162 *
2163 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
2164 *
2165 * @return void
2166 *
2167 * @since 11.1
2168 * @throws RuntimeException
2169 */
2170 abstract public function transactionStart($asSavepoint = false);
2171
2172 /**
2173 * Method to truncate a table.
2174 *
2175 * @param string $table The table to truncate
2176 *
2177 * @return void
2178 *
2179 * @since 11.3
2180 * @throws RuntimeException
2181 */
2182 public function truncateTable($table)
2183 {
2184 $this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table));
2185 $this->execute();
2186 }
2187
2188 /**
2189 * Updates a row in a table based on an object's properties.
2190 *
2191 * @param string $table The name of the database table to update.
2192 * @param object &$object A reference to an object whose public properties match the table fields.
2193 * @param array $key The name of the primary key.
2194 * @param boolean $nulls True to update null fields or false to ignore them.
2195 *
2196 * @return boolean True on success.
2197 *
2198 * @since 11.1
2199 * @throws RuntimeException
2200 */
2201 public function updateObject($table, &$object, $key, $nulls = false)
2202 {
2203 $fields = array();
2204 $where = array();
2205
2206 if (is_string($key))
2207 {
2208 $key = array($key);
2209 }
2210
2211 if (is_object($key))
2212 {
2213 $key = (array) $key;
2214 }
2215
2216 // Create the base update statement.
2217 $statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';
2218
2219 // Iterate over the object variables to build the query fields/value pairs.
2220 foreach (get_object_vars($object) as $k => $v)
2221 {
2222 // Only process scalars that are not internal fields.
2223 if (is_array($v) || is_object($v) || $k[0] === '_')
2224 {
2225 continue;
2226 }
2227
2228 // Set the primary key to the WHERE clause instead of a field to update.
2229 if (in_array($k, $key))
2230 {
2231 $where[] = $this->quoteName($k) . ($v === null ? ' IS NULL' : ' = ' . $this->quote($v));
2232 continue;
2233 }
2234
2235 // Prepare and sanitize the fields and values for the database query.
2236 if ($v === null)
2237 {
2238 // If the value is null and we want to update nulls then set it.
2239 if ($nulls)
2240 {
2241 $val = 'NULL';
2242 }
2243 // If the value is null and we do not want to update nulls then ignore this field.
2244 else
2245 {
2246 continue;
2247 }
2248 }
2249 // The field is not null so we prep it for update.
2250 else
2251 {
2252 $val = $this->quote($v);
2253 }
2254
2255 // Add the field to be updated.
2256 $fields[] = $this->quoteName($k) . '=' . $val;
2257 }
2258
2259 // We don't have any fields to update.
2260 if (empty($fields))
2261 {
2262 return true;
2263 }
2264
2265 // Set the query and execute the update.
2266 $this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where)));
2267
2268 return $this->execute();
2269 }
2270
2271 /**
2272 * Execute the SQL statement.
2273 *
2274 * @return mixed A database cursor resource on success, boolean false on failure.
2275 *
2276 * @since 12.1
2277 * @throws RuntimeException
2278 */
2279 abstract public function execute();
2280
2281 /**
2282 * Unlocks tables in the database.
2283 *
2284 * @return JDatabaseDriver Returns this object to support chaining.
2285 *
2286 * @since 11.4
2287 * @throws RuntimeException
2288 */
2289 abstract public function unlockTables();
2290 }
2291