2256 lines
54 KiB
PHP
2256 lines
54 KiB
PHP
<?php
|
|
/**
|
|
* @package FrameworkOnFramework
|
|
* @subpackage database
|
|
* @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
|
*
|
|
* This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning F0FTable objects
|
|
* instead of plain stdClass objects
|
|
*/
|
|
|
|
// Protect from unauthorized access
|
|
defined('F0F_INCLUDED') or die;
|
|
|
|
/**
|
|
* Joomla Platform Database Driver Class
|
|
*
|
|
* @since 12.1
|
|
*
|
|
* @method string q() q($text, $escape = true) Alias for quote method
|
|
* @method string qn() qn($name, $as = null) Alias for quoteName method
|
|
*/
|
|
abstract class F0FDatabaseDriver extends F0FDatabase implements F0FDatabaseInterface
|
|
{
|
|
/**
|
|
* The name of the database.
|
|
*
|
|
* @var string
|
|
* @since 11.4
|
|
*/
|
|
private $_database;
|
|
|
|
/**
|
|
* The name of the database driver.
|
|
*
|
|
* @var string
|
|
* @since 11.1
|
|
*/
|
|
public $name;
|
|
|
|
/**
|
|
* The type of the database server family supported by this driver. Examples: mysql, oracle, postgresql, mssql,
|
|
* sqlite.
|
|
*
|
|
* @var string
|
|
* @since CMS 3.5.0
|
|
*/
|
|
public $serverType;
|
|
|
|
/**
|
|
* @var resource The database connection resource.
|
|
* @since 11.1
|
|
*/
|
|
protected $connection;
|
|
|
|
/**
|
|
* @var integer The number of SQL statements executed by the database driver.
|
|
* @since 11.1
|
|
*/
|
|
protected $count = 0;
|
|
|
|
/**
|
|
* @var resource The database connection cursor from the last query.
|
|
* @since 11.1
|
|
*/
|
|
protected $cursor;
|
|
|
|
/**
|
|
* @var boolean The database driver debugging state.
|
|
* @since 11.1
|
|
*/
|
|
protected $debug = false;
|
|
|
|
/**
|
|
* @var integer The affected row limit for the current SQL statement.
|
|
* @since 11.1
|
|
*/
|
|
protected $limit = 0;
|
|
|
|
/**
|
|
* @var array The log of executed SQL statements by the database driver.
|
|
* @since 11.1
|
|
*/
|
|
protected $log = array();
|
|
|
|
/**
|
|
* @var array The log of executed SQL statements timings (start and stop microtimes) by the database driver.
|
|
* @since CMS 3.1.2
|
|
*/
|
|
protected $timings = array();
|
|
|
|
/**
|
|
* @var array The log of executed SQL statements timings (start and stop microtimes) by the database driver.
|
|
* @since CMS 3.1.2
|
|
*/
|
|
protected $callStacks = array();
|
|
|
|
/**
|
|
* @var string The character(s) used to quote SQL statement names such as table names or field names,
|
|
* etc. The child classes should define this as necessary. If a single character string the
|
|
* same character is used for both sides of the quoted name, else the first character will be
|
|
* used for the opening quote and the second for the closing quote.
|
|
* @since 11.1
|
|
*/
|
|
protected $nameQuote;
|
|
|
|
/**
|
|
* @var string The null or zero representation of a timestamp for the database driver. This should be
|
|
* defined in child classes to hold the appropriate value for the engine.
|
|
* @since 11.1
|
|
*/
|
|
protected $nullDate;
|
|
|
|
/**
|
|
* @var integer The affected row offset to apply for the current SQL statement.
|
|
* @since 11.1
|
|
*/
|
|
protected $offset = 0;
|
|
|
|
/**
|
|
* @var array Passed in upon instantiation and saved.
|
|
* @since 11.1
|
|
*/
|
|
protected $options;
|
|
|
|
/**
|
|
* @var mixed The current SQL statement to execute.
|
|
* @since 11.1
|
|
*/
|
|
protected $sql;
|
|
|
|
/**
|
|
* @var string The common database table prefix.
|
|
* @since 11.1
|
|
*/
|
|
protected $tablePrefix;
|
|
|
|
/**
|
|
* @var boolean True if the database engine supports UTF-8 character encoding.
|
|
* @since 11.1
|
|
*/
|
|
protected $utf = true;
|
|
|
|
/**
|
|
* @var boolean True if the database engine supports UTF-8 Multibyte (utf8mb4) character encoding.
|
|
* @since CMS 3.5.0
|
|
*/
|
|
protected $utf8mb4 = false;
|
|
|
|
/**
|
|
* @var integer The database error number
|
|
* @since 11.1
|
|
* @deprecated 12.1
|
|
*/
|
|
protected $errorNum = 0;
|
|
|
|
/**
|
|
* @var string The database error message
|
|
* @since 11.1
|
|
* @deprecated 12.1
|
|
*/
|
|
protected $errorMsg;
|
|
|
|
/**
|
|
* @var array F0FDatabaseDriver instances container.
|
|
* @since 11.1
|
|
*/
|
|
protected static $instances = array();
|
|
|
|
/**
|
|
* @var string The minimum supported database version.
|
|
* @since 12.1
|
|
*/
|
|
protected static $dbMinimum;
|
|
|
|
/**
|
|
* @var integer The depth of the current transaction.
|
|
* @since 12.3
|
|
*/
|
|
protected $transactionDepth = 0;
|
|
|
|
/**
|
|
* @var callable[] List of callables to call just before disconnecting database
|
|
* @since CMS 3.1.2
|
|
*/
|
|
protected $disconnectHandlers = array();
|
|
|
|
/**
|
|
* Get a list of available database connectors. The list will only be populated with connectors that both
|
|
* the class exists and the static test method returns true. This gives us the ability to have a multitude
|
|
* of connector classes that are self-aware as to whether or not they are able to be used on a given system.
|
|
*
|
|
* @return array An array of available database connectors.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function getConnectors()
|
|
{
|
|
$connectors = array();
|
|
|
|
// Get an iterator and loop trough the driver classes.
|
|
$iterator = new DirectoryIterator(__DIR__ . '/driver');
|
|
|
|
/* @type $file DirectoryIterator */
|
|
foreach ($iterator as $file)
|
|
{
|
|
$fileName = $file->getFilename();
|
|
|
|
// Only load for php files.
|
|
if (!$file->isFile() || $file->getExtension() != 'php')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Block the ext/mysql driver for PHP 7
|
|
if ($fileName === 'mysql.php' && PHP_MAJOR_VERSION >= 7)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Derive the class name from the type.
|
|
$class = str_ireplace('.php', '', 'F0FDatabaseDriver' . ucfirst(trim($fileName)));
|
|
|
|
// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
|
|
if (!class_exists($class))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Sweet! Our class exists, so now we just need to know if it passes its test method.
|
|
if ($class::isSupported())
|
|
{
|
|
// Connector names should not have file extensions.
|
|
$connectors[] = str_ireplace('.php', '', $fileName);
|
|
}
|
|
}
|
|
|
|
return $connectors;
|
|
}
|
|
|
|
/**
|
|
* Method to return a F0FDatabaseDriver instance based on the given options. There are three global options and then
|
|
* the rest are specific to the database driver. The 'driver' option defines which F0FDatabaseDriver class is
|
|
* used for the connection -- the default is 'mysqli'. The 'database' option determines which database is to
|
|
* be used for the connection. The 'select' option determines whether the connector should automatically select
|
|
* the chosen database.
|
|
*
|
|
* Instances are unique to the given options and new objects are only created when a unique options array is
|
|
* passed into the method. This ensures that we don't end up with unnecessary database connection resources.
|
|
*
|
|
* @param array $options Parameters to be passed to the database driver.
|
|
*
|
|
* @return F0FDatabaseDriver A database object.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public static function getInstance($options = array())
|
|
{
|
|
// Sanitize the database connector options.
|
|
$options['driver'] = (isset($options['driver'])) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli';
|
|
$options['database'] = (isset($options['database'])) ? $options['database'] : null;
|
|
$options['select'] = (isset($options['select'])) ? $options['select'] : true;
|
|
|
|
// If the selected driver is `mysql` and we are on PHP 7 or greater, switch to the `mysqli` driver.
|
|
if ($options['driver'] === 'mysql' && PHP_MAJOR_VERSION >= 7)
|
|
{
|
|
// Check if we have support for the other MySQL drivers
|
|
$mysqliSupported = F0FDatabaseDriverMysqli::isSupported();
|
|
$pdoMysqlSupported = F0FDatabaseDriverPdomysql::isSupported();
|
|
|
|
// If neither is supported, then the user cannot use MySQL; throw an exception
|
|
if (!$mysqliSupported && !$pdoMysqlSupported)
|
|
{
|
|
throw new RuntimeException(
|
|
'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.'
|
|
. ' Also, this system does not support MySQLi or PDO MySQL. Cannot instantiate database driver.'
|
|
);
|
|
}
|
|
|
|
// Prefer MySQLi as it is a closer replacement for the removed MySQL driver, otherwise use the PDO driver
|
|
if ($mysqliSupported)
|
|
{
|
|
if (class_exists('JLog'))
|
|
{
|
|
JLog::add(
|
|
'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver. Trying `mysqli` instead.',
|
|
JLog::WARNING,
|
|
'deprecated'
|
|
);
|
|
}
|
|
|
|
$options['driver'] = 'mysqli';
|
|
}
|
|
else
|
|
{
|
|
if (class_exists('JLog'))
|
|
{
|
|
JLog::add(
|
|
'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver. Trying `pdomysql` instead.',
|
|
JLog::WARNING,
|
|
'deprecated'
|
|
);
|
|
}
|
|
|
|
$options['driver'] = 'pdomysql';
|
|
}
|
|
}
|
|
|
|
// Get the options signature for the database connector.
|
|
$signature = md5(serialize($options));
|
|
|
|
// If we already have a database connector instance for these options then just use that.
|
|
if (empty(self::$instances[$signature]))
|
|
{
|
|
// Derive the class name from the driver.
|
|
$class = 'F0FDatabaseDriver' . ucfirst(strtolower($options['driver']));
|
|
|
|
// If the class still doesn't exist we have nothing left to do but throw an exception. We did our best.
|
|
if (!class_exists($class))
|
|
{
|
|
throw new RuntimeException(sprintf('Unable to load Database Driver: %s', $options['driver']));
|
|
}
|
|
|
|
// Create our new F0FDatabaseDriver connector based on the options given.
|
|
try
|
|
{
|
|
$instance = new $class($options);
|
|
}
|
|
catch (RuntimeException $e)
|
|
{
|
|
throw new RuntimeException(sprintf('Unable to connect to the Database: %s', $e->getMessage()), $e->getCode(), $e);
|
|
}
|
|
|
|
// Set the new connector to the global instances based on signature.
|
|
self::$instances[$signature] = $instance;
|
|
}
|
|
|
|
return self::$instances[$signature];
|
|
}
|
|
|
|
/**
|
|
* Splits a string of multiple queries into an array of individual queries.
|
|
*
|
|
* @param string $sql Input SQL string with which to split into individual queries.
|
|
*
|
|
* @return array The queries from the input string separated into an array.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function splitSql($sql)
|
|
{
|
|
$start = 0;
|
|
$open = false;
|
|
$char = '';
|
|
$end = strlen($sql);
|
|
$queries = array();
|
|
|
|
for ($i = 0; $i < $end; $i++)
|
|
{
|
|
$current = substr($sql, $i, 1);
|
|
|
|
if (($current == '"' || $current == '\''))
|
|
{
|
|
$n = 2;
|
|
|
|
while (substr($sql, $i - $n + 1, 1) == '\\' && $n < $i)
|
|
{
|
|
$n++;
|
|
}
|
|
|
|
if ($n % 2 == 0)
|
|
{
|
|
if ($open)
|
|
{
|
|
if ($current == $char)
|
|
{
|
|
$open = false;
|
|
$char = '';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$open = true;
|
|
$char = $current;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (($current == ';' && !$open) || $i == $end - 1)
|
|
{
|
|
$queries[] = substr($sql, $start, ($i - $start + 1));
|
|
$start = $i + 1;
|
|
}
|
|
}
|
|
|
|
return $queries;
|
|
}
|
|
|
|
/**
|
|
* Magic method to provide method alias support for quote() and quoteName().
|
|
*
|
|
* @param string $method The called method.
|
|
* @param array $args The array of arguments passed to the method.
|
|
*
|
|
* @return mixed The aliased method's return value or null.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function __call($method, $args)
|
|
{
|
|
if (empty($args))
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch ($method)
|
|
{
|
|
case 'q':
|
|
return $this->quote($args[0], isset($args[1]) ? $args[1] : true);
|
|
break;
|
|
case 'qn':
|
|
return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param array $options List of options used to configure the connection
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function __construct($options)
|
|
{
|
|
// Initialise object variables.
|
|
$this->_database = (isset($options['database'])) ? $options['database'] : '';
|
|
$this->tablePrefix = (isset($options['prefix'])) ? $options['prefix'] : 'jos_';
|
|
$this->connection = array_key_exists('connection', $options) ? $options['connection'] : null;
|
|
|
|
$this->count = 0;
|
|
$this->errorNum = 0;
|
|
$this->log = array();
|
|
|
|
// Set class options.
|
|
$this->options = $options;
|
|
}
|
|
|
|
/**
|
|
* Alter database's character set, obtaining query string from protected member.
|
|
*
|
|
* @param string $dbName The database name that will be altered
|
|
*
|
|
* @return string The query that alter the database query string
|
|
*
|
|
* @since 12.2
|
|
* @throws RuntimeException
|
|
*/
|
|
public function alterDbCharacterSet($dbName)
|
|
{
|
|
if (is_null($dbName))
|
|
{
|
|
throw new RuntimeException('Database name must not be null.');
|
|
}
|
|
|
|
$this->setQuery($this->getAlterDbCharacterSet($dbName));
|
|
|
|
return $this->execute();
|
|
}
|
|
|
|
/**
|
|
* Alter a table's character set, obtaining an array of queries to do so from a protected method. The conversion is
|
|
* wrapped in a transaction, if supported by the database driver. Otherwise the table will be locked before the
|
|
* conversion. This prevents data corruption.
|
|
*
|
|
* @param string $tableName The name of the table to alter
|
|
* @param boolean $rethrow True to rethrow database exceptions. Default: false (exceptions are suppressed)
|
|
*
|
|
* @return boolean True if successful
|
|
*
|
|
* @since CMS 3.5.0
|
|
* @throws RuntimeException If the table name is empty
|
|
* @throws Exception Relayed from the database layer if a database error occurs and $rethrow == true
|
|
*/
|
|
public function alterTableCharacterSet($tableName, $rethrow = false)
|
|
{
|
|
if (is_null($tableName))
|
|
{
|
|
throw new RuntimeException('Table name must not be null.');
|
|
}
|
|
|
|
$queries = $this->getAlterTableCharacterSet($tableName);
|
|
|
|
if (empty($queries))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$hasTransaction = true;
|
|
|
|
try
|
|
{
|
|
$this->transactionStart();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$hasTransaction = false;
|
|
$this->lockTable($tableName);
|
|
}
|
|
|
|
foreach ($queries as $query)
|
|
{
|
|
try
|
|
{
|
|
$this->setQuery($query)->execute();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
if ($hasTransaction)
|
|
{
|
|
$this->transactionRollback();
|
|
}
|
|
else
|
|
{
|
|
$this->unlockTables();
|
|
}
|
|
|
|
if ($rethrow)
|
|
{
|
|
throw $e;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ($hasTransaction)
|
|
{
|
|
try
|
|
{
|
|
$this->transactionCommit();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$this->transactionRollback();
|
|
|
|
if ($rethrow)
|
|
{
|
|
throw $e;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->unlockTables();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Connects to the database if needed.
|
|
*
|
|
* @return void Returns void if the database connected successfully.
|
|
*
|
|
* @since 12.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function connect();
|
|
|
|
/**
|
|
* Determines if the connection to the server is active.
|
|
*
|
|
* @return boolean True if connected to the database engine.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract public function connected();
|
|
|
|
/**
|
|
* Create a new database using information from $options object, obtaining query string
|
|
* from protected member.
|
|
*
|
|
* @param stdClass $options Object used to pass user and database name to database driver.
|
|
* This object must have "db_name" and "db_user" set.
|
|
* @param boolean $utf True if the database supports the UTF-8 character set.
|
|
*
|
|
* @return string The query that creates database
|
|
*
|
|
* @since 12.2
|
|
* @throws RuntimeException
|
|
*/
|
|
public function createDatabase($options, $utf = true)
|
|
{
|
|
if (is_null($options))
|
|
{
|
|
throw new RuntimeException('$options object must not be null.');
|
|
}
|
|
elseif (empty($options->db_name))
|
|
{
|
|
throw new RuntimeException('$options object must have db_name set.');
|
|
}
|
|
elseif (empty($options->db_user))
|
|
{
|
|
throw new RuntimeException('$options object must have db_user set.');
|
|
}
|
|
|
|
$this->setQuery($this->getCreateDatabaseQuery($options, $utf));
|
|
|
|
return $this->execute();
|
|
}
|
|
|
|
/**
|
|
* Disconnects the database.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 12.1
|
|
*/
|
|
abstract public function disconnect();
|
|
|
|
/**
|
|
* Adds a function callable just before disconnecting the database. Parameter of the callable is $this F0FDatabaseDriver
|
|
*
|
|
* @param callable $callable Function to call in disconnect() method just before disconnecting from database
|
|
*
|
|
* @return void
|
|
*
|
|
* @since CMS 3.1.2
|
|
*/
|
|
public function addDisconnectHandler($callable)
|
|
{
|
|
$this->disconnectHandlers[] = $callable;
|
|
}
|
|
|
|
/**
|
|
* Drops a table from the database.
|
|
*
|
|
* @param string $table The name of the database table to drop.
|
|
* @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
|
|
*
|
|
* @return F0FDatabaseDriver Returns this object to support chaining.
|
|
*
|
|
* @since 11.4
|
|
* @throws RuntimeException
|
|
*/
|
|
public abstract function dropTable($table, $ifExists = true);
|
|
|
|
/**
|
|
* Escapes a string for usage in an SQL statement.
|
|
*
|
|
* @param string $text The string to be escaped.
|
|
* @param boolean $extra Optional parameter to provide extra escaping.
|
|
*
|
|
* @return string The escaped string.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract public function escape($text, $extra = false);
|
|
|
|
/**
|
|
* Method to fetch a row from the result set cursor as an array.
|
|
*
|
|
* @param mixed $cursor The optional result set cursor from which to fetch the row.
|
|
*
|
|
* @return mixed Either the next row from the result set or false if there are no more rows.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract protected function fetchArray($cursor = null);
|
|
|
|
/**
|
|
* Method to fetch a row from the result set cursor as an associative array.
|
|
*
|
|
* @param mixed $cursor The optional result set cursor from which to fetch the row.
|
|
*
|
|
* @return mixed Either the next row from the result set or false if there are no more rows.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract protected function fetchAssoc($cursor = null);
|
|
|
|
/**
|
|
* Method to fetch a row from the result set cursor as an object.
|
|
*
|
|
* @param mixed $cursor The optional result set cursor from which to fetch the row.
|
|
* @param string $class The class name to use for the returned row object.
|
|
*
|
|
* @return mixed Either the next row from the result set or false if there are no more rows.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract protected function fetchObject($cursor = null, $class = 'stdClass');
|
|
|
|
/**
|
|
* Method to free up the memory used for the result set.
|
|
*
|
|
* @param mixed $cursor The optional result set cursor from which to fetch the row.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract protected function freeResult($cursor = null);
|
|
|
|
/**
|
|
* Get the number of affected rows for the previous executed SQL statement.
|
|
*
|
|
* @return integer The number of affected rows.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract public function getAffectedRows();
|
|
|
|
/**
|
|
* Return the query string to alter the database character set.
|
|
*
|
|
* @param string $dbName The database name
|
|
*
|
|
* @return string The query that alter the database query string
|
|
*
|
|
* @since 12.2
|
|
*/
|
|
public function getAlterDbCharacterSet($dbName)
|
|
{
|
|
$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
|
|
|
|
return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`';
|
|
}
|
|
|
|
/**
|
|
* Get the query strings to alter the character set and collation of a table.
|
|
*
|
|
* @param string $tableName The name of the table
|
|
*
|
|
* @return string[] The queries required to alter the table's character set and collation
|
|
*
|
|
* @since CMS 3.5.0
|
|
*/
|
|
public function getAlterTableCharacterSet($tableName)
|
|
{
|
|
$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
|
|
$collation = $charset . '_general_ci';
|
|
|
|
$quotedTableName = $this->quoteName($tableName);
|
|
|
|
$queries = array();
|
|
$queries[] = "ALTER TABLE $quotedTableName CONVERT TO CHARACTER SET $charset COLLATE $collation";
|
|
|
|
/**
|
|
* We also need to convert each text column, modifying their character set and collation. This allows us to
|
|
* change, for example, a utf8_bin collated column to a utf8mb4_bin collated column.
|
|
*/
|
|
$sql = "SHOW FULL COLUMNS FROM $quotedTableName";
|
|
$this->setQuery($sql);
|
|
$columns = $this->loadAssocList();
|
|
$columnMods = array();
|
|
|
|
if (is_array($columns))
|
|
{
|
|
foreach ($columns as $column)
|
|
{
|
|
// Make sure we are redefining only columns which do support a collation
|
|
$col = (object) $column;
|
|
|
|
if (empty($col->Collation))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Default new collation: utf8_general_ci or utf8mb4_general_ci
|
|
$newCollation = $charset . '_general_ci';
|
|
$collationParts = explode('_', $col->Collation);
|
|
|
|
/**
|
|
* If the collation is in the form charset_collationType_ci or charset_collationType we have to change
|
|
* the charset but leave the collationType intact (e.g. utf8_bin must become utf8mb4_bin, NOT
|
|
* utf8mb4_general_ci).
|
|
*/
|
|
if (count($collationParts) >= 2)
|
|
{
|
|
$ci = array_pop($collationParts);
|
|
$collationType = array_pop($collationParts);
|
|
$newCollation = $charset . '_' . $collationType . '_' . $ci;
|
|
|
|
/**
|
|
* When the last part of the old collation is not _ci we have a charset_collationType format,
|
|
* something like utf8_bin. Therefore the new collation only has *two* parts.
|
|
*/
|
|
if ($ci != 'ci')
|
|
{
|
|
$newCollation = $charset . '_' . $ci;
|
|
}
|
|
}
|
|
|
|
// If the old and new collation is the same we don't have to change the collation type
|
|
if (strtolower($newCollation) == strtolower($col->Collation))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$null = $col->Null == 'YES' ? 'NULL' : 'NOT NULL';
|
|
$default = is_null($col->Default) ? '' : "DEFAULT '" . $this->q($col->Default) . "'";
|
|
$columnMods[] = "MODIFY COLUMN `{$col->Field}` {$col->Type} CHARACTER SET $charset COLLATE $newCollation $null $default";
|
|
}
|
|
}
|
|
|
|
if (count($columnMods))
|
|
{
|
|
$queries[] = "ALTER TABLE $quotedTableName " .
|
|
implode(',', $columnMods) .
|
|
" CHARACTER SET $charset COLLATE $collation";
|
|
}
|
|
|
|
return $queries;
|
|
}
|
|
|
|
/**
|
|
* Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. Used
|
|
* when the server doesn't support UTF-8 Multibyte.
|
|
*
|
|
* @param string $query The query to convert
|
|
*
|
|
* @return string The converted query
|
|
*/
|
|
public function convertUtf8mb4QueryToUtf8($query)
|
|
{
|
|
if ($this->hasUTF8mb4Support())
|
|
{
|
|
return $query;
|
|
}
|
|
|
|
// If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert
|
|
$beginningOfQuery = substr($query, 0, 12);
|
|
$beginningOfQuery = strtoupper($beginningOfQuery);
|
|
|
|
if (!in_array($beginningOfQuery, array('ALTER TABLE ', 'CREATE TABLE')))
|
|
{
|
|
return $query;
|
|
}
|
|
|
|
// Replace utf8mb4 with utf8
|
|
return str_replace('utf8mb4', 'utf8', $query);
|
|
}
|
|
|
|
/**
|
|
* Return the query string to create new Database.
|
|
* Each database driver, other than MySQL, need to override this member to return correct string.
|
|
*
|
|
* @param stdClass $options Object used to pass user and database name to database driver.
|
|
* This object must have "db_name" and "db_user" set.
|
|
* @param boolean $utf True if the database supports the UTF-8 character set.
|
|
*
|
|
* @return string The query that creates database
|
|
*
|
|
* @since 12.2
|
|
*/
|
|
protected function getCreateDatabaseQuery($options, $utf)
|
|
{
|
|
if ($utf)
|
|
{
|
|
$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
|
|
|
|
return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '`';
|
|
}
|
|
|
|
return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
|
|
}
|
|
|
|
/**
|
|
* Method to get the database collation in use by sampling a text field of a table in the database.
|
|
*
|
|
* @return mixed The collation in use by the database or boolean false if not supported.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract public function getCollation();
|
|
|
|
/**
|
|
* Method to get the database connection collation, as reported by the driver. If the connector doesn't support
|
|
* reporting this value please return an empty string.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getConnectionCollation()
|
|
{
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Method that provides access to the underlying database connection. Useful for when you need to call a
|
|
* proprietary method such as postgresql's lo_* methods.
|
|
*
|
|
* @return resource The underlying database connection resource.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getConnection()
|
|
{
|
|
return $this->connection;
|
|
}
|
|
|
|
/**
|
|
* Get the total number of SQL statements executed by the database driver.
|
|
*
|
|
* @return integer
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getCount()
|
|
{
|
|
return $this->count;
|
|
}
|
|
|
|
/**
|
|
* Gets the name of the database used by this conneciton.
|
|
*
|
|
* @return string
|
|
*
|
|
* @since 11.4
|
|
*/
|
|
protected function getDatabase()
|
|
{
|
|
return $this->_database;
|
|
}
|
|
|
|
/**
|
|
* Returns a PHP date() function compliant date format for the database driver.
|
|
*
|
|
* @return string The format string.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getDateFormat()
|
|
{
|
|
return 'Y-m-d H:i:s';
|
|
}
|
|
|
|
/**
|
|
* Get the database driver SQL statement log.
|
|
*
|
|
* @return array SQL statements executed by the database driver.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getLog()
|
|
{
|
|
return $this->log;
|
|
}
|
|
|
|
/**
|
|
* Get the database driver SQL statement log.
|
|
*
|
|
* @return array SQL statements executed by the database driver.
|
|
*
|
|
* @since CMS 3.1.2
|
|
*/
|
|
public function getTimings()
|
|
{
|
|
return $this->timings;
|
|
}
|
|
|
|
/**
|
|
* Get the database driver SQL statement log.
|
|
*
|
|
* @return array SQL statements executed by the database driver.
|
|
*
|
|
* @since CMS 3.1.2
|
|
*/
|
|
public function getCallStacks()
|
|
{
|
|
return $this->callStacks;
|
|
}
|
|
|
|
/**
|
|
* Get the minimum supported database version.
|
|
*
|
|
* @return string The minimum version number for the database driver.
|
|
*
|
|
* @since 12.1
|
|
*/
|
|
public function getMinimum()
|
|
{
|
|
return static::$dbMinimum;
|
|
}
|
|
|
|
/**
|
|
* Get the null or zero representation of a timestamp for the database driver.
|
|
*
|
|
* @return string Null or zero representation of a timestamp.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getNullDate()
|
|
{
|
|
return $this->nullDate;
|
|
}
|
|
|
|
/**
|
|
* Get the number of returned rows for the previous executed SQL statement.
|
|
*
|
|
* @param resource $cursor An optional database cursor resource to extract the row count from.
|
|
*
|
|
* @return integer The number of returned rows.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract public function getNumRows($cursor = null);
|
|
|
|
/**
|
|
* Get the common table prefix for the database driver.
|
|
*
|
|
* @return string The common database table prefix.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getPrefix()
|
|
{
|
|
return $this->tablePrefix;
|
|
}
|
|
|
|
/**
|
|
* Gets an exporter class object.
|
|
*
|
|
* @return F0FDatabaseExporter An exporter object.
|
|
*
|
|
* @since 12.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function getExporter()
|
|
{
|
|
// Derive the class name from the driver.
|
|
$class = 'F0FDatabaseExporter' . ucfirst($this->name);
|
|
|
|
// Make sure we have an exporter class for this driver.
|
|
if (!class_exists($class))
|
|
{
|
|
// If it doesn't exist we are at an impasse so throw an exception.
|
|
throw new RuntimeException('Database Exporter not found.');
|
|
}
|
|
|
|
$o = new $class;
|
|
$o->setDbo($this);
|
|
|
|
return $o;
|
|
}
|
|
|
|
/**
|
|
* Gets an importer class object.
|
|
*
|
|
* @return F0FDatabaseImporter An importer object.
|
|
*
|
|
* @since 12.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function getImporter()
|
|
{
|
|
// Derive the class name from the driver.
|
|
$class = 'F0FDatabaseImporter' . ucfirst($this->name);
|
|
|
|
// Make sure we have an importer class for this driver.
|
|
if (!class_exists($class))
|
|
{
|
|
// If it doesn't exist we are at an impasse so throw an exception.
|
|
throw new RuntimeException('Database Importer not found');
|
|
}
|
|
|
|
$o = new $class;
|
|
$o->setDbo($this);
|
|
|
|
return $o;
|
|
}
|
|
|
|
/**
|
|
* Get the name of the database driver. If $this->name is not set it will try guessing the driver name from the
|
|
* class name.
|
|
*
|
|
* @return string
|
|
*
|
|
* @since CMS 3.5.0
|
|
*/
|
|
public function getName()
|
|
{
|
|
if (empty($this->name))
|
|
{
|
|
$className = get_class($this);
|
|
$className = str_replace('F0FDatabaseDriver', '', $className);
|
|
$this->name = strtolower($className);
|
|
}
|
|
|
|
return $this->name;
|
|
}
|
|
|
|
/**
|
|
* Get the server family type, e.g. mysql, postgresql, oracle, sqlite, mssql. If $this->serverType is not set it
|
|
* will attempt guessing the server family type from the driver name. If this is not possible the driver name will
|
|
* be returned instead.
|
|
*
|
|
* @return string
|
|
*
|
|
* @since CMS 3.5.0
|
|
*/
|
|
public function getServerType()
|
|
{
|
|
if (empty($this->serverType))
|
|
{
|
|
$name = $this->getName();
|
|
|
|
if (stristr($name, 'mysql') !== false)
|
|
{
|
|
$this->serverType = 'mysql';
|
|
}
|
|
elseif (stristr($name, 'postgre') !== false)
|
|
{
|
|
$this->serverType = 'postgresql';
|
|
}
|
|
elseif (stristr($name, 'oracle') !== false)
|
|
{
|
|
$this->serverType = 'oracle';
|
|
}
|
|
elseif (stristr($name, 'sqlite') !== false)
|
|
{
|
|
$this->serverType = 'sqlite';
|
|
}
|
|
elseif (stristr($name, 'sqlsrv') !== false)
|
|
{
|
|
$this->serverType = 'mssql';
|
|
}
|
|
elseif (stristr($name, 'mssql') !== false)
|
|
{
|
|
$this->serverType = 'mssql';
|
|
}
|
|
else
|
|
{
|
|
$this->serverType = $name;
|
|
}
|
|
}
|
|
|
|
return $this->serverType;
|
|
}
|
|
|
|
/**
|
|
* Get the current query object or a new F0FDatabaseQuery object.
|
|
*
|
|
* @param boolean $new False to return the current query object, True to return a new F0FDatabaseQuery object.
|
|
*
|
|
* @return F0FDatabaseQuery The current query object or a new object extending the F0FDatabaseQuery class.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function getQuery($new = false)
|
|
{
|
|
if ($new)
|
|
{
|
|
// Derive the class name from the driver.
|
|
$class = 'F0FDatabaseQuery' . ucfirst($this->name);
|
|
|
|
// Make sure we have a query class for this driver.
|
|
if (!class_exists($class))
|
|
{
|
|
// If it doesn't exist we are at an impasse so throw an exception.
|
|
throw new RuntimeException('Database Query Class not found.');
|
|
}
|
|
|
|
return new $class($this);
|
|
}
|
|
else
|
|
{
|
|
return $this->sql;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a new iterator on the current query.
|
|
*
|
|
* @param string $column An option column to use as the iterator key.
|
|
* @param string $class The class of object that is returned.
|
|
*
|
|
* @return F0FDatabaseIterator A new database iterator.
|
|
*
|
|
* @since 12.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function getIterator($column = null, $class = 'stdClass')
|
|
{
|
|
// Derive the class name from the driver.
|
|
$iteratorClass = 'F0FDatabaseIterator' . ucfirst($this->name);
|
|
|
|
// Make sure we have an iterator class for this driver.
|
|
if (!class_exists($iteratorClass))
|
|
{
|
|
// If it doesn't exist we are at an impasse so throw an exception.
|
|
throw new RuntimeException(sprintf('class *%s* is not defined', $iteratorClass));
|
|
}
|
|
|
|
// Return a new iterator
|
|
return new $iteratorClass($this->execute(), $column, $class);
|
|
}
|
|
|
|
/**
|
|
* Retrieves field information about the given tables.
|
|
*
|
|
* @param string $table The name of the database table.
|
|
* @param boolean $typeOnly True (default) to only return field types.
|
|
*
|
|
* @return array An array of fields by table.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function getTableColumns($table, $typeOnly = true);
|
|
|
|
/**
|
|
* Shows the table CREATE statement that creates the given tables.
|
|
*
|
|
* @param mixed $tables A table name or a list of table names.
|
|
*
|
|
* @return array A list of the create SQL for the tables.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function getTableCreate($tables);
|
|
|
|
/**
|
|
* Retrieves field information about the given tables.
|
|
*
|
|
* @param mixed $tables A table name or a list of table names.
|
|
*
|
|
* @return array An array of keys for the table(s).
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function getTableKeys($tables);
|
|
|
|
/**
|
|
* Method to get an array of all tables in the database.
|
|
*
|
|
* @return array An array of all the tables in the database.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function getTableList();
|
|
|
|
/**
|
|
* Determine whether or not the database engine supports UTF-8 character encoding.
|
|
*
|
|
* @return boolean True if the database engine supports UTF-8 character encoding.
|
|
*
|
|
* @since 11.1
|
|
* @deprecated 12.3 (Platform) & 4.0 (CMS) - Use hasUTFSupport() instead
|
|
*/
|
|
public function getUTFSupport()
|
|
{
|
|
if (class_exists('JLog'))
|
|
{
|
|
JLog::add('F0FDatabaseDriver::getUTFSupport() is deprecated. Use F0FDatabaseDriver::hasUTFSupport() instead.', JLog::WARNING, 'deprecated');
|
|
}
|
|
|
|
return $this->hasUTFSupport();
|
|
}
|
|
|
|
/**
|
|
* Determine whether or not the database engine supports UTF-8 character encoding.
|
|
*
|
|
* @return boolean True if the database engine supports UTF-8 character encoding.
|
|
*
|
|
* @since 12.1
|
|
*/
|
|
public function hasUTFSupport()
|
|
{
|
|
return $this->utf;
|
|
}
|
|
|
|
/**
|
|
* Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. This applies to
|
|
* MySQL databases.
|
|
*
|
|
* @return boolean True if the database engine supports UTF-8 Multibyte.
|
|
*
|
|
* @since CMS 3.5.0
|
|
*/
|
|
public function hasUTF8mb4Support()
|
|
{
|
|
return $this->utf8mb4;
|
|
}
|
|
|
|
/**
|
|
* Get the version of the database connector
|
|
*
|
|
* @return string The database connector version.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract public function getVersion();
|
|
|
|
/**
|
|
* Method to get the auto-incremented value from the last INSERT statement.
|
|
*
|
|
* @return mixed The value of the auto-increment field from the last inserted row.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract public function insertid();
|
|
|
|
/**
|
|
* Inserts a row into a table based on an object's properties.
|
|
*
|
|
* @param string $table The name of the database table to insert into.
|
|
* @param object &$object A reference to an object whose public properties match the table fields.
|
|
* @param string $key The name of the primary key. If provided the object property is updated.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function insertObject($table, &$object, $key = null)
|
|
{
|
|
$fields = array();
|
|
$values = array();
|
|
|
|
// Iterate over the object variables to build the query fields and values.
|
|
foreach (get_object_vars($object) as $k => $v)
|
|
{
|
|
// Only process non-null scalars.
|
|
if (is_array($v) or is_object($v) or $v === null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Ignore any internal fields.
|
|
if ($k[0] == '_')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Prepare and sanitize the fields and values for the database query.
|
|
$fields[] = $this->quoteName($k);
|
|
$values[] = $this->quote($v);
|
|
}
|
|
|
|
// Create the base insert statement.
|
|
$query = $this->getQuery(true)
|
|
->insert($this->quoteName($table))
|
|
->columns($fields)
|
|
->values(implode(',', $values));
|
|
|
|
// Set the query and execute the insert.
|
|
$this->setQuery($query);
|
|
|
|
if (!$this->execute())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Update the primary key if it exists.
|
|
$id = $this->insertid();
|
|
|
|
if ($key && $id && is_string($key))
|
|
{
|
|
$object->$key = $id;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to check whether the installed database version is supported by the database driver
|
|
*
|
|
* @return boolean True if the database version is supported
|
|
*
|
|
* @since 12.1
|
|
*/
|
|
public function isMinimumVersion()
|
|
{
|
|
return version_compare($this->getVersion(), static::$dbMinimum) >= 0;
|
|
}
|
|
|
|
/**
|
|
* Method to get the first row of the result set from the database query as an associative array
|
|
* of ['field_name' => 'row_value'].
|
|
*
|
|
* @return mixed The return value or null if the query failed.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function loadAssoc()
|
|
{
|
|
$this->connect();
|
|
|
|
$ret = null;
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get the first row from the result set as an associative array.
|
|
if ($array = $this->fetchAssoc($cursor))
|
|
{
|
|
$ret = $array;
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of the result set rows from the database query where each row is an associative array
|
|
* of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to
|
|
* a sequential numeric array.
|
|
*
|
|
* NOTE: Chosing to key the result array by a non-unique field name can result in unwanted
|
|
* behavior and should be avoided.
|
|
*
|
|
* @param string $key The name of a field on which to key the result array.
|
|
* @param string $column An optional column name. Instead of the whole row, only this column value will be in
|
|
* the result array.
|
|
*
|
|
* @return mixed The return value or null if the query failed.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function loadAssocList($key = null, $column = null)
|
|
{
|
|
$this->connect();
|
|
|
|
$array = array();
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get all of the rows from the result set.
|
|
while ($row = $this->fetchAssoc($cursor))
|
|
{
|
|
$value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row;
|
|
|
|
if ($key)
|
|
{
|
|
$array[$row[$key]] = $value;
|
|
}
|
|
else
|
|
{
|
|
$array[] = $value;
|
|
}
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of values from the <var>$offset</var> field in each row of the result set from
|
|
* the database query.
|
|
*
|
|
* @param integer $offset The row offset to use to build the result array.
|
|
*
|
|
* @return mixed The return value or null if the query failed.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function loadColumn($offset = 0)
|
|
{
|
|
$this->connect();
|
|
|
|
$array = array();
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get all of the rows from the result set as arrays.
|
|
while ($row = $this->fetchArray($cursor))
|
|
{
|
|
$array[] = $row[$offset];
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Method to get the next row in the result set from the database query as an object.
|
|
*
|
|
* @param string $class The class name to use for the returned row object.
|
|
*
|
|
* @return mixed The result of the query as an array, false if there are no more rows.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
* @deprecated 12.3 (Platform) & 4.0 (CMS) - Use getIterator() instead
|
|
*/
|
|
public function loadNextObject($class = 'stdClass')
|
|
{
|
|
if (class_exists('JLog'))
|
|
{
|
|
JLog::add(__METHOD__ . '() is deprecated. Use F0FDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
|
|
}
|
|
|
|
$this->connect();
|
|
|
|
static $cursor = null;
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if ( is_null($cursor) )
|
|
{
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return $this->errorNum ? null : false;
|
|
}
|
|
}
|
|
|
|
// Get the next row from the result set as an object of type $class.
|
|
if ($row = $this->fetchObject($cursor, $class))
|
|
{
|
|
return $row;
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
$cursor = null;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Method to get the next row in the result set from the database query as an array.
|
|
*
|
|
* @return mixed The result of the query as an array, false if there are no more rows.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
* @deprecated 4.0 (CMS) Use F0FDatabaseDriver::getIterator() instead
|
|
*/
|
|
public function loadNextRow()
|
|
{
|
|
if (class_exists('JLog'))
|
|
{
|
|
JLog::add(__METHOD__ . '() is deprecated. Use F0FDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
|
|
}
|
|
|
|
$this->connect();
|
|
|
|
static $cursor = null;
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if ( is_null($cursor) )
|
|
{
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return $this->errorNum ? null : false;
|
|
}
|
|
}
|
|
|
|
// Get the next row from the result set as an object of type $class.
|
|
if ($row = $this->fetchArray($cursor))
|
|
{
|
|
return $row;
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
$cursor = null;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Method to get the first row of the result set from the database query as an object.
|
|
*
|
|
* @param string $class The class name to use for the returned row object.
|
|
*
|
|
* @return mixed The return value or null if the query failed.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function loadObject($class = 'stdClass')
|
|
{
|
|
$this->connect();
|
|
|
|
$ret = null;
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get the first row from the result set as an object of type $class.
|
|
if ($object = $this->fetchObject($cursor, $class))
|
|
{
|
|
$ret = $object;
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of the result set rows from the database query where each row is an object. The array
|
|
* of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
|
|
*
|
|
* NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
|
|
* behavior and should be avoided.
|
|
*
|
|
* @param string $key The name of a field on which to key the result array.
|
|
* @param string $class The class name to use for the returned row objects.
|
|
*
|
|
* @return mixed The return value or null if the query failed.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function loadObjectList($key = '', $class = 'stdClass')
|
|
{
|
|
$this->connect();
|
|
|
|
$array = array();
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get all of the rows from the result set as objects of type $class.
|
|
while ($row = $this->fetchObject($cursor, $class))
|
|
{
|
|
if ($key)
|
|
{
|
|
$array[$row->$key] = $row;
|
|
}
|
|
else
|
|
{
|
|
$array[] = $row;
|
|
}
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Method to get the first field of the first row of the result set from the database query.
|
|
*
|
|
* @return mixed The return value or null if the query failed.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function loadResult()
|
|
{
|
|
$this->connect();
|
|
|
|
$ret = null;
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get the first row from the result set as an array.
|
|
if ($row = $this->fetchArray($cursor))
|
|
{
|
|
$ret = $row[0];
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Method to get the first row of the result set from the database query as an array. Columns are indexed
|
|
* numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc.
|
|
*
|
|
* @return mixed The return value or null if the query failed.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function loadRow()
|
|
{
|
|
$this->connect();
|
|
|
|
$ret = null;
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get the first row from the result set as an array.
|
|
if ($row = $this->fetchArray($cursor))
|
|
{
|
|
$ret = $row;
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of the result set rows from the database query where each row is an array. The array
|
|
* of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array.
|
|
*
|
|
* NOTE: Choosing to key the result array by a non-unique field can result in unwanted
|
|
* behavior and should be avoided.
|
|
*
|
|
* @param string $key The name of a field on which to key the result array.
|
|
*
|
|
* @return mixed The return value or null if the query failed.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function loadRowList($key = null)
|
|
{
|
|
$this->connect();
|
|
|
|
$array = array();
|
|
|
|
// Execute the query and get the result set cursor.
|
|
if (!($cursor = $this->execute()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get all of the rows from the result set as arrays.
|
|
while ($row = $this->fetchArray($cursor))
|
|
{
|
|
if ($key !== null)
|
|
{
|
|
$array[$row[$key]] = $row;
|
|
}
|
|
else
|
|
{
|
|
$array[] = $row;
|
|
}
|
|
}
|
|
|
|
// Free up system resources and return.
|
|
$this->freeResult($cursor);
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Locks a table in the database.
|
|
*
|
|
* @param string $tableName The name of the table to unlock.
|
|
*
|
|
* @return F0FDatabaseDriver Returns this object to support chaining.
|
|
*
|
|
* @since 11.4
|
|
* @throws RuntimeException
|
|
*/
|
|
public abstract function lockTable($tableName);
|
|
|
|
/**
|
|
* Quotes and optionally escapes a string to database requirements for use in database queries.
|
|
*
|
|
* @param mixed $text A string or an array of strings to quote.
|
|
* @param boolean $escape True (default) to escape the string, false to leave it unchanged.
|
|
*
|
|
* @return string The quoted input string.
|
|
*
|
|
* @note Accepting an array of strings was added in 12.3.
|
|
* @since 11.1
|
|
*/
|
|
public function quote($text, $escape = true)
|
|
{
|
|
if (is_array($text))
|
|
{
|
|
foreach ($text as $k => $v)
|
|
{
|
|
$text[$k] = $this->quote($v, $escape);
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
else
|
|
{
|
|
return '\'' . ($escape ? $this->escape($text) : $text) . '\'';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
|
|
* risks and reserved word conflicts.
|
|
*
|
|
* @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
|
|
* Each type supports dot-notation name.
|
|
* @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has to be
|
|
* same length of $name; if is null there will not be any AS part for string or array element.
|
|
*
|
|
* @return mixed The quote wrapped name, same type of $name.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function quoteName($name, $as = null)
|
|
{
|
|
if (is_string($name))
|
|
{
|
|
$quotedName = $this->quoteNameStr(explode('.', $name));
|
|
|
|
$quotedAs = '';
|
|
|
|
if (!is_null($as))
|
|
{
|
|
settype($as, 'array');
|
|
$quotedAs .= ' AS ' . $this->quoteNameStr($as);
|
|
}
|
|
|
|
return $quotedName . $quotedAs;
|
|
}
|
|
else
|
|
{
|
|
$fin = array();
|
|
|
|
if (is_null($as))
|
|
{
|
|
foreach ($name as $str)
|
|
{
|
|
$fin[] = $this->quoteName($str);
|
|
}
|
|
}
|
|
elseif (is_array($name) && (count($name) == count($as)))
|
|
{
|
|
$count = count($name);
|
|
|
|
for ($i = 0; $i < $count; $i++)
|
|
{
|
|
$fin[] = $this->quoteName($name[$i], $as[$i]);
|
|
}
|
|
}
|
|
|
|
return $fin;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Quote strings coming from quoteName call.
|
|
*
|
|
* @param array $strArr Array of strings coming from quoteName dot-explosion.
|
|
*
|
|
* @return string Dot-imploded string of quoted parts.
|
|
*
|
|
* @since 11.3
|
|
*/
|
|
protected function quoteNameStr($strArr)
|
|
{
|
|
$parts = array();
|
|
$q = $this->nameQuote;
|
|
|
|
foreach ($strArr as $part)
|
|
{
|
|
if (is_null($part))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (strlen($q) == 1)
|
|
{
|
|
$parts[] = $q . $part . $q;
|
|
}
|
|
else
|
|
{
|
|
$parts[] = $q{0} . $part . $q{1};
|
|
}
|
|
}
|
|
|
|
return implode('.', $parts);
|
|
}
|
|
|
|
/**
|
|
* This function replaces a string identifier <var>$prefix</var> with the string held is the
|
|
* <var>tablePrefix</var> class variable.
|
|
*
|
|
* @param string $sql The SQL statement to prepare.
|
|
* @param string $prefix The common table prefix.
|
|
*
|
|
* @return string The processed SQL statement.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function replacePrefix($sql, $prefix = '#__')
|
|
{
|
|
$startPos = 0;
|
|
$literal = '';
|
|
|
|
$sql = trim($sql);
|
|
$n = strlen($sql);
|
|
|
|
while ($startPos < $n)
|
|
{
|
|
$ip = strpos($sql, $prefix, $startPos);
|
|
|
|
if ($ip === false)
|
|
{
|
|
break;
|
|
}
|
|
|
|
$j = strpos($sql, "'", $startPos);
|
|
$k = strpos($sql, '"', $startPos);
|
|
|
|
if (($k !== false) && (($k < $j) || ($j === false)))
|
|
{
|
|
$quoteChar = '"';
|
|
$j = $k;
|
|
}
|
|
else
|
|
{
|
|
$quoteChar = "'";
|
|
}
|
|
|
|
if ($j === false)
|
|
{
|
|
$j = $n;
|
|
}
|
|
|
|
$literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos));
|
|
$startPos = $j;
|
|
|
|
$j = $startPos + 1;
|
|
|
|
if ($j >= $n)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Quote comes first, find end of quote
|
|
while (true)
|
|
{
|
|
$k = strpos($sql, $quoteChar, $j);
|
|
$escaped = false;
|
|
|
|
if ($k === false)
|
|
{
|
|
break;
|
|
}
|
|
|
|
$l = $k - 1;
|
|
|
|
while ($l >= 0 && $sql{$l} == '\\')
|
|
{
|
|
$l--;
|
|
$escaped = !$escaped;
|
|
}
|
|
|
|
if ($escaped)
|
|
{
|
|
$j = $k + 1;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ($k === false)
|
|
{
|
|
// Error in the query - no end quote; ignore it
|
|
break;
|
|
}
|
|
|
|
$literal .= substr($sql, $startPos, $k - $startPos + 1);
|
|
$startPos = $k + 1;
|
|
}
|
|
|
|
if ($startPos < $n)
|
|
{
|
|
$literal .= substr($sql, $startPos, $n - $startPos);
|
|
}
|
|
|
|
return $literal;
|
|
}
|
|
|
|
/**
|
|
* Renames a table in the database.
|
|
*
|
|
* @param string $oldTable The name of the table to be renamed
|
|
* @param string $newTable The new name for the table.
|
|
* @param string $backup Table prefix
|
|
* @param string $prefix For the table - used to rename constraints in non-mysql databases
|
|
*
|
|
* @return F0FDatabaseDriver Returns this object to support chaining.
|
|
*
|
|
* @since 11.4
|
|
* @throws RuntimeException
|
|
*/
|
|
public abstract function renameTable($oldTable, $newTable, $backup = null, $prefix = null);
|
|
|
|
/**
|
|
* Select a database for use.
|
|
*
|
|
* @param string $database The name of the database to select for use.
|
|
*
|
|
* @return boolean True if the database was successfully selected.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function select($database);
|
|
|
|
/**
|
|
* Sets the database debugging state for the driver.
|
|
*
|
|
* @param boolean $level True to enable debugging.
|
|
*
|
|
* @return boolean The old debugging level.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function setDebug($level)
|
|
{
|
|
$previous = $this->debug;
|
|
$this->debug = (bool) $level;
|
|
|
|
return $previous;
|
|
}
|
|
|
|
/**
|
|
* Sets the SQL statement string for later execution.
|
|
*
|
|
* @param mixed $query The SQL statement to set either as a F0FDatabaseQuery object or a string.
|
|
* @param integer $offset The affected row offset to set.
|
|
* @param integer $limit The maximum affected rows to set.
|
|
*
|
|
* @return F0FDatabaseDriver This object to support method chaining.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function setQuery($query, $offset = 0, $limit = 0)
|
|
{
|
|
$this->sql = $query;
|
|
|
|
if ($query instanceof F0FDatabaseQueryLimitable)
|
|
{
|
|
if (!$limit && $query->limit)
|
|
{
|
|
$limit = $query->limit;
|
|
}
|
|
|
|
if (!$offset && $query->offset)
|
|
{
|
|
$offset = $query->offset;
|
|
}
|
|
|
|
$query->setLimit($limit, $offset);
|
|
}
|
|
else
|
|
{
|
|
$this->limit = (int) max(0, $limit);
|
|
$this->offset = (int) max(0, $offset);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the connection to use UTF-8 character encoding.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
abstract public function setUtf();
|
|
|
|
/**
|
|
* Method to commit a transaction.
|
|
*
|
|
* @param boolean $toSavepoint If true, commit to the last savepoint.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function transactionCommit($toSavepoint = false);
|
|
|
|
/**
|
|
* Method to roll back a transaction.
|
|
*
|
|
* @param boolean $toSavepoint If true, rollback to the last savepoint.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function transactionRollback($toSavepoint = false);
|
|
|
|
/**
|
|
* Method to initialize a transaction.
|
|
*
|
|
* @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function transactionStart($asSavepoint = false);
|
|
|
|
/**
|
|
* Method to truncate a table.
|
|
*
|
|
* @param string $table The table to truncate
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.3
|
|
* @throws RuntimeException
|
|
*/
|
|
public function truncateTable($table)
|
|
{
|
|
$this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table));
|
|
$this->execute();
|
|
}
|
|
|
|
/**
|
|
* Updates a row in a table based on an object's properties.
|
|
*
|
|
* @param string $table The name of the database table to update.
|
|
* @param object &$object A reference to an object whose public properties match the table fields.
|
|
* @param array $key The name of the primary key.
|
|
* @param boolean $nulls True to update null fields or false to ignore them.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function updateObject($table, &$object, $key, $nulls = false)
|
|
{
|
|
$fields = array();
|
|
$where = array();
|
|
|
|
if (is_string($key))
|
|
{
|
|
$key = array($key);
|
|
}
|
|
|
|
if (is_object($key))
|
|
{
|
|
$key = (array) $key;
|
|
}
|
|
|
|
// Create the base update statement.
|
|
$statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';
|
|
|
|
// Iterate over the object variables to build the query fields/value pairs.
|
|
foreach (get_object_vars($object) as $k => $v)
|
|
{
|
|
// Only process scalars that are not internal fields.
|
|
if (is_array($v) or is_object($v) or $k[0] == '_')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Set the primary key to the WHERE clause instead of a field to update.
|
|
if (in_array($k, $key))
|
|
{
|
|
$where[] = $this->quoteName($k) . '=' . $this->quote($v);
|
|
continue;
|
|
}
|
|
|
|
// Prepare and sanitize the fields and values for the database query.
|
|
if ($v === null)
|
|
{
|
|
// If the value is null and we want to update nulls then set it.
|
|
if ($nulls)
|
|
{
|
|
$val = 'NULL';
|
|
}
|
|
// If the value is null and we do not want to update nulls then ignore this field.
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
// The field is not null so we prep it for update.
|
|
else
|
|
{
|
|
$val = $this->quote($v);
|
|
}
|
|
|
|
// Add the field to be updated.
|
|
$fields[] = $this->quoteName($k) . '=' . $val;
|
|
}
|
|
|
|
// We don't have any fields to update.
|
|
if (empty($fields))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Set the query and execute the update.
|
|
$this->setQuery(sprintf($statement, implode(",", $fields), implode(' AND ', $where)));
|
|
|
|
return $this->execute();
|
|
}
|
|
|
|
/**
|
|
* Execute the SQL statement.
|
|
*
|
|
* @return mixed A database cursor resource on success, boolean false on failure.
|
|
*
|
|
* @since 12.1
|
|
* @throws RuntimeException
|
|
*/
|
|
abstract public function execute();
|
|
|
|
/**
|
|
* Unlocks tables in the database.
|
|
*
|
|
* @return F0FDatabaseDriver Returns this object to support chaining.
|
|
*
|
|
* @since 11.4
|
|
* @throws RuntimeException
|
|
*/
|
|
public abstract function unlockTables();
|
|
}
|