1<?php
2/**
3 * @package     Joomla.Platform
4 * @subpackage  Database
5 *
6 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
7 * @license     GNU General Public License version 2 or later; see LICENSE
8 */
9
10defined('JPATH_PLATFORM') or die;
11
12/**
13 * Joomla Platform Database Driver Class
14 *
15 * @since  3.0.0
16 *
17 * @method   string|array  q()   q($text, $escape = true)  Alias for quote method
18 * @method   string|array  qn()  qn($name, $as = null)     Alias for quoteName method
19 */
20abstract class JDatabaseDriver extends JDatabase implements JDatabaseInterface
21{
22	/**
23	 * The name of the database.
24	 *
25	 * @var    string
26	 * @since  2.5.0
27	 */
28	private $_database;
29
30	/**
31	 * The name of the database driver.
32	 *
33	 * @var    string
34	 * @since  1.7.0
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  1.7.0
50	 */
51	protected $connection;
52
53	/**
54	 * @var    integer  The number of SQL statements executed by the database driver.
55	 * @since  1.7.0
56	 */
57	protected $count = 0;
58
59	/**
60	 * @var    resource  The database connection cursor from the last query.
61	 * @since  1.7.0
62	 */
63	protected $cursor;
64
65	/**
66	 * @var    boolean  The database driver debugging state.
67	 * @since  1.7.0
68	 */
69	protected $debug = false;
70
71	/**
72	 * @var    integer  The affected row limit for the current SQL statement.
73	 * @since  1.7.0
74	 */
75	protected $limit = 0;
76
77	/**
78	 * @var    array  The log of executed SQL statements by the database driver.
79	 * @since  1.7.0
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  1.7.0
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  1.7.0
108	 */
109	protected $nullDate;
110
111	/**
112	 * @var    integer  The affected row offset to apply for the current SQL statement.
113	 * @since  1.7.0
114	 */
115	protected $offset = 0;
116
117	/**
118	 * @var    array  Passed in upon instantiation and saved.
119	 * @since  1.7.0
120	 */
121	protected $options;
122
123	/**
124	 * @var    JDatabaseQuery|string  The current SQL statement to execute.
125	 * @since  1.7.0
126	 */
127	protected $sql;
128
129	/**
130	 * @var    string  The common database table prefix.
131	 * @since  1.7.0
132	 */
133	protected $tablePrefix;
134
135	/**
136	 * @var    boolean  True if the database engine supports UTF-8 character encoding.
137	 * @since  1.7.0
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       1.7.0
150	 * @deprecated  3.0.0
151	 */
152	protected $errorNum = 0;
153
154	/**
155	 * @var         string  The database error message
156	 * @since       1.7.0
157	 * @deprecated  3.0.0
158	 */
159	protected $errorMsg;
160
161	/**
162	 * @var    array  JDatabaseDriver instances container.
163	 * @since  1.7.0
164	 */
165	protected static $instances = array();
166
167	/**
168	 * @var    string  The minimum supported database version.
169	 * @since  3.0.0
170	 */
171	protected static $dbMinimum;
172
173	/**
174	 * @var    integer  The depth of the current transaction.
175	 * @since  3.1.4
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   1.7.0
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   1.7.0
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   1.7.0
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   1.7.0
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   1.7.0
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   3.0.1
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   3.0.0
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   1.7.0
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   3.0.1
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	 * Destructor.
666	 *
667	 * @since   3.8.0
668	 */
669	public function __destruct()
670	{
671		$this->disconnect();
672	}
673
674	/**
675	 * Disconnects the database.
676	 *
677	 * @return  void
678	 *
679	 * @since   3.0.0
680	 */
681	abstract public function disconnect();
682
683	/**
684	 * Adds a function callable just before disconnecting the database. Parameter of the callable is $this JDatabaseDriver
685	 *
686	 * @param   callable  $callable  Function to call in disconnect() method just before disconnecting from database
687	 *
688	 * @return  void
689	 *
690	 * @since   CMS 3.1.2
691	 */
692	public function addDisconnectHandler($callable)
693	{
694		$this->disconnectHandlers[] = $callable;
695	}
696
697	/**
698	 * Drops a table from the database.
699	 *
700	 * @param   string   $table     The name of the database table to drop.
701	 * @param   boolean  $ifExists  Optionally specify that the table must exist before it is dropped.
702	 *
703	 * @return  JDatabaseDriver     Returns this object to support chaining.
704	 *
705	 * @since   2.5.0
706	 * @throws  RuntimeException
707	 */
708	abstract public function dropTable($table, $ifExists = true);
709
710	/**
711	 * Escapes a string for usage in an SQL statement.
712	 *
713	 * @param   string   $text   The string to be escaped.
714	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
715	 *
716	 * @return  string   The escaped string.
717	 *
718	 * @since   1.7.0
719	 */
720	abstract public function escape($text, $extra = false);
721
722	/**
723	 * Method to fetch a row from the result set cursor as an array.
724	 *
725	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
726	 *
727	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
728	 *
729	 * @since   1.7.0
730	 */
731	abstract protected function fetchArray($cursor = null);
732
733	/**
734	 * Method to fetch a row from the result set cursor as an associative array.
735	 *
736	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
737	 *
738	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
739	 *
740	 * @since   1.7.0
741	 */
742	abstract protected function fetchAssoc($cursor = null);
743
744	/**
745	 * Method to fetch a row from the result set cursor as an object.
746	 *
747	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
748	 * @param   string  $class   The class name to use for the returned row object.
749	 *
750	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
751	 *
752	 * @since   1.7.0
753	 */
754	abstract protected function fetchObject($cursor = null, $class = 'stdClass');
755
756	/**
757	 * Method to free up the memory used for the result set.
758	 *
759	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
760	 *
761	 * @return  void
762	 *
763	 * @since   1.7.0
764	 */
765	abstract protected function freeResult($cursor = null);
766
767	/**
768	 * Get the number of affected rows for the previous executed SQL statement.
769	 *
770	 * @return  integer  The number of affected rows.
771	 *
772	 * @since   1.7.0
773	 */
774	abstract public function getAffectedRows();
775
776	/**
777	 * Return the query string to alter the database character set.
778	 *
779	 * @param   string  $dbName  The database name
780	 *
781	 * @return  string  The query that alter the database query string
782	 *
783	 * @since   3.0.1
784	 */
785	public function getAlterDbCharacterSet($dbName)
786	{
787		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
788
789		return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`';
790	}
791
792	/**
793	 * Get the query strings to alter the character set and collation of a table.
794	 *
795	 * @param   string  $tableName  The name of the table
796	 *
797	 * @return  string[]  The queries required to alter the table's character set and collation
798	 *
799	 * @since   CMS 3.5.0
800	 */
801	public function getAlterTableCharacterSet($tableName)
802	{
803		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
804		$collation = $charset . '_unicode_ci';
805
806		$quotedTableName = $this->quoteName($tableName);
807
808		$queries = array();
809		$queries[] = "ALTER TABLE $quotedTableName CONVERT TO CHARACTER SET $charset COLLATE $collation";
810
811		/**
812		 * We also need to convert each text column, modifying their character set and collation. This allows us to
813		 * change, for example, a utf8_bin collated column to a utf8mb4_bin collated column.
814		 */
815		$sql = "SHOW FULL COLUMNS FROM $quotedTableName";
816		$this->setQuery($sql);
817		$columns = $this->loadAssocList();
818		$columnMods = array();
819
820		if (is_array($columns))
821		{
822			foreach ($columns as $column)
823			{
824				// Make sure we are redefining only columns which do support a collation
825				$col = (object) $column;
826
827				if (empty($col->Collation))
828				{
829					continue;
830				}
831
832				// Default new collation: utf8_unicode_ci or utf8mb4_unicode_ci
833				$newCollation = $charset . '_unicode_ci';
834				$collationParts = explode('_', $col->Collation);
835
836				/**
837				 * If the collation is in the form charset_collationType_ci or charset_collationType we have to change
838				 * the charset but leave the collationType intact (e.g. utf8_bin must become utf8mb4_bin, NOT
839				 * utf8mb4_general_ci).
840				 */
841				if (count($collationParts) >= 2)
842				{
843					$ci = array_pop($collationParts);
844					$collationType = array_pop($collationParts);
845					$newCollation = $charset . '_' . $collationType . '_' . $ci;
846
847					/**
848					 * When the last part of the old collation is not _ci we have a charset_collationType format,
849					 * something like utf8_bin. Therefore the new collation only has *two* parts.
850					 */
851					if ($ci != 'ci')
852					{
853						$newCollation = $charset . '_' . $ci;
854					}
855				}
856
857				// If the old and new collation is the same we don't have to change the collation type
858				if (strtolower($newCollation) == strtolower($col->Collation))
859				{
860					continue;
861				}
862
863				$null = $col->Null == 'YES' ? 'NULL' : 'NOT NULL';
864				$default = is_null($col->Default) ? '' : "DEFAULT '" . $this->q($col->Default) . "'";
865				$columnMods[] = "MODIFY COLUMN `{$col->Field}` {$col->Type} CHARACTER SET $charset COLLATE $newCollation $null $default";
866			}
867		}
868
869		if (count($columnMods))
870		{
871			$queries[] = "ALTER TABLE $quotedTableName " .
872				implode(',', $columnMods) .
873				" CHARACTER SET $charset COLLATE $collation";
874		}
875
876		return $queries;
877	}
878
879	/**
880	 * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. Used
881	 * when the server doesn't support UTF-8 Multibyte.
882	 *
883	 * @param   string  $query  The query to convert
884	 *
885	 * @return  string  The converted query
886	 */
887	public function convertUtf8mb4QueryToUtf8($query)
888	{
889		if ($this->hasUTF8mb4Support())
890		{
891			return $query;
892		}
893
894		// If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert
895		if (!preg_match('/^(ALTER|CREATE)\s+TABLE\s+/i', $query))
896		{
897			return $query;
898		}
899
900		// Don't do preg replacement if string does not exist
901		if (stripos($query, 'utf8mb4') === false)
902		{
903			return $query;
904		}
905
906		// Replace utf8mb4 with utf8 if not within single or double quotes or name quotes
907		return preg_replace('/[`"\'][^`"\']*[`"\'](*SKIP)(*FAIL)|utf8mb4/i', 'utf8', $query);
908	}
909
910	/**
911	 * Return the query string to create new Database.
912	 * Each database driver, other than MySQL, need to override this member to return correct string.
913	 *
914	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
915	 *                   This object must have "db_name" and "db_user" set.
916	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
917	 *
918	 * @return  string  The query that creates database
919	 *
920	 * @since   3.0.1
921	 */
922	protected function getCreateDatabaseQuery($options, $utf)
923	{
924		if ($utf)
925		{
926			$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
927			$collation = $charset . '_unicode_ci';
928
929			return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '` COLLATE `' . $collation . '`';
930		}
931
932		return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
933	}
934
935	/**
936	 * Method to get the database collation in use by sampling a text field of a table in the database.
937	 *
938	 * @return  mixed  The collation in use by the database or boolean false if not supported.
939	 *
940	 * @since   1.7.0
941	 */
942	abstract public function getCollation();
943
944	/**
945	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
946	 * reporting this value please return an empty string.
947	 *
948	 * @return  string
949	 */
950	public function getConnectionCollation()
951	{
952		return '';
953	}
954
955	/**
956	 * Method that provides access to the underlying database connection. Useful for when you need to call a
957	 * proprietary method such as postgresql's lo_* methods.
958	 *
959	 * @return  resource  The underlying database connection resource.
960	 *
961	 * @since   1.7.0
962	 */
963	public function getConnection()
964	{
965		return $this->connection;
966	}
967
968	/**
969	 * Get the total number of SQL statements executed by the database driver.
970	 *
971	 * @return  integer
972	 *
973	 * @since   1.7.0
974	 */
975	public function getCount()
976	{
977		return $this->count;
978	}
979
980	/**
981	 * Gets the name of the database used by this connection.
982	 *
983	 * @return  string
984	 *
985	 * @since   2.5.0
986	 */
987	protected function getDatabase()
988	{
989		return $this->_database;
990	}
991
992	/**
993	 * Returns a PHP date() function compliant date format for the database driver.
994	 *
995	 * @return  string  The format string.
996	 *
997	 * @since   1.7.0
998	 */
999	public function getDateFormat()
1000	{
1001		return 'Y-m-d H:i:s';
1002	}
1003
1004	/**
1005	 * Get the database driver SQL statement log.
1006	 *
1007	 * @return  array  SQL statements executed by the database driver.
1008	 *
1009	 * @since   1.7.0
1010	 */
1011	public function getLog()
1012	{
1013		return $this->log;
1014	}
1015
1016	/**
1017	 * Get the database driver SQL statement log.
1018	 *
1019	 * @return  array  SQL statements executed by the database driver.
1020	 *
1021	 * @since   CMS 3.1.2
1022	 */
1023	public function getTimings()
1024	{
1025		return $this->timings;
1026	}
1027
1028	/**
1029	 * Get the database driver SQL statement log.
1030	 *
1031	 * @return  array  SQL statements executed by the database driver.
1032	 *
1033	 * @since   CMS 3.1.2
1034	 */
1035	public function getCallStacks()
1036	{
1037		return $this->callStacks;
1038	}
1039
1040	/**
1041	 * Get the minimum supported database version.
1042	 *
1043	 * @return  string  The minimum version number for the database driver.
1044	 *
1045	 * @since   3.0.0
1046	 */
1047	public function getMinimum()
1048	{
1049		return static::$dbMinimum;
1050	}
1051
1052	/**
1053	 * Get the null or zero representation of a timestamp for the database driver.
1054	 *
1055	 * @return  string  Null or zero representation of a timestamp.
1056	 *
1057	 * @since   1.7.0
1058	 */
1059	public function getNullDate()
1060	{
1061		return $this->nullDate;
1062	}
1063
1064	/**
1065	 * Get the number of returned rows for the previous executed SQL statement.
1066	 *
1067	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
1068	 *
1069	 * @return  integer   The number of returned rows.
1070	 *
1071	 * @since   1.7.0
1072	 */
1073	abstract public function getNumRows($cursor = null);
1074
1075	/**
1076	 * Get the common table prefix for the database driver.
1077	 *
1078	 * @return  string  The common database table prefix.
1079	 *
1080	 * @since   1.7.0
1081	 */
1082	public function getPrefix()
1083	{
1084		return $this->tablePrefix;
1085	}
1086
1087	/**
1088	 * Gets an exporter class object.
1089	 *
1090	 * @return  JDatabaseExporter  An exporter object.
1091	 *
1092	 * @since   3.0.0
1093	 * @throws  RuntimeException
1094	 */
1095	public function getExporter()
1096	{
1097		// Derive the class name from the driver.
1098		$class = 'JDatabaseExporter' . ucfirst($this->name);
1099
1100		// Make sure we have an exporter class for this driver.
1101		if (!class_exists($class))
1102		{
1103			// If it doesn't exist we are at an impasse so throw an exception.
1104			throw new JDatabaseExceptionUnsupported('Database Exporter not found.');
1105		}
1106
1107		$o = new $class;
1108		$o->setDbo($this);
1109
1110		return $o;
1111	}
1112
1113	/**
1114	 * Gets an importer class object.
1115	 *
1116	 * @return  JDatabaseImporter  An importer object.
1117	 *
1118	 * @since   3.0.0
1119	 * @throws  RuntimeException
1120	 */
1121	public function getImporter()
1122	{
1123		// Derive the class name from the driver.
1124		$class = 'JDatabaseImporter' . ucfirst($this->name);
1125
1126		// Make sure we have an importer class for this driver.
1127		if (!class_exists($class))
1128		{
1129			// If it doesn't exist we are at an impasse so throw an exception.
1130			throw new JDatabaseExceptionUnsupported('Database Importer not found');
1131		}
1132
1133		$o = new $class;
1134		$o->setDbo($this);
1135
1136		return $o;
1137	}
1138
1139	/**
1140	 * Get the name of the database driver. If $this->name is not set it will try guessing the driver name from the
1141	 * class name.
1142	 *
1143	 * @return  string
1144	 *
1145	 * @since   CMS 3.5.0
1146	 */
1147	public function getName()
1148	{
1149		if (empty($this->name))
1150		{
1151			$className = get_class($this);
1152			$className = str_replace('JDatabaseDriver', '', $className);
1153			$this->name = strtolower($className);
1154		}
1155
1156		return $this->name;
1157	}
1158
1159	/**
1160	 * Get the server family type, e.g. mysql, postgresql, oracle, sqlite, mssql. If $this->serverType is not set it
1161	 * will attempt guessing the server family type from the driver name. If this is not possible the driver name will
1162	 * be returned instead.
1163	 *
1164	 * @return  string
1165	 *
1166	 * @since   CMS 3.5.0
1167	 */
1168	public function getServerType()
1169	{
1170		if (empty($this->serverType))
1171		{
1172			$name = $this->getName();
1173
1174			if (stristr($name, 'mysql') !== false)
1175			{
1176				$this->serverType = 'mysql';
1177			}
1178			elseif (stristr($name, 'postgre') !== false)
1179			{
1180				$this->serverType = 'postgresql';
1181			}
1182			elseif (stristr($name, 'pgsql') !== false)
1183			{
1184				$this->serverType = 'postgresql';
1185			}
1186			elseif (stristr($name, 'oracle') !== false)
1187			{
1188				$this->serverType = 'oracle';
1189			}
1190			elseif (stristr($name, 'sqlite') !== false)
1191			{
1192				$this->serverType = 'sqlite';
1193			}
1194			elseif (stristr($name, 'sqlsrv') !== false)
1195			{
1196				$this->serverType = 'mssql';
1197			}
1198			elseif (stristr($name, 'mssql') !== false)
1199			{
1200				$this->serverType = 'mssql';
1201			}
1202			else
1203			{
1204				$this->serverType = $name;
1205			}
1206		}
1207
1208		return $this->serverType;
1209	}
1210
1211	/**
1212	 * Get the current query object or a new JDatabaseQuery object.
1213	 *
1214	 * @param   boolean  $new  False to return the current query object, True to return a new JDatabaseQuery object.
1215	 *
1216	 * @return  JDatabaseQuery  The current query object or a new object extending the JDatabaseQuery class.
1217	 *
1218	 * @since   1.7.0
1219	 * @throws  RuntimeException
1220	 */
1221	public function getQuery($new = false)
1222	{
1223		if ($new)
1224		{
1225			// Derive the class name from the driver.
1226			$class = 'JDatabaseQuery' . ucfirst($this->name);
1227
1228			// Make sure we have a query class for this driver.
1229			if (!class_exists($class))
1230			{
1231				// If it doesn't exist we are at an impasse so throw an exception.
1232				throw new JDatabaseExceptionUnsupported('Database Query Class not found.');
1233			}
1234
1235			return new $class($this);
1236		}
1237		else
1238		{
1239			return $this->sql;
1240		}
1241	}
1242
1243	/**
1244	 * Get a new iterator on the current query.
1245	 *
1246	 * @param   string  $column  An option column to use as the iterator key.
1247	 * @param   string  $class   The class of object that is returned.
1248	 *
1249	 * @return  JDatabaseIterator  A new database iterator.
1250	 *
1251	 * @since   3.0.0
1252	 * @throws  RuntimeException
1253	 */
1254	public function getIterator($column = null, $class = 'stdClass')
1255	{
1256		// Derive the class name from the driver.
1257		$iteratorClass = 'JDatabaseIterator' . ucfirst($this->name);
1258
1259		// Make sure we have an iterator class for this driver.
1260		if (!class_exists($iteratorClass))
1261		{
1262			// If it doesn't exist we are at an impasse so throw an exception.
1263			throw new JDatabaseExceptionUnsupported(sprintf('class *%s* is not defined', $iteratorClass));
1264		}
1265
1266		// Return a new iterator
1267		return new $iteratorClass($this->execute(), $column, $class);
1268	}
1269
1270	/**
1271	 * Retrieves field information about the given tables.
1272	 *
1273	 * @param   string   $table     The name of the database table.
1274	 * @param   boolean  $typeOnly  True (default) to only return field types.
1275	 *
1276	 * @return  array  An array of fields by table.
1277	 *
1278	 * @since   1.7.0
1279	 * @throws  RuntimeException
1280	 */
1281	abstract public function getTableColumns($table, $typeOnly = true);
1282
1283	/**
1284	 * Shows the table CREATE statement that creates the given tables.
1285	 *
1286	 * @param   mixed  $tables  A table name or a list of table names.
1287	 *
1288	 * @return  array  A list of the create SQL for the tables.
1289	 *
1290	 * @since   1.7.0
1291	 * @throws  RuntimeException
1292	 */
1293	abstract public function getTableCreate($tables);
1294
1295	/**
1296	 * Retrieves keys information about the given table.
1297	 *
1298	 * @param   string  $table  The name of the table.
1299	 *
1300	 * @return  array   An array of keys for the table.
1301	 *
1302	 * @since   1.7.0
1303	 * @throws  RuntimeException
1304	 */
1305	abstract public function getTableKeys($table);
1306
1307	/**
1308	 * Method to get an array of all tables in the database.
1309	 *
1310	 * @return  array  An array of all the tables in the database.
1311	 *
1312	 * @since   1.7.0
1313	 * @throws  RuntimeException
1314	 */
1315	abstract public function getTableList();
1316
1317	/**
1318	 * Determine whether or not the database engine supports UTF-8 character encoding.
1319	 *
1320	 * @return  boolean  True if the database engine supports UTF-8 character encoding.
1321	 *
1322	 * @since   1.7.0
1323	 * @deprecated 4.0 - Use hasUTFSupport() instead
1324	 */
1325	public function getUTFSupport()
1326	{
1327		JLog::add('JDatabaseDriver::getUTFSupport() is deprecated. Use JDatabaseDriver::hasUTFSupport() instead.', JLog::WARNING, 'deprecated');
1328
1329		return $this->hasUTFSupport();
1330	}
1331
1332	/**
1333	 * Determine whether or not the database engine supports UTF-8 character encoding.
1334	 *
1335	 * @return  boolean  True if the database engine supports UTF-8 character encoding.
1336	 *
1337	 * @since   3.0.0
1338	 */
1339	public function hasUTFSupport()
1340	{
1341		return $this->utf;
1342	}
1343
1344	/**
1345	 * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. This applies to
1346	 * MySQL databases.
1347	 *
1348	 * @return  boolean  True if the database engine supports UTF-8 Multibyte.
1349	 *
1350	 * @since   CMS 3.5.0
1351	 */
1352	public function hasUTF8mb4Support()
1353	{
1354		return $this->utf8mb4;
1355	}
1356
1357	/**
1358	 * Get the version of the database connector
1359	 *
1360	 * @return  string  The database connector version.
1361	 *
1362	 * @since   1.7.0
1363	 */
1364	abstract public function getVersion();
1365
1366	/**
1367	 * Method to get the auto-incremented value from the last INSERT statement.
1368	 *
1369	 * @return  mixed  The value of the auto-increment field from the last inserted row.
1370	 *
1371	 * @since   1.7.0
1372	 */
1373	abstract public function insertid();
1374
1375	/**
1376	 * Inserts a row into a table based on an object's properties.
1377	 *
1378	 * @param   string  $table    The name of the database table to insert into.
1379	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
1380	 * @param   string  $key      The name of the primary key. If provided the object property is updated.
1381	 *
1382	 * @return  boolean    True on success.
1383	 *
1384	 * @since   1.7.0
1385	 * @throws  RuntimeException
1386	 */
1387	public function insertObject($table, &$object, $key = null)
1388	{
1389		$fields = array();
1390		$values = array();
1391
1392		// Iterate over the object variables to build the query fields and values.
1393		foreach (get_object_vars($object) as $k => $v)
1394		{
1395			// Only process non-null scalars.
1396			if (is_array($v) or is_object($v) or $v === null)
1397			{
1398				continue;
1399			}
1400
1401			// Ignore any internal fields.
1402			if ($k[0] == '_')
1403			{
1404				continue;
1405			}
1406
1407			// Prepare and sanitize the fields and values for the database query.
1408			$fields[] = $this->quoteName($k);
1409			$values[] = $this->quote($v);
1410		}
1411
1412		// Create the base insert statement.
1413		$query = $this->getQuery(true)
1414			->insert($this->quoteName($table))
1415			->columns($fields)
1416			->values(implode(',', $values));
1417
1418		// Set the query and execute the insert.
1419		$this->setQuery($query);
1420
1421		if (!$this->execute())
1422		{
1423			return false;
1424		}
1425
1426		// Update the primary key if it exists.
1427		$id = $this->insertid();
1428
1429		if ($key && $id && is_string($key))
1430		{
1431			$object->$key = $id;
1432		}
1433
1434		return true;
1435	}
1436
1437	/**
1438	 * Method to check whether the installed database version is supported by the database driver
1439	 *
1440	 * @return  boolean  True if the database version is supported
1441	 *
1442	 * @since   3.0.0
1443	 */
1444	public function isMinimumVersion()
1445	{
1446		return version_compare($this->getVersion(), static::$dbMinimum) >= 0;
1447	}
1448
1449	/**
1450	 * Method to get the first row of the result set from the database query as an associative array
1451	 * of ['field_name' => 'row_value'].
1452	 *
1453	 * @return  mixed  The return value or null if the query failed.
1454	 *
1455	 * @since   1.7.0
1456	 * @throws  RuntimeException
1457	 */
1458	public function loadAssoc()
1459	{
1460		$this->connect();
1461
1462		$ret = null;
1463
1464		// Execute the query and get the result set cursor.
1465		if (!($cursor = $this->execute()))
1466		{
1467			return;
1468		}
1469
1470		// Get the first row from the result set as an associative array.
1471		if ($array = $this->fetchAssoc($cursor))
1472		{
1473			$ret = $array;
1474		}
1475
1476		// Free up system resources and return.
1477		$this->freeResult($cursor);
1478
1479		return $ret;
1480	}
1481
1482	/**
1483	 * Method to get an array of the result set rows from the database query where each row is an associative array
1484	 * of ['field_name' => 'row_value'].  The array of rows can optionally be keyed by a field name, but defaults to
1485	 * a sequential numeric array.
1486	 *
1487	 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
1488	 * behavior and should be avoided.
1489	 *
1490	 * @param   string  $key     The name of a field on which to key the result array.
1491	 * @param   string  $column  An optional column name. Instead of the whole row, only this column value will be in
1492	 * the result array.
1493	 *
1494	 * @return  mixed   The return value or null if the query failed.
1495	 *
1496	 * @since   1.7.0
1497	 * @throws  RuntimeException
1498	 */
1499	public function loadAssocList($key = null, $column = null)
1500	{
1501		$this->connect();
1502
1503		$array = array();
1504
1505		// Execute the query and get the result set cursor.
1506		if (!($cursor = $this->execute()))
1507		{
1508			return;
1509		}
1510
1511		// Get all of the rows from the result set.
1512		while ($row = $this->fetchAssoc($cursor))
1513		{
1514			$value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row;
1515
1516			if ($key)
1517			{
1518				$array[$row[$key]] = $value;
1519			}
1520			else
1521			{
1522				$array[] = $value;
1523			}
1524		}
1525
1526		// Free up system resources and return.
1527		$this->freeResult($cursor);
1528
1529		return $array;
1530	}
1531
1532	/**
1533	 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from
1534	 * the database query.
1535	 *
1536	 * @param   integer  $offset  The row offset to use to build the result array.
1537	 *
1538	 * @return  mixed    The return value or null if the query failed.
1539	 *
1540	 * @since   1.7.0
1541	 * @throws  RuntimeException
1542	 */
1543	public function loadColumn($offset = 0)
1544	{
1545		$this->connect();
1546
1547		$array = array();
1548
1549		// Execute the query and get the result set cursor.
1550		if (!($cursor = $this->execute()))
1551		{
1552			return;
1553		}
1554
1555		// Get all of the rows from the result set as arrays.
1556		while ($row = $this->fetchArray($cursor))
1557		{
1558			$array[] = $row[$offset];
1559		}
1560
1561		// Free up system resources and return.
1562		$this->freeResult($cursor);
1563
1564		return $array;
1565	}
1566
1567	/**
1568	 * Method to get the next row in the result set from the database query as an object.
1569	 *
1570	 * @param   string  $class  The class name to use for the returned row object.
1571	 *
1572	 * @return  mixed   The result of the query as an array, false if there are no more rows.
1573	 *
1574	 * @since   1.7.0
1575	 * @throws  RuntimeException
1576	 * @deprecated  4.0 - Use getIterator() instead
1577	 */
1578	public function loadNextObject($class = 'stdClass')
1579	{
1580		JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
1581		$this->connect();
1582
1583		static $cursor = null;
1584
1585		// Execute the query and get the result set cursor.
1586		if (is_null($cursor))
1587		{
1588			if (!($cursor = $this->execute()))
1589			{
1590				return $this->errorNum ? null : false;
1591			}
1592		}
1593
1594		// Get the next row from the result set as an object of type $class.
1595		if ($row = $this->fetchObject($cursor, $class))
1596		{
1597			return $row;
1598		}
1599
1600		// Free up system resources and return.
1601		$this->freeResult($cursor);
1602		$cursor = null;
1603
1604		return false;
1605	}
1606
1607	/**
1608	 * Method to get the next row in the result set from the database query as an array.
1609	 *
1610	 * @return  mixed  The result of the query as an array, false if there are no more rows.
1611	 *
1612	 * @since   1.7.0
1613	 * @throws  RuntimeException
1614	 * @deprecated  4.0 (CMS)  Use JDatabaseDriver::getIterator() instead
1615	 */
1616	public function loadNextRow()
1617	{
1618		JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
1619		$this->connect();
1620
1621		static $cursor = null;
1622
1623		// Execute the query and get the result set cursor.
1624		if (is_null($cursor))
1625		{
1626			if (!($cursor = $this->execute()))
1627			{
1628				return $this->errorNum ? null : false;
1629			}
1630		}
1631
1632		// Get the next row from the result set as an object of type $class.
1633		if ($row = $this->fetchArray($cursor))
1634		{
1635			return $row;
1636		}
1637
1638		// Free up system resources and return.
1639		$this->freeResult($cursor);
1640		$cursor = null;
1641
1642		return false;
1643	}
1644
1645	/**
1646	 * Method to get the first row of the result set from the database query as an object.
1647	 *
1648	 * @param   string  $class  The class name to use for the returned row object.
1649	 *
1650	 * @return  mixed   The return value or null if the query failed.
1651	 *
1652	 * @since   1.7.0
1653	 * @throws  RuntimeException
1654	 */
1655	public function loadObject($class = 'stdClass')
1656	{
1657		$this->connect();
1658
1659		$ret = null;
1660
1661		// Execute the query and get the result set cursor.
1662		if (!($cursor = $this->execute()))
1663		{
1664			return;
1665		}
1666
1667		// Get the first row from the result set as an object of type $class.
1668		if ($object = $this->fetchObject($cursor, $class))
1669		{
1670			$ret = $object;
1671		}
1672
1673		// Free up system resources and return.
1674		$this->freeResult($cursor);
1675
1676		return $ret;
1677	}
1678
1679	/**
1680	 * Method to get an array of the result set rows from the database query where each row is an object.  The array
1681	 * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
1682	 *
1683	 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
1684	 * behavior and should be avoided.
1685	 *
1686	 * @param   string  $key    The name of a field on which to key the result array.
1687	 * @param   string  $class  The class name to use for the returned row objects.
1688	 *
1689	 * @return  mixed   The return value or null if the query failed.
1690	 *
1691	 * @since   1.7.0
1692	 * @throws  RuntimeException
1693	 */
1694	public function loadObjectList($key = '', $class = 'stdClass')
1695	{
1696		$this->connect();
1697
1698		$array = array();
1699
1700		// Execute the query and get the result set cursor.
1701		if (!($cursor = $this->execute()))
1702		{
1703			return;
1704		}
1705
1706		// Get all of the rows from the result set as objects of type $class.
1707		while ($row = $this->fetchObject($cursor, $class))
1708		{
1709			if ($key)
1710			{
1711				$array[$row->$key] = $row;
1712			}
1713			else
1714			{
1715				$array[] = $row;
1716			}
1717		}
1718
1719		// Free up system resources and return.
1720		$this->freeResult($cursor);
1721
1722		return $array;
1723	}
1724
1725	/**
1726	 * Method to get the first field of the first row of the result set from the database query.
1727	 *
1728	 * @return  mixed  The return value or null if the query failed.
1729	 *
1730	 * @since   1.7.0
1731	 * @throws  RuntimeException
1732	 */
1733	public function loadResult()
1734	{
1735		$this->connect();
1736
1737		$ret = null;
1738
1739		// Execute the query and get the result set cursor.
1740		if (!($cursor = $this->execute()))
1741		{
1742			return;
1743		}
1744
1745		// Get the first row from the result set as an array.
1746		if ($row = $this->fetchArray($cursor))
1747		{
1748			$ret = $row[0];
1749		}
1750
1751		// Free up system resources and return.
1752		$this->freeResult($cursor);
1753
1754		return $ret;
1755	}
1756
1757	/**
1758	 * Method to get the first row of the result set from the database query as an array.  Columns are indexed
1759	 * numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc.
1760	 *
1761	 * @return  mixed  The return value or null if the query failed.
1762	 *
1763	 * @since   1.7.0
1764	 * @throws  RuntimeException
1765	 */
1766	public function loadRow()
1767	{
1768		$this->connect();
1769
1770		$ret = null;
1771
1772		// Execute the query and get the result set cursor.
1773		if (!($cursor = $this->execute()))
1774		{
1775			return;
1776		}
1777
1778		// Get the first row from the result set as an array.
1779		if ($row = $this->fetchArray($cursor))
1780		{
1781			$ret = $row;
1782		}
1783
1784		// Free up system resources and return.
1785		$this->freeResult($cursor);
1786
1787		return $ret;
1788	}
1789
1790	/**
1791	 * Method to get an array of the result set rows from the database query where each row is an array.  The array
1792	 * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array.
1793	 *
1794	 * NOTE: Choosing to key the result array by a non-unique field can result in unwanted
1795	 * behavior and should be avoided.
1796	 *
1797	 * @param   integer  $index  The index of a field on which to key the result array.
1798	 *
1799	 * @return  mixed   The return value or null if the query failed.
1800	 *
1801	 * @since   1.7.0
1802	 * @throws  RuntimeException
1803	 */
1804	public function loadRowList($index = null)
1805	{
1806		$this->connect();
1807
1808		$array = array();
1809
1810		// Execute the query and get the result set cursor.
1811		if (!($cursor = $this->execute()))
1812		{
1813			return;
1814		}
1815
1816		// Get all of the rows from the result set as arrays.
1817		while ($row = $this->fetchArray($cursor))
1818		{
1819			if ($index !== null)
1820			{
1821				$array[$row[$index]] = $row;
1822			}
1823			else
1824			{
1825				$array[] = $row;
1826			}
1827		}
1828
1829		// Free up system resources and return.
1830		$this->freeResult($cursor);
1831
1832		return $array;
1833	}
1834
1835	/**
1836	 * Locks a table in the database.
1837	 *
1838	 * @param   string  $tableName  The name of the table to unlock.
1839	 *
1840	 * @return  JDatabaseDriver     Returns this object to support chaining.
1841	 *
1842	 * @since   2.5.0
1843	 * @throws  RuntimeException
1844	 */
1845	abstract public function lockTable($tableName);
1846
1847	/**
1848	 * Quotes and optionally escapes a string to database requirements for use in database queries.
1849	 *
1850	 * @param   mixed    $text    A string or an array of strings to quote.
1851	 * @param   boolean  $escape  True (default) to escape the string, false to leave it unchanged.
1852	 *
1853	 * @return  string|array  The quoted input.
1854	 *
1855	 * @note    Accepting an array of strings was added in 12.3.
1856	 * @since   1.7.0
1857	 */
1858	public function quote($text, $escape = true)
1859	{
1860		if (is_array($text))
1861		{
1862			foreach ($text as $k => $v)
1863			{
1864				$text[$k] = $this->quote($v, $escape);
1865			}
1866
1867			return $text;
1868		}
1869		else
1870		{
1871			return '\'' . ($escape ? $this->escape($text) : $text) . '\'';
1872		}
1873	}
1874
1875	/**
1876	 * Quotes a binary string to database requirements for use in database queries.
1877	 *
1878	 * @param   mixed  $data  A binary string to quote.
1879	 *
1880	 * @return  string  The binary quoted input string.
1881	 *
1882	 * @since   3.9.12
1883	 */
1884	public function quoteBinary($data)
1885	{
1886		// SQL standard syntax for hexadecimal literals
1887		return "X'" . bin2hex($data) . "'";
1888	}
1889
1890	/**
1891	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
1892	 * risks and reserved word conflicts.
1893	 *
1894	 * @param   mixed  $name  The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
1895	 *                        Each type supports dot-notation name.
1896	 * @param   mixed  $as    The AS query part associated to $name. It can be string or array, in latter case it has to be
1897	 *                        same length of $name; if is null there will not be any AS part for string or array element.
1898	 *
1899	 * @return  mixed  The quote wrapped name, same type of $name.
1900	 *
1901	 * @since   1.7.0
1902	 */
1903	public function quoteName($name, $as = null)
1904	{
1905		if (is_string($name))
1906		{
1907			$quotedName = $this->quoteNameStr(explode('.', $name));
1908
1909			$quotedAs = '';
1910
1911			if (!is_null($as))
1912			{
1913				settype($as, 'array');
1914				$quotedAs .= ' AS ' . $this->quoteNameStr($as);
1915			}
1916
1917			return $quotedName . $quotedAs;
1918		}
1919		else
1920		{
1921			$fin = array();
1922
1923			if (is_null($as))
1924			{
1925				foreach ($name as $str)
1926				{
1927					$fin[] = $this->quoteName($str);
1928				}
1929			}
1930			elseif (is_array($name) && (count($name) == count($as)))
1931			{
1932				$count = count($name);
1933
1934				for ($i = 0; $i < $count; $i++)
1935				{
1936					$fin[] = $this->quoteName($name[$i], $as[$i]);
1937				}
1938			}
1939
1940			return $fin;
1941		}
1942	}
1943
1944	/**
1945	 * Quote strings coming from quoteName call.
1946	 *
1947	 * @param   array  $strArr  Array of strings coming from quoteName dot-explosion.
1948	 *
1949	 * @return  string  Dot-imploded string of quoted parts.
1950	 *
1951	 * @since 1.7.3
1952	 */
1953	protected function quoteNameStr($strArr)
1954	{
1955		$parts = array();
1956		$q = $this->nameQuote;
1957
1958		foreach ($strArr as $part)
1959		{
1960			if (is_null($part))
1961			{
1962				continue;
1963			}
1964
1965			if (strlen($q) == 1)
1966			{
1967				$parts[] = $q . str_replace($q, $q . $q, $part) . $q;
1968			}
1969			else
1970			{
1971				$parts[] = $q[0] . str_replace($q[1], $q[1] . $q[1], $part) . $q[1];
1972			}
1973		}
1974
1975		return implode('.', $parts);
1976	}
1977
1978	/**
1979	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
1980	 * <var>tablePrefix</var> class variable.
1981	 *
1982	 * @param   string  $sql     The SQL statement to prepare.
1983	 * @param   string  $prefix  The common table prefix.
1984	 *
1985	 * @return  string  The processed SQL statement.
1986	 *
1987	 * @since   1.7.0
1988	 */
1989	public function replacePrefix($sql, $prefix = '#__')
1990	{
1991		$startPos = 0;
1992		$literal = '';
1993
1994		$sql = trim($sql);
1995		$n = strlen($sql);
1996
1997		while ($startPos < $n)
1998		{
1999			$ip = strpos($sql, $prefix, $startPos);
2000
2001			if ($ip === false)
2002			{
2003				break;
2004			}
2005
2006			$j = strpos($sql, "'", $startPos);
2007			$k = strpos($sql, '"', $startPos);
2008
2009			if (($k !== false) && (($k < $j) || ($j === false)))
2010			{
2011				$quoteChar = '"';
2012				$j = $k;
2013			}
2014			else
2015			{
2016				$quoteChar = "'";
2017			}
2018
2019			if ($j === false)
2020			{
2021				$j = $n;
2022			}
2023
2024			$literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos));
2025			$startPos = $j;
2026
2027			$j = $startPos + 1;
2028
2029			if ($j >= $n)
2030			{
2031				break;
2032			}
2033
2034			// Quote comes first, find end of quote
2035			while (true)
2036			{
2037				$k = strpos($sql, $quoteChar, $j);
2038				$escaped = false;
2039
2040				if ($k === false)
2041				{
2042					break;
2043				}
2044
2045				$l = $k - 1;
2046
2047				while ($l >= 0 && $sql[$l] == '\\')
2048				{
2049					$l--;
2050					$escaped = !$escaped;
2051				}
2052
2053				if ($escaped)
2054				{
2055					$j = $k + 1;
2056					continue;
2057				}
2058
2059				break;
2060			}
2061
2062			if ($k === false)
2063			{
2064				// Error in the query - no end quote; ignore it
2065				break;
2066			}
2067
2068			$literal .= substr($sql, $startPos, $k - $startPos + 1);
2069			$startPos = $k + 1;
2070		}
2071
2072		if ($startPos < $n)
2073		{
2074			$literal .= substr($sql, $startPos, $n - $startPos);
2075		}
2076
2077		return $literal;
2078	}
2079
2080	/**
2081	 * Renames a table in the database.
2082	 *
2083	 * @param   string  $oldTable  The name of the table to be renamed
2084	 * @param   string  $newTable  The new name for the table.
2085	 * @param   string  $backup    Table prefix
2086	 * @param   string  $prefix    For the table - used to rename constraints in non-mysql databases
2087	 *
2088	 * @return  JDatabaseDriver    Returns this object to support chaining.
2089	 *
2090	 * @since   2.5.0
2091	 * @throws  RuntimeException
2092	 */
2093	abstract public function renameTable($oldTable, $newTable, $backup = null, $prefix = null);
2094
2095	/**
2096	 * Select a database for use.
2097	 *
2098	 * @param   string  $database  The name of the database to select for use.
2099	 *
2100	 * @return  boolean  True if the database was successfully selected.
2101	 *
2102	 * @since   1.7.0
2103	 * @throws  RuntimeException
2104	 */
2105	abstract public function select($database);
2106
2107	/**
2108	 * Sets the database debugging state for the driver.
2109	 *
2110	 * @param   boolean  $level  True to enable debugging.
2111	 *
2112	 * @return  boolean  The old debugging level.
2113	 *
2114	 * @since   1.7.0
2115	 * @deprecated  4.0  This will be removed in Joomla 4 without replacement
2116	 */
2117	public function setDebug($level)
2118	{
2119		$previous = $this->debug;
2120		$this->debug = (bool) $level;
2121
2122		return $previous;
2123	}
2124
2125	/**
2126	 * Sets the SQL statement string for later execution.
2127	 *
2128	 * @param   mixed    $query   The SQL statement to set either as a JDatabaseQuery object or a string.
2129	 * @param   integer  $offset  The affected row offset to set.
2130	 * @param   integer  $limit   The maximum affected rows to set.
2131	 *
2132	 * @return  JDatabaseDriver  This object to support method chaining.
2133	 *
2134	 * @since   1.7.0
2135	 */
2136	public function setQuery($query, $offset = 0, $limit = 0)
2137	{
2138		$this->sql = $query;
2139
2140		if ($query instanceof JDatabaseQueryLimitable)
2141		{
2142			if (!$limit && $query->limit)
2143			{
2144				$limit = $query->limit;
2145			}
2146
2147			if (!$offset && $query->offset)
2148			{
2149				$offset = $query->offset;
2150			}
2151
2152			$query->setLimit($limit, $offset);
2153		}
2154		else
2155		{
2156			$this->limit = (int) max(0, $limit);
2157			$this->offset = (int) max(0, $offset);
2158		}
2159
2160		return $this;
2161	}
2162
2163	/**
2164	 * Set the connection to use UTF-8 character encoding.
2165	 *
2166	 * @return  boolean  True on success.
2167	 *
2168	 * @since   1.7.0
2169	 */
2170	abstract public function setUtf();
2171
2172	/**
2173	 * Method to commit a transaction.
2174	 *
2175	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
2176	 *
2177	 * @return  void
2178	 *
2179	 * @since   1.7.0
2180	 * @throws  RuntimeException
2181	 */
2182	abstract public function transactionCommit($toSavepoint = false);
2183
2184	/**
2185	 * Method to roll back a transaction.
2186	 *
2187	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
2188	 *
2189	 * @return  void
2190	 *
2191	 * @since   1.7.0
2192	 * @throws  RuntimeException
2193	 */
2194	abstract public function transactionRollback($toSavepoint = false);
2195
2196	/**
2197	 * Method to initialize a transaction.
2198	 *
2199	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
2200	 *
2201	 * @return  void
2202	 *
2203	 * @since   1.7.0
2204	 * @throws  RuntimeException
2205	 */
2206	abstract public function transactionStart($asSavepoint = false);
2207
2208	/**
2209	 * Method to truncate a table.
2210	 *
2211	 * @param   string  $table  The table to truncate
2212	 *
2213	 * @return  void
2214	 *
2215	 * @since   1.7.3
2216	 * @throws  RuntimeException
2217	 */
2218	public function truncateTable($table)
2219	{
2220		$this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table));
2221		$this->execute();
2222	}
2223
2224	/**
2225	 * Updates a row in a table based on an object's properties.
2226	 *
2227	 * @param   string        $table    The name of the database table to update.
2228	 * @param   object        &$object  A reference to an object whose public properties match the table fields.
2229	 * @param   array|string  $key      The name of the primary key.
2230	 * @param   boolean       $nulls    True to update null fields or false to ignore them.
2231	 *
2232	 * @return  boolean  True on success.
2233	 *
2234	 * @since   1.7.0
2235	 * @throws  RuntimeException
2236	 */
2237	public function updateObject($table, &$object, $key, $nulls = false)
2238	{
2239		$fields = array();
2240		$where = array();
2241
2242		if (is_string($key))
2243		{
2244			$key = array($key);
2245		}
2246
2247		if (is_object($key))
2248		{
2249			$key = (array) $key;
2250		}
2251
2252		// Create the base update statement.
2253		$statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';
2254
2255		// Iterate over the object variables to build the query fields/value pairs.
2256		foreach (get_object_vars($object) as $k => $v)
2257		{
2258			// Only process scalars that are not internal fields.
2259			if (is_array($v) || is_object($v) || $k[0] === '_')
2260			{
2261				continue;
2262			}
2263
2264			// Set the primary key to the WHERE clause instead of a field to update.
2265			if (in_array($k, $key))
2266			{
2267				$where[] = $this->quoteName($k) . ($v === null ? ' IS NULL' : ' = ' . $this->quote($v));
2268				continue;
2269			}
2270
2271			// Prepare and sanitize the fields and values for the database query.
2272			if ($v === null)
2273			{
2274				// If the value is null and we want to update nulls then set it.
2275				if ($nulls)
2276				{
2277					$val = 'NULL';
2278				}
2279				// If the value is null and we do not want to update nulls then ignore this field.
2280				else
2281				{
2282					continue;
2283				}
2284			}
2285			// The field is not null so we prep it for update.
2286			else
2287			{
2288				$val = $this->quote($v);
2289			}
2290
2291			// Add the field to be updated.
2292			$fields[] = $this->quoteName($k) . '=' . $val;
2293		}
2294
2295		// We don't have any fields to update.
2296		if (empty($fields))
2297		{
2298			return true;
2299		}
2300
2301		// Set the query and execute the update.
2302		$this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where)));
2303
2304		return $this->execute();
2305	}
2306
2307	/**
2308	 * Execute the SQL statement.
2309	 *
2310	 * @return  mixed  A database cursor resource on success, boolean false on failure.
2311	 *
2312	 * @since   3.0.0
2313	 * @throws  RuntimeException
2314	 */
2315	abstract public function execute();
2316
2317	/**
2318	 * Unlocks tables in the database.
2319	 *
2320	 * @return  JDatabaseDriver  Returns this object to support chaining.
2321	 *
2322	 * @since   2.5.0
2323	 * @throws  RuntimeException
2324	 */
2325	abstract public function unlockTables();
2326}
2327