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