<?php
namespace CodeIgniter\Database;
use CodeIgniter\Events\Events;
use CodeIgniter\Database\Exceptions\DatabaseException;
abstract class BaseConnection implements ConnectionInterface
{
protected $DSN;
protected $port = '';
protected $hostname;
protected $username;
protected $password;
protected $database;
protected $DBDriver = 'MySQLi';
protected $subdriver;
protected $DBPrefix = '';
protected $pConnect = false;
protected $DBDebug = false;
protected $cacheOn = false;
protected $cacheDir;
protected $charset = 'utf8';
protected $DBCollat = 'utf8_general_ci';
protected $swapPre = '';
protected $encrypt = false;
protected $compress = false;
protected $strictOn;
protected $failover = [];
protected $lastQuery;
public $connID = false;
public $resultID = false;
public $protectIdentifiers = true;
protected $reservedIdentifiers = ['*'];
public $escapeChar = '"';
public $likeEscapeStr = " ESCAPE '%s' ";
public $likeEscapeChar = '!';
public $dataCache = [];
protected $connectTime;
protected $connectDuration;
protected $pretend = false;
public $transEnabled = true;
public $transStrict = true;
protected $transDepth = 0;
protected $transStatus = true;
protected $transFailure = false;
protected $aliasedTables = [];
public function __construct(array $params)
{
foreach ($params as $key => $value)
{
if (property_exists($this, $key))
{
$this->$key = $value;
}
}
}
public function initialize()
{
if ($this->connID)
{
return;
}
$this->connectTime = microtime(true);
$this->connID = $this->connect($this->pConnect);
if (! $this->connID)
{
if (! empty($this->failover) && is_array($this->failover))
{
foreach ($this->failover as $failover)
{
foreach ($failover as $key => $val)
{
if (property_exists($this, $key))
{
$this->$key = $val;
}
}
$this->connID = $this->connect($this->pConnect);
if ($this->connID)
{
break;
}
}
}
if (! $this->connID)
{
throw new DatabaseException('Unable to connect to the database.');
}
}
$this->connectDuration = microtime(true) - $this->connectTime;
}
abstract public function connect(bool $persistent = false);
public function close()
{
if ($this->connID)
{
$this->_close();
$this->connID = false;
}
}
abstract protected function _close();
public function persistentConnect()
{
return $this->connect(true);
}
abstract public function reconnect();
public function getConnection(string $alias = null)
{
return $this->connID;
}
abstract public function setDatabase(string $databaseName);
public function getDatabase(): string
{
return empty($this->database) ? '' : $this->database;
}
public function setPrefix(string $prefix = ''): string
{
return $this->DBPrefix = $prefix;
}
public function getPrefix(): string
{
return $this->DBPrefix;
}
public function getError()
{
}
public function getPlatform(): string
{
return $this->DBDriver;
}
abstract public function getVersion(): string;
public function setAliasedTables(array $aliases)
{
$this->aliasedTables = $aliases;
return $this;
}
public function addTableAlias(string $table)
{
if (! in_array($table, $this->aliasedTables))
{
$this->aliasedTables[] = $table;
}
return $this;
}
abstract protected function execute(string $sql);
public function query(string $sql, $binds = null, bool $setEscapeFlags = true, string $queryClass = 'CodeIgniter\\Database\\Query')
{
if (empty($this->connID))
{
$this->initialize();
}
$resultClass = str_replace('Connection', 'Result', get_class($this));
$query = new $queryClass($this);
$query->setQuery($sql, $binds, $setEscapeFlags);
if (! empty($this->swapPre) && ! empty($this->DBPrefix))
{
$query->swapPrefix($this->DBPrefix, $this->swapPre);
}
$startTime = microtime(true);
$this->lastQuery = $query;
if (! $this->pretend && false === ($this->resultID = $this->simpleQuery($query->getQuery())))
{
$query->setDuration($startTime, $startTime);
if ($this->transDepth !== 0)
{
$this->transStatus = false;
}
if ($this->DBDebug)
{
while ($this->transDepth !== 0)
{
$transDepth = $this->transDepth;
$this->transComplete();
if ($transDepth === $this->transDepth)
{
log_message('error', 'Database: Failure during an automated transaction commit/rollback!');
break;
}
}
return false;
}
if (! $this->pretend)
{
Events::trigger('DBQuery', $query);
}
return new $resultClass($this->connID, $this->resultID);
}
$query->setDuration($startTime);
if (! $this->pretend)
{
Events::trigger('DBQuery', $query);
}
return $this->pretend ? $query : new $resultClass($this->connID, $this->resultID);
}
public function simpleQuery(string $sql)
{
if (empty($this->connID))
{
$this->initialize();
}
return $this->execute($sql);
}
public function transOff()
{
$this->transEnabled = false;
}
public function transStrict(bool $mode = true)
{
$this->transStrict = $mode;
return $this;
}
public function transStart(bool $test_mode = false): bool
{
if (! $this->transEnabled)
{
return false;
}
return $this->transBegin($test_mode);
}
public function transComplete(): bool
{
if (! $this->transEnabled)
{
return false;
}
if ($this->transStatus === false || $this->transFailure === true)
{
$this->transRollback();
if ($this->transStrict === false)
{
$this->transStatus = true;
}
return false;
}
return $this->transCommit();
}
public function transStatus(): bool
{
return $this->transStatus;
}
public function transBegin(bool $test_mode = false): bool
{
if (! $this->transEnabled)
{
return false;
}
elseif ($this->transDepth > 0)
{
$this->transDepth ++;
return true;
}
if (empty($this->connID))
{
$this->initialize();
}
$this->transFailure = ($test_mode === true);
if ($this->_transBegin())
{
$this->transDepth ++;
return true;
}
return false;
}
public function transCommit(): bool
{
if (! $this->transEnabled || $this->transDepth === 0)
{
return false;
}
elseif ($this->transDepth > 1 || $this->_transCommit())
{
$this->transDepth --;
return true;
}
return false;
}
public function transRollback(): bool
{
if (! $this->transEnabled || $this->transDepth === 0)
{
return false;
}
elseif ($this->transDepth > 1 || $this->_transRollback())
{
$this->transDepth --;
return true;
}
return false;
}
abstract protected function _transBegin(): bool;
abstract protected function _transCommit(): bool;
abstract protected function _transRollback(): bool;
public function table($tableName)
{
if (empty($tableName))
{
throw new DatabaseException('You must set the database table to be used with your query.');
}
$className = str_replace('Connection', 'Builder', get_class($this));
return new $className($tableName, $this);
}
public function prepare(\Closure $func, array $options = [])
{
if (empty($this->connID))
{
$this->initialize();
}
$this->pretend(true);
$sql = $func($this);
$this->pretend(false);
if ($sql instanceof QueryInterface)
{
$sql = $sql->getOriginalQuery();
}
$class = str_ireplace('Connection', 'PreparedQuery', get_class($this));
$class = new $class($this);
return $class->prepare($sql, $options);
}
public function getLastQuery()
{
return $this->lastQuery;
}
public function showLastQuery(): string
{
return (string) $this->lastQuery;
}
public function getConnectStart(): float
{
return $this->connectTime;
}
public function getConnectDuration(int $decimals = 6): string
{
return number_format($this->connectDuration, $decimals);
}
public function protectIdentifiers($item, bool $prefixSingle = false, bool $protectIdentifiers = null, bool $fieldExists = true)
{
if (! is_bool($protectIdentifiers))
{
$protectIdentifiers = $this->protectIdentifiers;
}
if (is_array($item))
{
$escaped_array = [];
foreach ($item as $k => $v)
{
$escaped_array[$this->protectIdentifiers($k)] = $this->protectIdentifiers($v, $prefixSingle, $protectIdentifiers, $fieldExists);
}
return $escaped_array;
}
if (strcspn($item, "()'") !== strlen($item))
{
return $item;
}
$item = preg_replace('/\s+/', ' ', trim($item));
if ($offset = strripos($item, ' AS '))
{
$alias = ($protectIdentifiers) ? substr($item, $offset, 4) . $this->escapeIdentifiers(substr($item, $offset + 4)) : substr($item, $offset);
$item = substr($item, 0, $offset);
}
elseif ($offset = strrpos($item, ' '))
{
$alias = ($protectIdentifiers) ? ' ' . $this->escapeIdentifiers(substr($item, $offset + 1)) : substr($item, $offset);
$item = substr($item, 0, $offset);
}
else
{
$alias = '';
}
if (strpos($item, '.') !== false)
{
$parts = explode('.', $item);
if (! empty($this->aliasedTables) && in_array($parts[0], $this->aliasedTables))
{
if ($protectIdentifiers === true)
{
foreach ($parts as $key => $val)
{
if (! in_array($val, $this->reservedIdentifiers))
{
$parts[$key] = $this->escapeIdentifiers($val);
}
}
$item = implode('.', $parts);
}
return $item . $alias;
}
if ($this->DBPrefix !== '')
{
if (isset($parts[3]))
{
$i = 2;
}
elseif (isset($parts[2]))
{
$i = 1;
}
else
{
$i = 0;
}
if ($fieldExists === false)
{
$i ++;
}
if ($this->swapPre !== '' && strpos($parts[$i], $this->swapPre) === 0)
{
$parts[$i] = preg_replace('/^' . $this->swapPre . '(\S+?)/', $this->DBPrefix . '\\1', $parts[$i]);
}
elseif (strpos($parts[$i], $this->DBPrefix) !== 0)
{
$parts[$i] = $this->DBPrefix . $parts[$i];
}
$item = implode('.', $parts);
}
if ($protectIdentifiers === true)
{
$item = $this->escapeIdentifiers($item);
}
return $item . $alias;
}
$item = trim($item, $this->escapeChar);
if ($this->DBPrefix !== '')
{
if ($this->swapPre !== '' && strpos($item, $this->swapPre) === 0)
{
$item = preg_replace('/^' . $this->swapPre . '(\S+?)/', $this->DBPrefix . '\\1', $item);
}
elseif ($prefixSingle === true && strpos($item, $this->DBPrefix) !== 0)
{
$item = $this->DBPrefix . $item;
}
}
if ($protectIdentifiers === true && ! in_array($item, $this->reservedIdentifiers))
{
$item = $this->escapeIdentifiers($item);
}
return $item . $alias;
}
public function escapeIdentifiers($item)
{
if ($this->escapeChar === '' || empty($item) || in_array($item, $this->reservedIdentifiers))
{
return $item;
}
elseif (is_array($item))
{
foreach ($item as $key => $value)
{
$item[$key] = $this->escapeIdentifiers($value);
}
return $item;
}
elseif (ctype_digit($item) || $item[0] === "'" || ( $this->escapeChar !== '"' && $item[0] === '"') ||
strpos($item, '(') !== false
)
{
return $item;
}
static $preg_ec = [];
if (empty($preg_ec))
{
if (is_array($this->escapeChar))
{
$preg_ec = [
preg_quote($this->escapeChar[0], '/'),
preg_quote($this->escapeChar[1], '/'),
$this->escapeChar[0],
$this->escapeChar[1],
];
}
else
{
$preg_ec[0] = $preg_ec[1] = preg_quote($this->escapeChar, '/');
$preg_ec[2] = $preg_ec[3] = $this->escapeChar;
}
}
foreach ($this->reservedIdentifiers as $id)
{
if (strpos($item, '.' . $id) !== false)
{
return preg_replace('/' . $preg_ec[0] . '?([^' . $preg_ec[1] . '\.]+)' . $preg_ec[1] . '?\./i', $preg_ec[2] . '$1' . $preg_ec[3] . '.', $item);
}
}
return preg_replace('/' . $preg_ec[0] . '?([^' . $preg_ec[1] . '\.]+)' . $preg_ec[1] . '?(\.)?/i', $preg_ec[2] . '$1' . $preg_ec[3] . '$2', $item);
}
public function prefixTable(string $table = ''): string
{
if ($table === '')
{
throw new DatabaseException('A table name is required for that operation.');
}
return $this->DBPrefix . $table;
}
abstract public function affectedRows(): int;
public function escape($str)
{
if (is_array($str))
{
$str = array_map([&$this, 'escape'], $str);
return $str;
}
else if (is_string($str) || ( is_object($str) && method_exists($str, '__toString')))
{
return "'" . $this->escapeString($str) . "'";
}
else if (is_bool($str))
{
return ($str === false) ? 0 : 1;
}
else if (is_numeric($str) && $str < 0)
{
return "'{$str}'";
}
else if ($str === null)
{
return 'NULL';
}
return $str;
}
public function escapeString($str, bool $like = false)
{
if (is_array($str))
{
foreach ($str as $key => $val)
{
$str[$key] = $this->escapeString($val, $like);
}
return $str;
}
$str = $this->_escapeString($str);
if ($like === true)
{
return str_replace([
$this->likeEscapeChar,
'%',
'_',
], [
$this->likeEscapeChar . $this->likeEscapeChar,
$this->likeEscapeChar . '%',
$this->likeEscapeChar . '_',
], $str
);
}
return $str;
}
public function escapeLikeString($str)
{
return $this->escapeString($str, true);
}
protected function _escapeString(string $str): string
{
return str_replace("'", "''", remove_invisible_characters($str, false));
}
public function callFunction(string $functionName, ...$params): bool
{
$driver = ($this->DBDriver === 'postgre' ? 'pg' : strtolower($this->DBDriver)) . '_';
if (false === strpos($driver, $functionName))
{
$functionName = $driver . $functionName;
}
if (! function_exists($functionName))
{
if ($this->DBDebug)
{
throw new DatabaseException('This feature is not available for the database you are using.');
}
return false;
}
return $functionName(...$params);
}
public function listTables(bool $constrainByPrefix = false)
{
if (isset($this->dataCache['table_names']) && $this->dataCache['table_names'])
{
return $constrainByPrefix ?
preg_grep("/^{$this->DBPrefix}/", $this->dataCache['table_names'])
: $this->dataCache['table_names'];
}
if (false === ($sql = $this->_listTables($constrainByPrefix)))
{
if ($this->DBDebug)
{
throw new DatabaseException('This feature is not available for the database you are using.');
}
return false;
}
$this->dataCache['table_names'] = [];
$query = $this->query($sql);
foreach ($query->getResultArray() as $row)
{
if (! isset($key))
{
if (isset($row['table_name']))
{
$key = 'table_name';
}
elseif (isset($row['TABLE_NAME']))
{
$key = 'TABLE_NAME';
}
else
{
$key = array_keys($row);
$key = array_shift($key);
}
}
$this->dataCache['table_names'][] = $row[$key];
}
return $this->dataCache['table_names'];
}
public function tableExists(string $tableName): bool
{
return in_array($this->protectIdentifiers($tableName, true, false, false), $this->listTables());
}
public function getFieldNames(string $table)
{
if (isset($this->dataCache['field_names'][$table]))
{
return $this->dataCache['field_names'][$table];
}
if (empty($this->connID))
{
$this->initialize();
}
if (false === ($sql = $this->_listColumns($table)))
{
if ($this->DBDebug)
{
throw new DatabaseException('This feature is not available for the database you are using.');
}
return false;
}
$query = $this->query($sql);
$this->dataCache['field_names'][$table] = [];
foreach ($query->getResultArray() as $row)
{
if (! isset($key))
{
if (isset($row['column_name']))
{
$key = 'column_name';
}
elseif (isset($row['COLUMN_NAME']))
{
$key = 'COLUMN_NAME';
}
else
{
$key = key($row);
}
}
$this->dataCache['field_names'][$table][] = $row[$key];
}
return $this->dataCache['field_names'][$table];
}
public function fieldExists(string $fieldName, string $tableName): bool
{
return in_array($fieldName, $this->getFieldNames($tableName));
}
public function getFieldData(string $table)
{
$fields = $this->_fieldData($this->protectIdentifiers($table, true, false, false));
return $fields ?? false;
}
public function getIndexData(string $table)
{
$fields = $this->_indexData($this->protectIdentifiers($table, true, false, false));
return $fields ?? false;
}
public function getForeignKeyData(string $table)
{
$fields = $this->_foreignKeyData($this->protectIdentifiers($table, true, false, false));
return $fields ?? false;
}
public function disableForeignKeyChecks()
{
$sql = $this->_disableForeignKeyChecks();
return $this->query($sql);
}
public function enableForeignKeyChecks()
{
$sql = $this->_enableForeignKeyChecks();
return $this->query($sql);
}
public function pretend(bool $pretend = true)
{
$this->pretend = $pretend;
return $this;
}
public function resetDataCache()
{
$this->dataCache = [];
return $this;
}
abstract public function error(): array;
abstract public function insertID(): int;
abstract protected function _listTables(bool $constrainByPrefix = false): string;
abstract protected function _listColumns(string $table = ''): string;
abstract protected function _fieldData(string $table): array;
abstract protected function _indexData(string $table): array;
abstract protected function _foreignKeyData(string $table): array;
public function __get(string $key)
{
if (property_exists($this, $key))
{
return $this->$key;
}
return null;
}
public function __isset(string $key): bool
{
return property_exists($this, $key);
}
}