CI3 => CI4
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use BadMethodCallException;
|
||||
use CodeIgniter\Events\Events;
|
||||
|
||||
/**
|
||||
* Base prepared query
|
||||
*/
|
||||
abstract class BasePreparedQuery implements PreparedQueryInterface
|
||||
{
|
||||
/**
|
||||
* The prepared statement itself.
|
||||
*
|
||||
* @var object|resource
|
||||
*/
|
||||
protected $statement;
|
||||
|
||||
/**
|
||||
* The error code, if any.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $errorCode;
|
||||
|
||||
/**
|
||||
* The error message, if any.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $errorString;
|
||||
|
||||
/**
|
||||
* Holds the prepared query object
|
||||
* that is cloned during execute.
|
||||
*
|
||||
* @var Query
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* A reference to the db connection to use.
|
||||
*
|
||||
* @var BaseConnection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
public function __construct(BaseConnection $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the query against the database, and saves the connection
|
||||
* info necessary to execute the query later.
|
||||
*
|
||||
* NOTE: This version is based on SQL code. Child classes should
|
||||
* override this method.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function prepare(string $sql, array $options = [], string $queryClass = Query::class)
|
||||
{
|
||||
// We only supports positional placeholders (?)
|
||||
// in order to work with the execute method below, so we
|
||||
// need to replace our named placeholders (:name)
|
||||
$sql = preg_replace('/:[^\s,)]+/', '?', $sql);
|
||||
|
||||
/** @var Query $query */
|
||||
$query = new $queryClass($this->db);
|
||||
|
||||
$query->setQuery($sql);
|
||||
|
||||
if (! empty($this->db->swapPre) && ! empty($this->db->DBPrefix)) {
|
||||
$query->swapPrefix($this->db->DBPrefix, $this->db->swapPre);
|
||||
}
|
||||
|
||||
$this->query = $query;
|
||||
|
||||
return $this->_prepare($query->getOriginalQuery(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* The database-dependent portion of the prepare statement.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function _prepare(string $sql, array $options = []);
|
||||
|
||||
/**
|
||||
* Takes a new set of data and runs it against the currently
|
||||
* prepared query. Upon success, will return a Results object.
|
||||
*
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function execute(...$data)
|
||||
{
|
||||
// Execute the Query.
|
||||
$startTime = microtime(true);
|
||||
|
||||
$this->_execute($data);
|
||||
|
||||
// Update our query object
|
||||
$query = clone $this->query;
|
||||
$query->setBinds($data);
|
||||
|
||||
$query->setDuration($startTime);
|
||||
|
||||
// Let others do something with this query
|
||||
Events::trigger('DBQuery', $query);
|
||||
|
||||
// Return a result object
|
||||
$resultClass = str_replace('PreparedQuery', 'Result', static::class);
|
||||
|
||||
$resultID = $this->_getResult();
|
||||
|
||||
return new $resultClass($this->db->connID, $resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* The database dependant version of the execute method.
|
||||
*/
|
||||
abstract public function _execute(array $data): bool;
|
||||
|
||||
/**
|
||||
* Returns the result object for the prepared query.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function _getResult();
|
||||
|
||||
/**
|
||||
* Explicitly closes the statement.
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if (! is_object($this->statement) || ! method_exists($this->statement, 'close')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->statement->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SQL that has been prepared.
|
||||
*/
|
||||
public function getQueryString(): string
|
||||
{
|
||||
if (! $this->query instanceof QueryInterface) {
|
||||
throw new BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.');
|
||||
}
|
||||
|
||||
return $this->query->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to determine if any error exists.
|
||||
*/
|
||||
public function hasError(): bool
|
||||
{
|
||||
return ! empty($this->errorString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error code created while executing this statement.
|
||||
*/
|
||||
public function getErrorCode(): int
|
||||
{
|
||||
return $this->errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error message created while executing this statement.
|
||||
*/
|
||||
public function getErrorMessage(): string
|
||||
{
|
||||
return $this->errorString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use CodeIgniter\Entity\Entity;
|
||||
|
||||
/**
|
||||
* Class BaseResult
|
||||
*/
|
||||
abstract class BaseResult implements ResultInterface
|
||||
{
|
||||
/**
|
||||
* Connection ID
|
||||
*
|
||||
* @var object|resource
|
||||
*/
|
||||
public $connID;
|
||||
|
||||
/**
|
||||
* Result ID
|
||||
*
|
||||
* @var false|object|resource
|
||||
*/
|
||||
public $resultID;
|
||||
|
||||
/**
|
||||
* Result Array
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
public $resultArray = [];
|
||||
|
||||
/**
|
||||
* Result Object
|
||||
*
|
||||
* @var object[]
|
||||
*/
|
||||
public $resultObject = [];
|
||||
|
||||
/**
|
||||
* Custom Result Object
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $customResultObject = [];
|
||||
|
||||
/**
|
||||
* Current Row index
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $currentRow = 0;
|
||||
|
||||
/**
|
||||
* The number of records in the query result
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $numRows;
|
||||
|
||||
/**
|
||||
* Row data
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
public $rowData;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object|resource $connID
|
||||
* @param object|resource $resultID
|
||||
*/
|
||||
public function __construct(&$connID, &$resultID)
|
||||
{
|
||||
$this->connID = $connID;
|
||||
$this->resultID = $resultID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the results of the query. Typically an array of
|
||||
* individual data rows, which can be either an 'array', an
|
||||
* 'object', or a custom class name.
|
||||
*
|
||||
* @param string $type The row type. Either 'array', 'object', or a class name to use
|
||||
*/
|
||||
public function getResult(string $type = 'object'): array
|
||||
{
|
||||
if ($type === 'array') {
|
||||
return $this->getResultArray();
|
||||
}
|
||||
|
||||
if ($type === 'object') {
|
||||
return $this->getResultObject();
|
||||
}
|
||||
|
||||
return $this->getCustomResultObject($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the results as an array of custom objects.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCustomResultObject(string $className)
|
||||
{
|
||||
if (isset($this->customResultObject[$className])) {
|
||||
return $this->customResultObject[$className];
|
||||
}
|
||||
|
||||
if (is_bool($this->resultID) || ! $this->resultID) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Don't fetch the result set again if we already have it
|
||||
$_data = null;
|
||||
if (($c = count($this->resultArray)) > 0) {
|
||||
$_data = 'resultArray';
|
||||
} elseif (($c = count($this->resultObject)) > 0) {
|
||||
$_data = 'resultObject';
|
||||
}
|
||||
|
||||
if ($_data !== null) {
|
||||
for ($i = 0; $i < $c; $i++) {
|
||||
$this->customResultObject[$className][$i] = new $className();
|
||||
|
||||
foreach ($this->{$_data}[$i] as $key => $value) {
|
||||
$this->customResultObject[$className][$i]->{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->customResultObject[$className];
|
||||
}
|
||||
|
||||
if ($this->rowData !== null) {
|
||||
$this->dataSeek();
|
||||
}
|
||||
$this->customResultObject[$className] = [];
|
||||
|
||||
while ($row = $this->fetchObject($className)) {
|
||||
if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) {
|
||||
$row->syncOriginal();
|
||||
}
|
||||
|
||||
$this->customResultObject[$className][] = $row;
|
||||
}
|
||||
|
||||
return $this->customResultObject[$className];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the results as an array of arrays.
|
||||
*
|
||||
* If no results, an empty array is returned.
|
||||
*/
|
||||
public function getResultArray(): array
|
||||
{
|
||||
if (! empty($this->resultArray)) {
|
||||
return $this->resultArray;
|
||||
}
|
||||
|
||||
// In the event that query caching is on, the result_id variable
|
||||
// will not be a valid resource so we'll simply return an empty
|
||||
// array.
|
||||
if (is_bool($this->resultID) || ! $this->resultID) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($this->resultObject) {
|
||||
foreach ($this->resultObject as $row) {
|
||||
$this->resultArray[] = (array) $row;
|
||||
}
|
||||
|
||||
return $this->resultArray;
|
||||
}
|
||||
|
||||
if ($this->rowData !== null) {
|
||||
$this->dataSeek();
|
||||
}
|
||||
|
||||
while ($row = $this->fetchAssoc()) {
|
||||
$this->resultArray[] = $row;
|
||||
}
|
||||
|
||||
return $this->resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the results as an array of objects.
|
||||
*
|
||||
* If no results, an empty array is returned.
|
||||
*/
|
||||
public function getResultObject(): array
|
||||
{
|
||||
if (! empty($this->resultObject)) {
|
||||
return $this->resultObject;
|
||||
}
|
||||
|
||||
// In the event that query caching is on, the result_id variable
|
||||
// will not be a valid resource so we'll simply return an empty
|
||||
// array.
|
||||
if (is_bool($this->resultID) || ! $this->resultID) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($this->resultArray) {
|
||||
foreach ($this->resultArray as $row) {
|
||||
$this->resultObject[] = (object) $row;
|
||||
}
|
||||
|
||||
return $this->resultObject;
|
||||
}
|
||||
|
||||
if ($this->rowData !== null) {
|
||||
$this->dataSeek();
|
||||
}
|
||||
|
||||
while ($row = $this->fetchObject()) {
|
||||
if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) {
|
||||
$row->syncOriginal();
|
||||
}
|
||||
|
||||
$this->resultObject[] = $row;
|
||||
}
|
||||
|
||||
return $this->resultObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper object to return a row as either an array, an object, or
|
||||
* a custom class.
|
||||
*
|
||||
* If row doesn't exist, returns null.
|
||||
*
|
||||
* @param mixed $n The index of the results to return
|
||||
* @param string $type The type of result object. 'array', 'object' or class name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRow($n = 0, string $type = 'object')
|
||||
{
|
||||
if (! is_numeric($n)) {
|
||||
// We cache the row data for subsequent uses
|
||||
if (! is_array($this->rowData)) {
|
||||
$this->rowData = $this->getRowArray();
|
||||
}
|
||||
|
||||
// array_key_exists() instead of isset() to allow for NULL values
|
||||
if (empty($this->rowData) || ! array_key_exists($n, $this->rowData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->rowData[$n];
|
||||
}
|
||||
|
||||
if ($type === 'object') {
|
||||
return $this->getRowObject($n);
|
||||
}
|
||||
|
||||
if ($type === 'array') {
|
||||
return $this->getRowArray($n);
|
||||
}
|
||||
|
||||
return $this->getCustomRowObject($n, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a row as a custom class instance.
|
||||
*
|
||||
* If row doesn't exists, returns null.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCustomRowObject(int $n, string $className)
|
||||
{
|
||||
if (! isset($this->customResultObject[$className])) {
|
||||
$this->getCustomResultObject($className);
|
||||
}
|
||||
|
||||
if (empty($this->customResultObject[$className])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($n !== $this->currentRow && isset($this->customResultObject[$className][$n])) {
|
||||
$this->currentRow = $n;
|
||||
}
|
||||
|
||||
return $this->customResultObject[$className][$this->currentRow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single row from the results as an array.
|
||||
*
|
||||
* If row doesn't exist, returns null.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRowArray(int $n = 0)
|
||||
{
|
||||
$result = $this->getResultArray();
|
||||
if (empty($result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($n !== $this->currentRow && isset($result[$n])) {
|
||||
$this->currentRow = $n;
|
||||
}
|
||||
|
||||
return $result[$this->currentRow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single row from the results as an object.
|
||||
*
|
||||
* If row doesn't exist, returns null.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRowObject(int $n = 0)
|
||||
{
|
||||
$result = $this->getResultObject();
|
||||
if (empty($result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($n !== $this->customResultObject && isset($result[$n])) {
|
||||
$this->currentRow = $n;
|
||||
}
|
||||
|
||||
return $result[$this->currentRow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns an item into a particular column slot.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setRow($key, $value = null)
|
||||
{
|
||||
// We cache the row data for subsequent uses
|
||||
if (! is_array($this->rowData)) {
|
||||
$this->rowData = $this->getRowArray();
|
||||
}
|
||||
|
||||
if (is_array($key)) {
|
||||
foreach ($key as $k => $v) {
|
||||
$this->rowData[$k] = $v;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($key !== '' && $value !== null) {
|
||||
$this->rowData[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "first" row of the current results.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFirstRow(string $type = 'object')
|
||||
{
|
||||
$result = $this->getResult($type);
|
||||
|
||||
return (empty($result)) ? null : $result[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "last" row of the current results.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLastRow(string $type = 'object')
|
||||
{
|
||||
$result = $this->getResult($type);
|
||||
|
||||
return (empty($result)) ? null : $result[count($result) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "next" row of the current results.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNextRow(string $type = 'object')
|
||||
{
|
||||
$result = $this->getResult($type);
|
||||
if (empty($result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isset($result[$this->currentRow + 1]) ? $result[++$this->currentRow] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "previous" row of the current results.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPreviousRow(string $type = 'object')
|
||||
{
|
||||
$result = $this->getResult($type);
|
||||
if (empty($result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($result[$this->currentRow - 1])) {
|
||||
$this->currentRow--;
|
||||
}
|
||||
|
||||
return $result[$this->currentRow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unbuffered row and move the pointer to the next row.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUnbufferedRow(string $type = 'object')
|
||||
{
|
||||
if ($type === 'array') {
|
||||
return $this->fetchAssoc();
|
||||
}
|
||||
|
||||
if ($type === 'object') {
|
||||
return $this->fetchObject();
|
||||
}
|
||||
|
||||
return $this->fetchObject($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of rows in the result set; checks for previous count, falls
|
||||
* back on counting resultArray or resultObject, finally fetching resultArray
|
||||
* if nothing was previously fetched
|
||||
*/
|
||||
public function getNumRows(): int
|
||||
{
|
||||
if (is_int($this->numRows)) {
|
||||
return $this->numRows;
|
||||
}
|
||||
if ($this->resultArray !== []) {
|
||||
return $this->numRows = count($this->resultArray);
|
||||
}
|
||||
if ($this->resultObject !== []) {
|
||||
return $this->numRows = count($this->resultObject);
|
||||
}
|
||||
|
||||
return $this->numRows = count($this->getResultArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of fields in the result set.
|
||||
*/
|
||||
abstract public function getFieldCount(): int;
|
||||
|
||||
/**
|
||||
* Generates an array of column names in the result set.
|
||||
*/
|
||||
abstract public function getFieldNames(): array;
|
||||
|
||||
/**
|
||||
* Generates an array of objects representing field meta-data.
|
||||
*/
|
||||
abstract public function getFieldData(): array;
|
||||
|
||||
/**
|
||||
* Frees the current result.
|
||||
*/
|
||||
abstract public function freeResult();
|
||||
|
||||
/**
|
||||
* Moves the internal pointer to the desired offset. This is called
|
||||
* internally before fetching results to make sure the result set
|
||||
* starts at zero.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function dataSeek(int $n = 0);
|
||||
|
||||
/**
|
||||
* Returns the result set as an array.
|
||||
*
|
||||
* Overridden by driver classes.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function fetchAssoc();
|
||||
|
||||
/**
|
||||
* Returns the result set as an object.
|
||||
*
|
||||
* Overridden by child classes.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
abstract protected function fetchObject(string $className = 'stdClass');
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Class BaseUtils
|
||||
*/
|
||||
abstract class BaseUtils
|
||||
{
|
||||
/**
|
||||
* Database object
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var bool|string
|
||||
*/
|
||||
protected $listDatabases = false;
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var bool|string
|
||||
*/
|
||||
protected $optimizeTable = false;
|
||||
|
||||
/**
|
||||
* REPAIR TABLE statement
|
||||
*
|
||||
* @var bool|string
|
||||
*/
|
||||
protected $repairTable = false;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct(ConnectionInterface $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* List databases
|
||||
*
|
||||
* @return array|bool
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function listDatabases()
|
||||
{
|
||||
// Is there a cached result?
|
||||
if (isset($this->db->dataCache['db_names'])) {
|
||||
return $this->db->dataCache['db_names'];
|
||||
}
|
||||
|
||||
if ($this->listDatabases === false) {
|
||||
if ($this->db->DBDebug) {
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->db->dataCache['db_names'] = [];
|
||||
|
||||
$query = $this->db->query($this->listDatabases);
|
||||
if ($query === false) {
|
||||
return $this->db->dataCache['db_names'];
|
||||
}
|
||||
|
||||
for ($i = 0, $query = $query->getResultArray(), $c = count($query); $i < $c; $i++) {
|
||||
$this->db->dataCache['db_names'][] = current($query[$i]);
|
||||
}
|
||||
|
||||
return $this->db->dataCache['db_names'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a particular database exists
|
||||
*/
|
||||
public function databaseExists(string $databaseName): bool
|
||||
{
|
||||
return in_array($databaseName, $this->listDatabases(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize Table
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function optimizeTable(string $tableName)
|
||||
{
|
||||
if ($this->optimizeTable === false) {
|
||||
if ($this->db->DBDebug) {
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($tableName)));
|
||||
|
||||
return $query !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize Database
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function optimizeDatabase()
|
||||
{
|
||||
if ($this->optimizeTable === false) {
|
||||
if ($this->db->DBDebug) {
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($this->db->listTables() as $tableName) {
|
||||
$res = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($tableName)));
|
||||
if (is_bool($res)) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
// Build the result array...
|
||||
|
||||
$res = $res->getResultArray();
|
||||
|
||||
// Postgre & SQLite3 returns empty array
|
||||
if (empty($res)) {
|
||||
$key = $tableName;
|
||||
} else {
|
||||
$res = current($res);
|
||||
$key = str_replace($this->db->database . '.', '', current($res));
|
||||
$keys = array_keys($res);
|
||||
unset($res[$keys[0]]);
|
||||
}
|
||||
|
||||
$result[$key] = $res;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair Table
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function repairTable(string $tableName)
|
||||
{
|
||||
if ($this->repairTable === false) {
|
||||
if ($this->db->DBDebug) {
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = $this->db->query(sprintf($this->repairTable, $this->db->escapeIdentifiers($tableName)));
|
||||
if (is_bool($query)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query = $query->getResultArray();
|
||||
|
||||
return current($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSV from a query result object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCSVFromResult(ResultInterface $query, string $delim = ',', string $newline = "\n", string $enclosure = '"')
|
||||
{
|
||||
$out = '';
|
||||
|
||||
foreach ($query->getFieldNames() as $name) {
|
||||
$out .= $enclosure . str_replace($enclosure, $enclosure . $enclosure, $name) . $enclosure . $delim;
|
||||
}
|
||||
|
||||
$out = substr($out, 0, -strlen($delim)) . $newline;
|
||||
|
||||
// Next blast through the result array and build out the rows
|
||||
while ($row = $query->getUnbufferedRow('array')) {
|
||||
$line = [];
|
||||
|
||||
foreach ($row as $item) {
|
||||
$line[] = $enclosure . str_replace($enclosure, $enclosure . $enclosure, $item ?? '') . $enclosure;
|
||||
}
|
||||
|
||||
$out .= implode($delim, $line) . $newline;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate XML data from a query result object
|
||||
*/
|
||||
public function getXMLFromResult(ResultInterface $query, array $params = []): string
|
||||
{
|
||||
foreach (['root' => 'root', 'element' => 'element', 'newline' => "\n", 'tab' => "\t"] as $key => $val) {
|
||||
if (! isset($params[$key])) {
|
||||
$params[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
$root = $params['root'];
|
||||
$newline = $params['newline'];
|
||||
$tab = $params['tab'];
|
||||
$element = $params['element'];
|
||||
|
||||
helper('xml');
|
||||
$xml = '<' . $root . '>' . $newline;
|
||||
|
||||
while ($row = $query->getUnbufferedRow()) {
|
||||
$xml .= $tab . '<' . $element . '>' . $newline;
|
||||
|
||||
foreach ($row as $key => $val) {
|
||||
$val = (! empty($val)) ? xml_convert($val) : '';
|
||||
|
||||
$xml .= $tab . $tab . '<' . $key . '>' . $val . '</' . $key . '>' . $newline;
|
||||
}
|
||||
|
||||
$xml .= $tab . '</' . $element . '>' . $newline;
|
||||
}
|
||||
|
||||
return $xml . '</' . $root . '>' . $newline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database Backup
|
||||
*
|
||||
* @param array|string $params
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function backup($params = [])
|
||||
{
|
||||
if (is_string($params)) {
|
||||
$params = ['tables' => $params];
|
||||
}
|
||||
|
||||
$prefs = [
|
||||
'tables' => [],
|
||||
'ignore' => [],
|
||||
'filename' => '',
|
||||
'format' => 'gzip', // gzip, txt
|
||||
'add_drop' => true,
|
||||
'add_insert' => true,
|
||||
'newline' => "\n",
|
||||
'foreign_key_checks' => true,
|
||||
];
|
||||
|
||||
if (! empty($params)) {
|
||||
foreach (array_keys($prefs) as $key) {
|
||||
if (isset($params[$key])) {
|
||||
$prefs[$key] = $params[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($prefs['tables'])) {
|
||||
$prefs['tables'] = $this->db->listTables();
|
||||
}
|
||||
|
||||
if (! in_array($prefs['format'], ['gzip', 'txt'], true)) {
|
||||
$prefs['format'] = 'txt';
|
||||
}
|
||||
|
||||
if ($prefs['format'] === 'gzip' && ! function_exists('gzencode')) {
|
||||
if ($this->db->DBDebug) {
|
||||
throw new DatabaseException('The file compression format you chose is not supported by your server.');
|
||||
}
|
||||
|
||||
$prefs['format'] = 'txt';
|
||||
}
|
||||
|
||||
if ($prefs['format'] === 'txt') {
|
||||
return $this->_backup($prefs);
|
||||
}
|
||||
|
||||
return gzencode($this->_backup($prefs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform dependent version of the backup function.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function _backup(?array $prefs = null);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use Config\Database as DbConfig;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Config
|
||||
*/
|
||||
class Config extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* Cache for instance of any connections that
|
||||
* have been requested as a "shared" instance.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $instances = [];
|
||||
|
||||
/**
|
||||
* The main instance used to manage all of
|
||||
* our open database connections.
|
||||
*
|
||||
* @var Database|null
|
||||
*/
|
||||
protected static $factory;
|
||||
|
||||
/**
|
||||
* Creates the default
|
||||
*
|
||||
* @param array|BaseConnection|string|null $group The name of the connection group to use,
|
||||
* or an array of configuration settings.
|
||||
* @phpstan-param array|BaseConnection|non-empty-string|null $group
|
||||
* @param bool $getShared Whether to return a shared instance of the connection.
|
||||
*
|
||||
* @return BaseConnection
|
||||
*/
|
||||
public static function connect($group = null, bool $getShared = true)
|
||||
{
|
||||
// If a DB connection is passed in, just pass it back
|
||||
if ($group instanceof BaseConnection) {
|
||||
return $group;
|
||||
}
|
||||
|
||||
if (is_array($group)) {
|
||||
$config = $group;
|
||||
$group = 'custom-' . md5(json_encode($config));
|
||||
} else {
|
||||
/** @var DbConfig $dbConfig */
|
||||
$dbConfig = config('Database');
|
||||
|
||||
if ($group === null) {
|
||||
$group = (ENVIRONMENT === 'testing') ? 'tests' : $dbConfig->defaultGroup;
|
||||
}
|
||||
|
||||
assert(is_string($group));
|
||||
|
||||
if (! isset($dbConfig->{$group})) {
|
||||
throw new InvalidArgumentException($group . ' is not a valid database connection group.');
|
||||
}
|
||||
|
||||
$config = $dbConfig->{$group};
|
||||
}
|
||||
|
||||
if ($getShared && isset(static::$instances[$group])) {
|
||||
return static::$instances[$group];
|
||||
}
|
||||
|
||||
static::ensureFactory();
|
||||
|
||||
$connection = static::$factory->load($config, $group);
|
||||
|
||||
static::$instances[$group] = $connection;
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all db connections currently made.
|
||||
*/
|
||||
public static function getConnections(): array
|
||||
{
|
||||
return static::$instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and returns an instance of the Forge for the specified
|
||||
* database group, and loads the group if it hasn't been loaded yet.
|
||||
*
|
||||
* @param array|ConnectionInterface|string|null $group
|
||||
*
|
||||
* @return Forge
|
||||
*/
|
||||
public static function forge($group = null)
|
||||
{
|
||||
$db = static::connect($group);
|
||||
|
||||
return static::$factory->loadForge($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of the Database Utilities class.
|
||||
*
|
||||
* @param array|string|null $group
|
||||
*
|
||||
* @return BaseUtils
|
||||
*/
|
||||
public static function utils($group = null)
|
||||
{
|
||||
$db = static::connect($group);
|
||||
|
||||
return static::$factory->loadUtils($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of the Database Seeder.
|
||||
*
|
||||
* @phpstan-param null|non-empty-string $group
|
||||
*
|
||||
* @return Seeder
|
||||
*/
|
||||
public static function seeder(?string $group = null)
|
||||
{
|
||||
$config = config('Database');
|
||||
|
||||
return new Seeder($config, static::connect($group));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the database Connection Manager/Factory is loaded and ready to use.
|
||||
*/
|
||||
protected static function ensureFactory()
|
||||
{
|
||||
if (static::$factory instanceof Database) {
|
||||
return;
|
||||
}
|
||||
|
||||
static::$factory = new Database();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
/**
|
||||
* Interface ConnectionInterface
|
||||
*/
|
||||
interface ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Initializes the database connection/settings.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function initialize();
|
||||
|
||||
/**
|
||||
* Connect to the database.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function connect(bool $persistent = false);
|
||||
|
||||
/**
|
||||
* Create a persistent database connection.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function persistentConnect();
|
||||
|
||||
/**
|
||||
* Keep or establish the connection if no queries have been sent for
|
||||
* a length of time exceeding the server's idle timeout.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function reconnect();
|
||||
|
||||
/**
|
||||
* Returns the actual connection object. If both a 'read' and 'write'
|
||||
* connection has been specified, you can pass either term in to
|
||||
* get that connection. If you pass either alias in and only a single
|
||||
* connection is present, it must return the sole connection.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConnection(?string $alias = null);
|
||||
|
||||
/**
|
||||
* Select a specific database table to use.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setDatabase(string $databaseName);
|
||||
|
||||
/**
|
||||
* Returns the name of the current database being used.
|
||||
*/
|
||||
public function getDatabase(): string;
|
||||
|
||||
/**
|
||||
* Returns the last error encountered by this connection.
|
||||
* Must return this format: ['code' => string|int, 'message' => string]
|
||||
* intval(code) === 0 means "no error".
|
||||
*
|
||||
* @return array<string, int|string>
|
||||
*/
|
||||
public function error(): array;
|
||||
|
||||
/**
|
||||
* The name of the platform in use (MySQLi, mssql, etc)
|
||||
*/
|
||||
public function getPlatform(): string;
|
||||
|
||||
/**
|
||||
* Returns a string containing the version of the database being used.
|
||||
*/
|
||||
public function getVersion(): string;
|
||||
|
||||
/**
|
||||
* Orchestrates a query against the database. Queries must use
|
||||
* Database\Statement objects to store the query and build it.
|
||||
* This method works with the cache.
|
||||
*
|
||||
* Should automatically handle different connections for read/write
|
||||
* queries if needed.
|
||||
*
|
||||
* @param mixed ...$binds
|
||||
*
|
||||
* @return BaseResult|bool|Query
|
||||
*/
|
||||
public function query(string $sql, $binds = null);
|
||||
|
||||
/**
|
||||
* Performs a basic query against the database. No binding or caching
|
||||
* is performed, nor are transactions handled. Simply takes a raw
|
||||
* query string and returns the database-specific result id.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function simpleQuery(string $sql);
|
||||
|
||||
/**
|
||||
* Returns an instance of the query builder for this connection.
|
||||
*
|
||||
* @param array|string $tableName Table name.
|
||||
*
|
||||
* @return BaseBuilder Builder.
|
||||
*/
|
||||
public function table($tableName);
|
||||
|
||||
/**
|
||||
* Returns the last query's statement object.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLastQuery();
|
||||
|
||||
/**
|
||||
* "Smart" Escaping
|
||||
*
|
||||
* Escapes data based on type.
|
||||
* Sets boolean and null types.
|
||||
*
|
||||
* @param array|bool|float|int|object|string|null $str
|
||||
*
|
||||
* @return array|float|int|string
|
||||
* @phpstan-return ($str is array ? array : float|int|string)
|
||||
*/
|
||||
public function escape($str);
|
||||
|
||||
/**
|
||||
* Allows for custom calls to the database engine that are not
|
||||
* supported through our database layer.
|
||||
*
|
||||
* @param array ...$params
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function callFunction(string $functionName, ...$params);
|
||||
|
||||
/**
|
||||
* Determines if the statement is a write-type query or not.
|
||||
*
|
||||
* @param string $sql
|
||||
*/
|
||||
public function isWriteType($sql): bool;
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Initialize the database
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*
|
||||
* @param string|string[] $params
|
||||
* @param bool $query_builder_override
|
||||
* Determines if query builder should be used or not
|
||||
*/
|
||||
function &DB($params = '', $query_builder_override = NULL)
|
||||
{
|
||||
// Load the DB config file if a DSN string wasn't passed
|
||||
if (is_string($params) && strpos($params, '://') === FALSE)
|
||||
{
|
||||
// Is the config file in the environment folder?
|
||||
if ( ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/database.php')
|
||||
&& ! file_exists($file_path = APPPATH.'config/database.php'))
|
||||
{
|
||||
show_error('The configuration file database.php does not exist.');
|
||||
}
|
||||
|
||||
include($file_path);
|
||||
|
||||
// Make packages contain database config files,
|
||||
// given that the controller instance already exists
|
||||
if (class_exists('CI_Controller', FALSE))
|
||||
{
|
||||
foreach (get_instance()->load->get_package_paths() as $path)
|
||||
{
|
||||
if ($path !== APPPATH)
|
||||
{
|
||||
if (file_exists($file_path = $path.'config/'.ENVIRONMENT.'/database.php'))
|
||||
{
|
||||
include($file_path);
|
||||
}
|
||||
elseif (file_exists($file_path = $path.'config/database.php'))
|
||||
{
|
||||
include($file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset($db) OR count($db) === 0)
|
||||
{
|
||||
show_error('No database connection settings were found in the database config file.');
|
||||
}
|
||||
|
||||
if ($params !== '')
|
||||
{
|
||||
$active_group = $params;
|
||||
}
|
||||
|
||||
if ( ! isset($active_group))
|
||||
{
|
||||
show_error('You have not specified a database connection group via $active_group in your config/database.php file.');
|
||||
}
|
||||
elseif ( ! isset($db[$active_group]))
|
||||
{
|
||||
show_error('You have specified an invalid database connection group ('.$active_group.') in your config/database.php file.');
|
||||
}
|
||||
|
||||
$params = $db[$active_group];
|
||||
}
|
||||
elseif (is_string($params))
|
||||
{
|
||||
/**
|
||||
* Parse the URL from the DSN string
|
||||
* Database settings can be passed as discreet
|
||||
* parameters or as a data source name in the first
|
||||
* parameter. DSNs must have this prototype:
|
||||
* $dsn = 'driver://username:password@hostname/database';
|
||||
*/
|
||||
if (($dsn = @parse_url($params)) === FALSE)
|
||||
{
|
||||
show_error('Invalid DB Connection String');
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'dbdriver' => $dsn['scheme'],
|
||||
'hostname' => isset($dsn['host']) ? rawurldecode($dsn['host']) : '',
|
||||
'port' => isset($dsn['port']) ? rawurldecode($dsn['port']) : '',
|
||||
'username' => isset($dsn['user']) ? rawurldecode($dsn['user']) : '',
|
||||
'password' => isset($dsn['pass']) ? rawurldecode($dsn['pass']) : '',
|
||||
'database' => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : ''
|
||||
);
|
||||
|
||||
// Were additional config items set?
|
||||
if (isset($dsn['query']))
|
||||
{
|
||||
parse_str($dsn['query'], $extra);
|
||||
|
||||
foreach ($extra as $key => $val)
|
||||
{
|
||||
if (is_string($val) && in_array(strtoupper($val), array('TRUE', 'FALSE', 'NULL')))
|
||||
{
|
||||
$val = var_export($val, TRUE);
|
||||
}
|
||||
|
||||
$params[$key] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No DB specified yet? Beat them senseless...
|
||||
if (empty($params['dbdriver']))
|
||||
{
|
||||
show_error('You have not selected a database type to connect to.');
|
||||
}
|
||||
|
||||
// Load the DB classes. Note: Since the query builder class is optional
|
||||
// we need to dynamically create a class that extends proper parent class
|
||||
// based on whether we're using the query builder class or not.
|
||||
if ($query_builder_override !== NULL)
|
||||
{
|
||||
$query_builder = $query_builder_override;
|
||||
}
|
||||
// Backwards compatibility work-around for keeping the
|
||||
// $active_record config variable working. Should be
|
||||
// removed in v3.1
|
||||
elseif ( ! isset($query_builder) && isset($active_record))
|
||||
{
|
||||
$query_builder = $active_record;
|
||||
}
|
||||
|
||||
require_once(BASEPATH.'database/DB_driver.php');
|
||||
|
||||
if ( ! isset($query_builder) OR $query_builder === TRUE)
|
||||
{
|
||||
require_once(BASEPATH.'database/DB_query_builder.php');
|
||||
if ( ! class_exists('CI_DB', FALSE))
|
||||
{
|
||||
/**
|
||||
* CI_DB
|
||||
*
|
||||
* Acts as an alias for both CI_DB_driver and CI_DB_query_builder.
|
||||
*
|
||||
* @see CI_DB_query_builder
|
||||
* @see CI_DB_driver
|
||||
*/
|
||||
class CI_DB extends CI_DB_query_builder { }
|
||||
}
|
||||
}
|
||||
elseif ( ! class_exists('CI_DB', FALSE))
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class CI_DB extends CI_DB_driver { }
|
||||
}
|
||||
|
||||
// Load the DB driver
|
||||
$driver_file = BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php';
|
||||
|
||||
file_exists($driver_file) OR show_error('Invalid DB driver');
|
||||
require_once($driver_file);
|
||||
|
||||
// Instantiate the DB adapter
|
||||
$driver = 'CI_DB_'.$params['dbdriver'].'_driver';
|
||||
$DB = new $driver($params);
|
||||
|
||||
// Check for a subdriver
|
||||
if ( ! empty($DB->subdriver))
|
||||
{
|
||||
$driver_file = BASEPATH.'database/drivers/'.$DB->dbdriver.'/subdrivers/'.$DB->dbdriver.'_'.$DB->subdriver.'_driver.php';
|
||||
|
||||
if (file_exists($driver_file))
|
||||
{
|
||||
require_once($driver_file);
|
||||
$driver = 'CI_DB_'.$DB->dbdriver.'_'.$DB->subdriver.'_driver';
|
||||
$DB = new $driver($params);
|
||||
}
|
||||
}
|
||||
|
||||
$DB->initialize();
|
||||
return $DB;
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Database Cache Class
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_Cache {
|
||||
|
||||
/**
|
||||
* CI Singleton
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
public $CI;
|
||||
|
||||
/**
|
||||
* Database object
|
||||
*
|
||||
* Allows passing of DB object so that multiple database connections
|
||||
* and returned DB objects can be supported.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
public $db;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object &$db
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(&$db)
|
||||
{
|
||||
// Assign the main CI object to $this->CI and load the file helper since we use it a lot
|
||||
$this->CI =& get_instance();
|
||||
$this->db =& $db;
|
||||
$this->CI->load->helper('file');
|
||||
|
||||
$this->check_path();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set Cache Directory Path
|
||||
*
|
||||
* @param string $path Path to the cache directory
|
||||
* @return bool
|
||||
*/
|
||||
public function check_path($path = '')
|
||||
{
|
||||
if ($path === '')
|
||||
{
|
||||
if ($this->db->cachedir === '')
|
||||
{
|
||||
return $this->db->cache_off();
|
||||
}
|
||||
|
||||
$path = $this->db->cachedir;
|
||||
}
|
||||
|
||||
// Add a trailing slash to the path if needed
|
||||
$path = realpath($path)
|
||||
? rtrim(realpath($path), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR
|
||||
: rtrim($path, '/').'/';
|
||||
|
||||
if ( ! is_dir($path))
|
||||
{
|
||||
log_message('debug', 'DB cache path error: '.$path);
|
||||
|
||||
// If the path is wrong we'll turn off caching
|
||||
return $this->db->cache_off();
|
||||
}
|
||||
|
||||
if ( ! is_really_writable($path))
|
||||
{
|
||||
log_message('debug', 'DB cache dir not writable: '.$path);
|
||||
|
||||
// If the path is not really writable we'll turn off caching
|
||||
return $this->db->cache_off();
|
||||
}
|
||||
|
||||
$this->db->cachedir = $path;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retrieve a cached query
|
||||
*
|
||||
* The URI being requested will become the name of the cache sub-folder.
|
||||
* An MD5 hash of the SQL statement will become the cache file name.
|
||||
*
|
||||
* @param string $sql
|
||||
* @return string
|
||||
*/
|
||||
public function read($sql)
|
||||
{
|
||||
$segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
|
||||
$segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
|
||||
$filepath = $this->db->cachedir.$segment_one.'+'.$segment_two.'/'.md5($sql);
|
||||
|
||||
if ( ! is_file($filepath) OR FALSE === ($cachedata = file_get_contents($filepath)))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return unserialize($cachedata);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Write a query to a cache file
|
||||
*
|
||||
* @param string $sql
|
||||
* @param object $object
|
||||
* @return bool
|
||||
*/
|
||||
public function write($sql, $object)
|
||||
{
|
||||
$segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
|
||||
$segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
|
||||
$dir_path = $this->db->cachedir.$segment_one.'+'.$segment_two.'/';
|
||||
$filename = md5($sql);
|
||||
|
||||
if ( ! is_dir($dir_path) && ! @mkdir($dir_path, 0750))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (write_file($dir_path.$filename, serialize($object)) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
chmod($dir_path.$filename, 0640);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Delete cache files within a particular directory
|
||||
*
|
||||
* @param string $segment_one
|
||||
* @param string $segment_two
|
||||
* @return void
|
||||
*/
|
||||
public function delete($segment_one = '', $segment_two = '')
|
||||
{
|
||||
if ($segment_one === '')
|
||||
{
|
||||
$segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
|
||||
}
|
||||
|
||||
if ($segment_two === '')
|
||||
{
|
||||
$segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
|
||||
}
|
||||
|
||||
$dir_path = $this->db->cachedir.$segment_one.'+'.$segment_two.'/';
|
||||
delete_files($dir_path, TRUE);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Delete all existing cache files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
delete_files($this->db->cachedir, TRUE, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,666 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Database Result Class
|
||||
*
|
||||
* This is the platform-independent result class.
|
||||
* This class will not be called directly. Rather, the adapter
|
||||
* class for the specific database will extend and instantiate it.
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_result {
|
||||
|
||||
/**
|
||||
* Connection ID
|
||||
*
|
||||
* @var resource|object
|
||||
*/
|
||||
public $conn_id;
|
||||
|
||||
/**
|
||||
* Result ID
|
||||
*
|
||||
* @var resource|object
|
||||
*/
|
||||
public $result_id;
|
||||
|
||||
/**
|
||||
* Result Array
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
public $result_array = array();
|
||||
|
||||
/**
|
||||
* Result Object
|
||||
*
|
||||
* @var object[]
|
||||
*/
|
||||
public $result_object = array();
|
||||
|
||||
/**
|
||||
* Custom Result Object
|
||||
*
|
||||
* @var object[]
|
||||
*/
|
||||
public $custom_result_object = array();
|
||||
|
||||
/**
|
||||
* Current Row index
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $current_row = 0;
|
||||
|
||||
/**
|
||||
* Number of rows
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $num_rows;
|
||||
|
||||
/**
|
||||
* Row data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $row_data;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object $driver_object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(&$driver_object)
|
||||
{
|
||||
$this->conn_id = $driver_object->conn_id;
|
||||
$this->result_id = $driver_object->result_id;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of rows in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_rows()
|
||||
{
|
||||
if (is_int($this->num_rows))
|
||||
{
|
||||
return $this->num_rows;
|
||||
}
|
||||
elseif (count($this->result_array) > 0)
|
||||
{
|
||||
return $this->num_rows = count($this->result_array);
|
||||
}
|
||||
elseif (count($this->result_object) > 0)
|
||||
{
|
||||
return $this->num_rows = count($this->result_object);
|
||||
}
|
||||
|
||||
return $this->num_rows = count($this->result_array());
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Query result. Acts as a wrapper function for the following functions.
|
||||
*
|
||||
* @param string $type 'object', 'array' or a custom class name
|
||||
* @return array
|
||||
*/
|
||||
public function result($type = 'object')
|
||||
{
|
||||
if ($type === 'array')
|
||||
{
|
||||
return $this->result_array();
|
||||
}
|
||||
elseif ($type === 'object')
|
||||
{
|
||||
return $this->result_object();
|
||||
}
|
||||
|
||||
return $this->custom_result_object($type);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Custom query result.
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return array
|
||||
*/
|
||||
public function custom_result_object($class_name)
|
||||
{
|
||||
if (isset($this->custom_result_object[$class_name]))
|
||||
{
|
||||
return $this->custom_result_object[$class_name];
|
||||
}
|
||||
elseif ( ! $this->result_id OR $this->num_rows === 0)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
// Don't fetch the result set again if we already have it
|
||||
$_data = NULL;
|
||||
if (($c = count($this->result_array)) > 0)
|
||||
{
|
||||
$_data = 'result_array';
|
||||
}
|
||||
elseif (($c = count($this->result_object)) > 0)
|
||||
{
|
||||
$_data = 'result_object';
|
||||
}
|
||||
|
||||
if ($_data !== NULL)
|
||||
{
|
||||
for ($i = 0; $i < $c; $i++)
|
||||
{
|
||||
$this->custom_result_object[$class_name][$i] = new $class_name();
|
||||
|
||||
foreach ($this->{$_data}[$i] as $key => $value)
|
||||
{
|
||||
$this->custom_result_object[$class_name][$i]->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->custom_result_object[$class_name];
|
||||
}
|
||||
|
||||
is_null($this->row_data) OR $this->data_seek(0);
|
||||
$this->custom_result_object[$class_name] = array();
|
||||
|
||||
while ($row = $this->_fetch_object($class_name))
|
||||
{
|
||||
$this->custom_result_object[$class_name][] = $row;
|
||||
}
|
||||
|
||||
return $this->custom_result_object[$class_name];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Query result. "object" version.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function result_object()
|
||||
{
|
||||
if (count($this->result_object) > 0)
|
||||
{
|
||||
return $this->result_object;
|
||||
}
|
||||
|
||||
// In the event that query caching is on, the result_id variable
|
||||
// will not be a valid resource so we'll simply return an empty
|
||||
// array.
|
||||
if ( ! $this->result_id OR $this->num_rows === 0)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
if (($c = count($this->result_array)) > 0)
|
||||
{
|
||||
for ($i = 0; $i < $c; $i++)
|
||||
{
|
||||
$this->result_object[$i] = (object) $this->result_array[$i];
|
||||
}
|
||||
|
||||
return $this->result_object;
|
||||
}
|
||||
|
||||
is_null($this->row_data) OR $this->data_seek(0);
|
||||
while ($row = $this->_fetch_object())
|
||||
{
|
||||
$this->result_object[] = $row;
|
||||
}
|
||||
|
||||
return $this->result_object;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Query result. "array" version.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function result_array()
|
||||
{
|
||||
if (count($this->result_array) > 0)
|
||||
{
|
||||
return $this->result_array;
|
||||
}
|
||||
|
||||
// In the event that query caching is on, the result_id variable
|
||||
// will not be a valid resource so we'll simply return an empty
|
||||
// array.
|
||||
if ( ! $this->result_id OR $this->num_rows === 0)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
if (($c = count($this->result_object)) > 0)
|
||||
{
|
||||
for ($i = 0; $i < $c; $i++)
|
||||
{
|
||||
$this->result_array[$i] = (array) $this->result_object[$i];
|
||||
}
|
||||
|
||||
return $this->result_array;
|
||||
}
|
||||
|
||||
is_null($this->row_data) OR $this->data_seek(0);
|
||||
while ($row = $this->_fetch_assoc())
|
||||
{
|
||||
$this->result_array[] = $row;
|
||||
}
|
||||
|
||||
return $this->result_array;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Row
|
||||
*
|
||||
* A wrapper method.
|
||||
*
|
||||
* @param mixed $n
|
||||
* @param string $type 'object' or 'array'
|
||||
* @return mixed
|
||||
*/
|
||||
public function row($n = 0, $type = 'object')
|
||||
{
|
||||
if ( ! is_numeric($n))
|
||||
{
|
||||
// We cache the row data for subsequent uses
|
||||
is_array($this->row_data) OR $this->row_data = $this->row_array(0);
|
||||
|
||||
// array_key_exists() instead of isset() to allow for NULL values
|
||||
if (empty($this->row_data) OR ! array_key_exists($n, $this->row_data))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $this->row_data[$n];
|
||||
}
|
||||
|
||||
if ($type === 'object') return $this->row_object($n);
|
||||
elseif ($type === 'array') return $this->row_array($n);
|
||||
|
||||
return $this->custom_row_object($n, $type);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Assigns an item into a particular column slot
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function set_row($key, $value = NULL)
|
||||
{
|
||||
// We cache the row data for subsequent uses
|
||||
if ( ! is_array($this->row_data))
|
||||
{
|
||||
$this->row_data = $this->row_array(0);
|
||||
}
|
||||
|
||||
if (is_array($key))
|
||||
{
|
||||
foreach ($key as $k => $v)
|
||||
{
|
||||
$this->row_data[$k] = $v;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ($key !== '' && $value !== NULL)
|
||||
{
|
||||
$this->row_data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a single result row - custom object version
|
||||
*
|
||||
* @param int $n
|
||||
* @param string $type
|
||||
* @return object
|
||||
*/
|
||||
public function custom_row_object($n, $type)
|
||||
{
|
||||
isset($this->custom_result_object[$type]) OR $this->custom_result_object[$type] = $this->custom_result_object($type);
|
||||
|
||||
if (count($this->custom_result_object[$type]) === 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ($n !== $this->current_row && isset($this->custom_result_object[$type][$n]))
|
||||
{
|
||||
$this->current_row = $n;
|
||||
}
|
||||
|
||||
return $this->custom_result_object[$type][$this->current_row];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a single result row - object version
|
||||
*
|
||||
* @param int $n
|
||||
* @return object
|
||||
*/
|
||||
public function row_object($n = 0)
|
||||
{
|
||||
$result = $this->result_object();
|
||||
if (count($result) === 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ($n !== $this->current_row && isset($result[$n]))
|
||||
{
|
||||
$this->current_row = $n;
|
||||
}
|
||||
|
||||
return $result[$this->current_row];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a single result row - array version
|
||||
*
|
||||
* @param int $n
|
||||
* @return array
|
||||
*/
|
||||
public function row_array($n = 0)
|
||||
{
|
||||
$result = $this->result_array();
|
||||
if (count($result) === 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ($n !== $this->current_row && isset($result[$n]))
|
||||
{
|
||||
$this->current_row = $n;
|
||||
}
|
||||
|
||||
return $result[$this->current_row];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the "first" row
|
||||
*
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
public function first_row($type = 'object')
|
||||
{
|
||||
$result = $this->result($type);
|
||||
return (count($result) === 0) ? NULL : $result[0];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the "last" row
|
||||
*
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
public function last_row($type = 'object')
|
||||
{
|
||||
$result = $this->result($type);
|
||||
return (count($result) === 0) ? NULL : $result[count($result) - 1];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the "next" row
|
||||
*
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
public function next_row($type = 'object')
|
||||
{
|
||||
$result = $this->result($type);
|
||||
if (count($result) === 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return isset($result[$this->current_row + 1])
|
||||
? $result[++$this->current_row]
|
||||
: NULL;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the "previous" row
|
||||
*
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
public function previous_row($type = 'object')
|
||||
{
|
||||
$result = $this->result($type);
|
||||
if (count($result) === 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (isset($result[$this->current_row - 1]))
|
||||
{
|
||||
--$this->current_row;
|
||||
}
|
||||
return $result[$this->current_row];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns an unbuffered row and move pointer to next row
|
||||
*
|
||||
* @param string $type 'array', 'object' or a custom class name
|
||||
* @return mixed
|
||||
*/
|
||||
public function unbuffered_row($type = 'object')
|
||||
{
|
||||
if ($type === 'array')
|
||||
{
|
||||
return $this->_fetch_assoc();
|
||||
}
|
||||
elseif ($type === 'object')
|
||||
{
|
||||
return $this->_fetch_object();
|
||||
}
|
||||
|
||||
return $this->_fetch_object($type);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The following methods are normally overloaded by the identically named
|
||||
* methods in the platform-specific driver -- except when query caching
|
||||
* is used. When caching is enabled we do not load the other driver.
|
||||
* These functions are primarily here to prevent undefined function errors
|
||||
* when a cached result object is in use. They are not otherwise fully
|
||||
* operational due to the unavailability of the database resource IDs with
|
||||
* cached results.
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* Overridden by driver result classes.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names.
|
||||
*
|
||||
* Overridden by driver result classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data.
|
||||
*
|
||||
* Overridden by driver result classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* Overridden by driver result classes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
$this->result_id = FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Data Seek
|
||||
*
|
||||
* Moves the internal pointer to the desired offset. We call
|
||||
* this internally before fetching results to make sure the
|
||||
* result set starts at zero.
|
||||
*
|
||||
* Overridden by driver result classes.
|
||||
*
|
||||
* @param int $n
|
||||
* @return bool
|
||||
*/
|
||||
public function data_seek($n = 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array.
|
||||
*
|
||||
* Overridden by driver result classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object.
|
||||
*
|
||||
* Overridden by driver result classes.
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
return new $class_name();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,425 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Database Utility Class
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
abstract class CI_DB_utility {
|
||||
|
||||
/**
|
||||
* Database object
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_list_databases = FALSE;
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_optimize_table = FALSE;
|
||||
|
||||
/**
|
||||
* REPAIR TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_repair_table = FALSE;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param object &$db Database object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(&$db)
|
||||
{
|
||||
$this->db =& $db;
|
||||
log_message('info', 'Database Utility Class Initialized');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* List databases
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_databases()
|
||||
{
|
||||
// Is there a cached result?
|
||||
if (isset($this->db->data_cache['db_names']))
|
||||
{
|
||||
return $this->db->data_cache['db_names'];
|
||||
}
|
||||
elseif ($this->_list_databases === FALSE)
|
||||
{
|
||||
return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
|
||||
}
|
||||
|
||||
$this->db->data_cache['db_names'] = array();
|
||||
|
||||
$query = $this->db->query($this->_list_databases);
|
||||
if ($query === FALSE)
|
||||
{
|
||||
return $this->db->data_cache['db_names'];
|
||||
}
|
||||
|
||||
for ($i = 0, $query = $query->result_array(), $c = count($query); $i < $c; $i++)
|
||||
{
|
||||
$this->db->data_cache['db_names'][] = current($query[$i]);
|
||||
}
|
||||
|
||||
return $this->db->data_cache['db_names'];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determine if a particular database exists
|
||||
*
|
||||
* @param string $database_name
|
||||
* @return bool
|
||||
*/
|
||||
public function database_exists($database_name)
|
||||
{
|
||||
return in_array($database_name, $this->list_databases());
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Optimize Table
|
||||
*
|
||||
* @param string $table_name
|
||||
* @return mixed
|
||||
*/
|
||||
public function optimize_table($table_name)
|
||||
{
|
||||
if ($this->_optimize_table === FALSE)
|
||||
{
|
||||
return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
|
||||
}
|
||||
|
||||
$query = $this->db->query(sprintf($this->_optimize_table, $this->db->escape_identifiers($table_name)));
|
||||
if ($query !== FALSE)
|
||||
{
|
||||
$query = $query->result_array();
|
||||
return current($query);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Optimize Database
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function optimize_database()
|
||||
{
|
||||
if ($this->_optimize_table === FALSE)
|
||||
{
|
||||
return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach ($this->db->list_tables() as $table_name)
|
||||
{
|
||||
$res = $this->db->query(sprintf($this->_optimize_table, $this->db->escape_identifiers($table_name)));
|
||||
if (is_bool($res))
|
||||
{
|
||||
return $res;
|
||||
}
|
||||
|
||||
// Build the result array...
|
||||
$res = $res->result_array();
|
||||
$res = current($res);
|
||||
$key = str_replace($this->db->database.'.', '', current($res));
|
||||
$keys = array_keys($res);
|
||||
unset($res[$keys[0]]);
|
||||
|
||||
$result[$key] = $res;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Repair Table
|
||||
*
|
||||
* @param string $table_name
|
||||
* @return mixed
|
||||
*/
|
||||
public function repair_table($table_name)
|
||||
{
|
||||
if ($this->_repair_table === FALSE)
|
||||
{
|
||||
return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
|
||||
}
|
||||
|
||||
$query = $this->db->query(sprintf($this->_repair_table, $this->db->escape_identifiers($table_name)));
|
||||
if (is_bool($query))
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query = $query->result_array();
|
||||
return current($query);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Generate CSV from a query result object
|
||||
*
|
||||
* @param object $query Query result object
|
||||
* @param string $delim Delimiter (default: ,)
|
||||
* @param string $newline Newline character (default: \n)
|
||||
* @param string $enclosure Enclosure (default: ")
|
||||
* @return string
|
||||
*/
|
||||
public function csv_from_result($query, $delim = ',', $newline = "\n", $enclosure = '"')
|
||||
{
|
||||
if ( ! is_object($query) OR ! method_exists($query, 'list_fields'))
|
||||
{
|
||||
show_error('You must submit a valid result object');
|
||||
}
|
||||
|
||||
$out = '';
|
||||
// First generate the headings from the table column names
|
||||
foreach ($query->list_fields() as $name)
|
||||
{
|
||||
$out .= $enclosure.str_replace($enclosure, $enclosure.$enclosure, $name).$enclosure.$delim;
|
||||
}
|
||||
|
||||
$out = substr($out, 0, -strlen($delim)).$newline;
|
||||
|
||||
// Next blast through the result array and build out the rows
|
||||
while ($row = $query->unbuffered_row('array'))
|
||||
{
|
||||
$line = array();
|
||||
foreach ($row as $item)
|
||||
{
|
||||
$line[] = $enclosure.str_replace($enclosure, $enclosure.$enclosure, (string) $item).$enclosure;
|
||||
}
|
||||
$out .= implode($delim, $line).$newline;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Generate XML data from a query result object
|
||||
*
|
||||
* @param object $query Query result object
|
||||
* @param array $params Any preferences
|
||||
* @return string
|
||||
*/
|
||||
public function xml_from_result($query, $params = array())
|
||||
{
|
||||
if ( ! is_object($query) OR ! method_exists($query, 'list_fields'))
|
||||
{
|
||||
show_error('You must submit a valid result object');
|
||||
}
|
||||
|
||||
// Set our default values
|
||||
foreach (array('root' => 'root', 'element' => 'element', 'newline' => "\n", 'tab' => "\t") as $key => $val)
|
||||
{
|
||||
if ( ! isset($params[$key]))
|
||||
{
|
||||
$params[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// Create variables for convenience
|
||||
extract($params);
|
||||
|
||||
// Load the xml helper
|
||||
get_instance()->load->helper('xml');
|
||||
|
||||
// Generate the result
|
||||
$xml = '<'.$root.'>'.$newline;
|
||||
while ($row = $query->unbuffered_row())
|
||||
{
|
||||
$xml .= $tab.'<'.$element.'>'.$newline;
|
||||
foreach ($row as $key => $val)
|
||||
{
|
||||
$xml .= $tab.$tab.'<'.$key.'>'.xml_convert($val).'</'.$key.'>'.$newline;
|
||||
}
|
||||
$xml .= $tab.'</'.$element.'>'.$newline;
|
||||
}
|
||||
|
||||
return $xml.'</'.$root.'>'.$newline;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database Backup
|
||||
*
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function backup($params = array())
|
||||
{
|
||||
// If the parameters have not been submitted as an
|
||||
// array then we know that it is simply the table
|
||||
// name, which is a valid short cut.
|
||||
if (is_string($params))
|
||||
{
|
||||
$params = array('tables' => $params);
|
||||
}
|
||||
|
||||
// Set up our default preferences
|
||||
$prefs = array(
|
||||
'tables' => array(),
|
||||
'ignore' => array(),
|
||||
'filename' => '',
|
||||
'format' => 'gzip', // gzip, zip, txt
|
||||
'add_drop' => TRUE,
|
||||
'add_insert' => TRUE,
|
||||
'newline' => "\n",
|
||||
'foreign_key_checks' => TRUE
|
||||
);
|
||||
|
||||
// Did the user submit any preferences? If so set them....
|
||||
if (count($params) > 0)
|
||||
{
|
||||
foreach ($prefs as $key => $val)
|
||||
{
|
||||
if (isset($params[$key]))
|
||||
{
|
||||
$prefs[$key] = $params[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Are we backing up a complete database or individual tables?
|
||||
// If no table names were submitted we'll fetch the entire table list
|
||||
if (count($prefs['tables']) === 0)
|
||||
{
|
||||
$prefs['tables'] = $this->db->list_tables();
|
||||
}
|
||||
|
||||
// Validate the format
|
||||
if ( ! in_array($prefs['format'], array('gzip', 'zip', 'txt'), TRUE))
|
||||
{
|
||||
$prefs['format'] = 'txt';
|
||||
}
|
||||
|
||||
// Is the encoder supported? If not, we'll either issue an
|
||||
// error or use plain text depending on the debug settings
|
||||
if (($prefs['format'] === 'gzip' && ! function_exists('gzencode'))
|
||||
OR ($prefs['format'] === 'zip' && ! function_exists('gzcompress')))
|
||||
{
|
||||
if ($this->db->db_debug)
|
||||
{
|
||||
return $this->db->display_error('db_unsupported_compression');
|
||||
}
|
||||
|
||||
$prefs['format'] = 'txt';
|
||||
}
|
||||
|
||||
// Was a Zip file requested?
|
||||
if ($prefs['format'] === 'zip')
|
||||
{
|
||||
// Set the filename if not provided (only needed with Zip files)
|
||||
if ($prefs['filename'] === '')
|
||||
{
|
||||
$prefs['filename'] = (count($prefs['tables']) === 1 ? $prefs['tables'] : $this->db->database)
|
||||
.date('Y-m-d_H-i', time()).'.sql';
|
||||
}
|
||||
else
|
||||
{
|
||||
// If they included the .zip file extension we'll remove it
|
||||
if (preg_match('|.+?\.zip$|', $prefs['filename']))
|
||||
{
|
||||
$prefs['filename'] = str_replace('.zip', '', $prefs['filename']);
|
||||
}
|
||||
|
||||
// Tack on the ".sql" file extension if needed
|
||||
if ( ! preg_match('|.+?\.sql$|', $prefs['filename']))
|
||||
{
|
||||
$prefs['filename'] .= '.sql';
|
||||
}
|
||||
}
|
||||
|
||||
// Load the Zip class and output it
|
||||
$CI =& get_instance();
|
||||
$CI->load->library('zip');
|
||||
$CI->zip->add_data($prefs['filename'], $this->_backup($prefs));
|
||||
return $CI->zip->get_zip();
|
||||
}
|
||||
elseif ($prefs['format'] === 'txt') // Was a text file requested?
|
||||
{
|
||||
return $this->_backup($prefs);
|
||||
}
|
||||
elseif ($prefs['format'] === 'gzip') // Was a Gzip file requested?
|
||||
{
|
||||
return gzencode($this->_backup($prefs));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Database Connection Factory
|
||||
*
|
||||
* Creates and returns an instance of the appropriate DatabaseConnection
|
||||
*/
|
||||
class Database
|
||||
{
|
||||
/**
|
||||
* Maintains an array of the instances of all connections that have
|
||||
* been created.
|
||||
*
|
||||
* Helps to keep track of all open connections for performance
|
||||
* monitoring, logging, etc.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $connections = [];
|
||||
|
||||
/**
|
||||
* Parses the connection binds and returns an instance of the driver
|
||||
* ready to go.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function load(array $params = [], string $alias = '')
|
||||
{
|
||||
if ($alias === '') {
|
||||
throw new InvalidArgumentException('You must supply the parameter: alias.');
|
||||
}
|
||||
|
||||
if (! empty($params['DSN']) && strpos($params['DSN'], '://') !== false) {
|
||||
$params = $this->parseDSN($params);
|
||||
}
|
||||
|
||||
if (empty($params['DBDriver'])) {
|
||||
throw new InvalidArgumentException('You have not selected a database type to connect to.');
|
||||
}
|
||||
|
||||
$this->connections[$alias] = $this->initDriver($params['DBDriver'], 'Connection', $params);
|
||||
|
||||
return $this->connections[$alias];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Forge instance for the current database type.
|
||||
*/
|
||||
public function loadForge(ConnectionInterface $db): object
|
||||
{
|
||||
if (! $db->connID) {
|
||||
$db->initialize();
|
||||
}
|
||||
|
||||
return $this->initDriver($db->DBDriver, 'Forge', $db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Utils instance for the current database type.
|
||||
*/
|
||||
public function loadUtils(ConnectionInterface $db): object
|
||||
{
|
||||
if (! $db->connID) {
|
||||
$db->initialize();
|
||||
}
|
||||
|
||||
return $this->initDriver($db->DBDriver, 'Utils', $db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse universal DSN string
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function parseDSN(array $params): array
|
||||
{
|
||||
$dsn = parse_url($params['DSN']);
|
||||
|
||||
if (! $dsn) {
|
||||
throw new InvalidArgumentException('Your DSN connection string is invalid.');
|
||||
}
|
||||
|
||||
$dsnParams = [
|
||||
'DSN' => '',
|
||||
'DBDriver' => $dsn['scheme'],
|
||||
'hostname' => isset($dsn['host']) ? rawurldecode($dsn['host']) : '',
|
||||
'port' => isset($dsn['port']) ? rawurldecode((string) $dsn['port']) : '',
|
||||
'username' => isset($dsn['user']) ? rawurldecode($dsn['user']) : '',
|
||||
'password' => isset($dsn['pass']) ? rawurldecode($dsn['pass']) : '',
|
||||
'database' => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : '',
|
||||
];
|
||||
|
||||
if (! empty($dsn['query'])) {
|
||||
parse_str($dsn['query'], $extra);
|
||||
|
||||
foreach ($extra as $key => $val) {
|
||||
if (is_string($val) && in_array(strtolower($val), ['true', 'false', 'null'], true)) {
|
||||
$val = $val === 'null' ? null : filter_var($val, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
$dsnParams[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($params, $dsnParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database driver.
|
||||
*
|
||||
* @param array|object $argument
|
||||
*/
|
||||
protected function initDriver(string $driver, string $class, $argument): object
|
||||
{
|
||||
$class = $driver . '\\' . $class;
|
||||
|
||||
if (strpos($driver, '\\') === false) {
|
||||
$class = "CodeIgniter\\Database\\{$class}";
|
||||
}
|
||||
|
||||
return new $class($argument);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Exceptions;
|
||||
|
||||
use CodeIgniter\Exceptions\DebugTraceableTrait;
|
||||
use RuntimeException;
|
||||
|
||||
class DataException extends RuntimeException implements ExceptionInterface
|
||||
{
|
||||
use DebugTraceableTrait;
|
||||
|
||||
/**
|
||||
* Used by the Model's trigger() method when the callback cannot be found.
|
||||
*
|
||||
* @return DataException
|
||||
*/
|
||||
public static function forInvalidMethodTriggered(string $method)
|
||||
{
|
||||
return new static(lang('Database.invalidEvent', [$method]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by Model's insert/update methods when there isn't
|
||||
* any data to actually work with.
|
||||
*
|
||||
* @return DataException
|
||||
*/
|
||||
public static function forEmptyDataset(string $mode)
|
||||
{
|
||||
return new static(lang('Database.emptyDataset', [$mode]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by Model's insert/update methods when there is no
|
||||
* primary key defined and Model has option `useAutoIncrement`
|
||||
* set to false.
|
||||
*
|
||||
* @return DataException
|
||||
*/
|
||||
public static function forEmptyPrimaryKey(string $mode)
|
||||
{
|
||||
return new static(lang('Database.emptyPrimaryKey', [$mode]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an argument for one of the Model's methods
|
||||
* were empty or otherwise invalid, and they could not be
|
||||
* to work correctly for that method.
|
||||
*
|
||||
* @return DataException
|
||||
*/
|
||||
public static function forInvalidArgument(string $argument)
|
||||
{
|
||||
return new static(lang('Database.invalidArgument', [$argument]));
|
||||
}
|
||||
|
||||
public static function forInvalidAllowedFields(string $model)
|
||||
{
|
||||
return new static(lang('Database.invalidAllowedFields', [$model]));
|
||||
}
|
||||
|
||||
public static function forTableNotFound(string $table)
|
||||
{
|
||||
return new static(lang('Database.tableNotFound', [$table]));
|
||||
}
|
||||
|
||||
public static function forEmptyInputGiven(string $argument)
|
||||
{
|
||||
return new static(lang('Database.forEmptyInputGiven', [$argument]));
|
||||
}
|
||||
|
||||
public static function forFindColumnHaveMultipleColumns()
|
||||
{
|
||||
return new static(lang('Database.forFindColumnHaveMultipleColumns'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Exceptions;
|
||||
|
||||
use Error;
|
||||
|
||||
class DatabaseException extends Error implements ExceptionInterface
|
||||
{
|
||||
/**
|
||||
* Exit status code
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $code = EXIT_DATABASE;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Exceptions;
|
||||
|
||||
/**
|
||||
* Provides a domain-level interface for broad capture
|
||||
* of all database-related exceptions.
|
||||
*
|
||||
* catch (\CodeIgniter\Database\Exceptions\ExceptionInterface) { ... }
|
||||
*/
|
||||
interface ExceptionInterface extends \CodeIgniter\Exceptions\ExceptionInterface
|
||||
{
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use Config\Database;
|
||||
|
||||
/**
|
||||
* Class Migration
|
||||
*/
|
||||
abstract class Migration
|
||||
{
|
||||
/**
|
||||
* The name of the database group to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $DBGroup;
|
||||
|
||||
/**
|
||||
* Database Connection instance
|
||||
*
|
||||
* @var ConnectionInterface
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Database Forge instance.
|
||||
*
|
||||
* @var Forge
|
||||
*/
|
||||
protected $forge;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Forge $forge
|
||||
*/
|
||||
public function __construct(?Forge $forge = null)
|
||||
{
|
||||
$this->forge = $forge ?? Database::forge($this->DBGroup ?? config('Database')->defaultGroup);
|
||||
|
||||
$this->db = $this->forge->getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database group name this migration uses.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDBGroup(): ?string
|
||||
{
|
||||
return $this->DBGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a migration step.
|
||||
*/
|
||||
abstract public function up();
|
||||
|
||||
/**
|
||||
* Revert a migration step.
|
||||
*/
|
||||
abstract public function down();
|
||||
}
|
||||
@@ -0,0 +1,872 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Exceptions\ConfigException;
|
||||
use Config\Database;
|
||||
use Config\Migrations as MigrationsConfig;
|
||||
use Config\Services;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class MigrationRunner
|
||||
*/
|
||||
class MigrationRunner
|
||||
{
|
||||
/**
|
||||
* Whether or not migrations are allowed to run.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $enabled = false;
|
||||
|
||||
/**
|
||||
* Name of table to store meta information
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* The Namespace where migrations can be found.
|
||||
* `null` is all namespaces.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $namespace;
|
||||
|
||||
/**
|
||||
* The database Group to migrate.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group;
|
||||
|
||||
/**
|
||||
* The migration name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The pattern used to locate migration file versions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex = '/^\d{4}[_-]?\d{2}[_-]?\d{2}[_-]?\d{6}_(\w+)$/';
|
||||
|
||||
/**
|
||||
* The main database connection. Used to store
|
||||
* migration information in.
|
||||
*
|
||||
* @var BaseConnection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* If true, will continue instead of throwing
|
||||
* exceptions.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $silent = false;
|
||||
|
||||
/**
|
||||
* used to return messages for CLI.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cliMessages = [];
|
||||
|
||||
/**
|
||||
* Tracks whether we have already ensured
|
||||
* the table exists or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $tableChecked = false;
|
||||
|
||||
/**
|
||||
* The full path to locate migration files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* The database Group filter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $groupFilter;
|
||||
|
||||
/**
|
||||
* Used to skip current migration.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $groupSkip = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* When passing in $db, you may pass any of the following to connect:
|
||||
* - group name
|
||||
* - existing connection instance
|
||||
* - array of database configuration values
|
||||
*
|
||||
* @param array|ConnectionInterface|string|null $db
|
||||
*
|
||||
* @throws ConfigException
|
||||
*/
|
||||
public function __construct(MigrationsConfig $config, $db = null)
|
||||
{
|
||||
$this->enabled = $config->enabled ?? false;
|
||||
$this->table = $config->table ?? 'migrations';
|
||||
|
||||
// Default name space is the app namespace
|
||||
$this->namespace = APP_NAMESPACE;
|
||||
|
||||
// get default database group
|
||||
$config = config('Database');
|
||||
$this->group = $config->defaultGroup;
|
||||
unset($config);
|
||||
|
||||
// If no db connection passed in, use
|
||||
// default database group.
|
||||
$this->db = db_connect($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate and run all new migrations
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws ConfigException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function latest(?string $group = null)
|
||||
{
|
||||
if (! $this->enabled) {
|
||||
throw ConfigException::forDisabledMigrations();
|
||||
}
|
||||
|
||||
$this->ensureTable();
|
||||
|
||||
if ($group !== null) {
|
||||
$this->groupFilter = $group;
|
||||
$this->setGroup($group);
|
||||
}
|
||||
|
||||
$migrations = $this->findMigrations();
|
||||
|
||||
if (empty($migrations)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->getHistory((string) $group) as $history) {
|
||||
unset($migrations[$this->getObjectUid($history)]);
|
||||
}
|
||||
|
||||
$batch = $this->getLastBatch() + 1;
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
if ($this->migrate('up', $migration)) {
|
||||
if ($this->groupSkip === true) {
|
||||
$this->groupSkip = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addHistory($migration, $batch);
|
||||
} else {
|
||||
$this->regress(-1);
|
||||
|
||||
$message = lang('Migrations.generalFault');
|
||||
|
||||
if ($this->silent) {
|
||||
$this->cliMessages[] = "\t" . CLI::color($message, 'red');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
}
|
||||
|
||||
$data = get_object_vars($this);
|
||||
$data['method'] = 'latest';
|
||||
Events::trigger('migrate', $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate down to a previous batch
|
||||
*
|
||||
* Calls each migration step required to get to the provided batch
|
||||
*
|
||||
* @param int $targetBatch Target batch number, or negative for a relative batch, 0 for all
|
||||
*
|
||||
* @return mixed Current batch number on success, FALSE on failure or no migrations are found
|
||||
*
|
||||
* @throws ConfigException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function regress(int $targetBatch = 0, ?string $group = null)
|
||||
{
|
||||
if (! $this->enabled) {
|
||||
throw ConfigException::forDisabledMigrations();
|
||||
}
|
||||
|
||||
// Set database group if not null
|
||||
if ($group !== null) {
|
||||
$this->setGroup($group);
|
||||
}
|
||||
|
||||
$this->ensureTable();
|
||||
|
||||
$batches = $this->getBatches();
|
||||
|
||||
if ($targetBatch < 0) {
|
||||
$targetBatch = $batches[count($batches) - 1 + $targetBatch] ?? 0;
|
||||
}
|
||||
|
||||
if (empty($batches) && $targetBatch === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($targetBatch !== 0 && ! in_array($targetBatch, $batches, true)) {
|
||||
$message = lang('Migrations.batchNotFound') . $targetBatch;
|
||||
|
||||
if ($this->silent) {
|
||||
$this->cliMessages[] = "\t" . CLI::color($message, 'red');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$tmpNamespace = $this->namespace;
|
||||
|
||||
$this->namespace = null;
|
||||
$allMigrations = $this->findMigrations();
|
||||
|
||||
$migrations = [];
|
||||
|
||||
while ($batch = array_pop($batches)) {
|
||||
if ($batch <= $targetBatch) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($this->getBatchHistory($batch, 'desc') as $history) {
|
||||
$uid = $this->getObjectUid($history);
|
||||
|
||||
if (! isset($allMigrations[$uid])) {
|
||||
$message = lang('Migrations.gap') . ' ' . $history->version;
|
||||
|
||||
if ($this->silent) {
|
||||
$this->cliMessages[] = "\t" . CLI::color($message, 'red');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$migration = $allMigrations[$uid];
|
||||
$migration->history = $history;
|
||||
$migrations[] = $migration;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
if ($this->migrate('down', $migration)) {
|
||||
$this->removeHistory($migration->history);
|
||||
} else {
|
||||
$message = lang('Migrations.generalFault');
|
||||
|
||||
if ($this->silent) {
|
||||
$this->cliMessages[] = "\t" . CLI::color($message, 'red');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
}
|
||||
|
||||
$data = get_object_vars($this);
|
||||
$data['method'] = 'regress';
|
||||
Events::trigger('migrate', $data);
|
||||
|
||||
$this->namespace = $tmpNamespace;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate a single file regardless of order or batches.
|
||||
* Method "up" or "down" determined by presence in history.
|
||||
* NOTE: This is not recommended and provided mostly for testing.
|
||||
*
|
||||
* @param string $path Full path to a valid migration file
|
||||
* @param string $path Namespace of the target migration
|
||||
*/
|
||||
public function force(string $path, string $namespace, ?string $group = null)
|
||||
{
|
||||
if (! $this->enabled) {
|
||||
throw ConfigException::forDisabledMigrations();
|
||||
}
|
||||
|
||||
$this->ensureTable();
|
||||
|
||||
if ($group !== null) {
|
||||
$this->groupFilter = $group;
|
||||
$this->setGroup($group);
|
||||
}
|
||||
|
||||
$migration = $this->migrationFromFile($path, $namespace);
|
||||
if (empty($migration)) {
|
||||
$message = lang('Migrations.notFound');
|
||||
|
||||
if ($this->silent) {
|
||||
$this->cliMessages[] = "\t" . CLI::color($message, 'red');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$method = 'up';
|
||||
$this->setNamespace($migration->namespace);
|
||||
|
||||
foreach ($this->getHistory($this->group) as $history) {
|
||||
if ($this->getObjectUid($history) === $migration->uid) {
|
||||
$method = 'down';
|
||||
$migration->history = $history;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($method === 'up') {
|
||||
$batch = $this->getLastBatch() + 1;
|
||||
|
||||
if ($this->migrate('up', $migration) && $this->groupSkip === false) {
|
||||
$this->addHistory($migration, $batch);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->groupSkip = false;
|
||||
} elseif ($this->migrate('down', $migration)) {
|
||||
$this->removeHistory($migration->history);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = lang('Migrations.generalFault');
|
||||
|
||||
if ($this->silent) {
|
||||
$this->cliMessages[] = "\t" . CLI::color($message, 'red');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves list of available migration scripts
|
||||
*
|
||||
* @return array List of all located migrations by their UID
|
||||
*/
|
||||
public function findMigrations(): array
|
||||
{
|
||||
$namespaces = $this->namespace ? [$this->namespace] : array_keys(Services::autoloader()->getNamespace());
|
||||
$migrations = [];
|
||||
|
||||
foreach ($namespaces as $namespace) {
|
||||
if (ENVIRONMENT !== 'testing' && $namespace === 'Tests\Support') {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->findNamespaceMigrations($namespace) as $migration) {
|
||||
$migrations[$migration->uid] = $migration;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort migrations ascending by their UID (version)
|
||||
ksort($migrations);
|
||||
|
||||
return $migrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of available migration scripts for one namespace
|
||||
*/
|
||||
public function findNamespaceMigrations(string $namespace): array
|
||||
{
|
||||
$migrations = [];
|
||||
$locator = Services::locator(true);
|
||||
|
||||
if (! empty($this->path)) {
|
||||
helper('filesystem');
|
||||
$dir = rtrim($this->path, DIRECTORY_SEPARATOR) . '/';
|
||||
$files = get_filenames($dir, true, false, false);
|
||||
} else {
|
||||
$files = $locator->listNamespaceFiles($namespace, '/Database/Migrations/');
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$file = empty($this->path) ? $file : $this->path . str_replace($this->path, '', $file);
|
||||
|
||||
if ($migration = $this->migrationFromFile($file, $namespace)) {
|
||||
$migrations[] = $migration;
|
||||
}
|
||||
}
|
||||
|
||||
return $migrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a migration object from a file path.
|
||||
*
|
||||
* @return false|object Returns the migration object, or false on failure
|
||||
*/
|
||||
protected function migrationFromFile(string $path, string $namespace)
|
||||
{
|
||||
if (substr($path, -4) !== '.php') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$name = basename($path, '.php');
|
||||
|
||||
if (! preg_match($this->regex, $name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$locator = Services::locator(true);
|
||||
|
||||
$migration = new stdClass();
|
||||
|
||||
$migration->version = $this->getMigrationNumber($name);
|
||||
$migration->name = $this->getMigrationName($name);
|
||||
$migration->path = $path;
|
||||
$migration->class = $locator->getClassname($path);
|
||||
$migration->namespace = $namespace;
|
||||
$migration->uid = $this->getObjectUid($migration);
|
||||
|
||||
return $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows other scripts to modify on the fly as needed.
|
||||
*
|
||||
* @return MigrationRunner
|
||||
*/
|
||||
public function setNamespace(?string $namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows other scripts to modify on the fly as needed.
|
||||
*
|
||||
* @return MigrationRunner
|
||||
*/
|
||||
public function setGroup(string $group)
|
||||
{
|
||||
$this->group = $group;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MigrationRunner
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If $silent == true, then will not throw exceptions and will
|
||||
* attempt to continue gracefully.
|
||||
*
|
||||
* @return MigrationRunner
|
||||
*/
|
||||
public function setSilent(bool $silent)
|
||||
{
|
||||
$this->silent = $silent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the migration number from a filename
|
||||
*/
|
||||
protected function getMigrationNumber(string $migration): string
|
||||
{
|
||||
preg_match('/^\d{4}[_-]?\d{2}[_-]?\d{2}[_-]?\d{6}/', $migration, $matches);
|
||||
|
||||
return count($matches) ? $matches[0] : '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the migration class name from a filename
|
||||
*/
|
||||
protected function getMigrationName(string $migration): string
|
||||
{
|
||||
$parts = explode('_', $migration);
|
||||
array_shift($parts);
|
||||
|
||||
return implode('_', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the non-repeatable portions of a migration or history
|
||||
* to create a sortable unique key
|
||||
*
|
||||
* @param object $object migration or $history
|
||||
*/
|
||||
public function getObjectUid($object): string
|
||||
{
|
||||
return preg_replace('/[^0-9]/', '', $object->version) . $object->class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves messages formatted for CLI output
|
||||
*/
|
||||
public function getCliMessages(): array
|
||||
{
|
||||
return $this->cliMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any CLI messages.
|
||||
*
|
||||
* @return MigrationRunner
|
||||
*/
|
||||
public function clearCliMessages()
|
||||
{
|
||||
$this->cliMessages = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates the history table.
|
||||
*/
|
||||
public function clearHistory()
|
||||
{
|
||||
if ($this->db->tableExists($this->table)) {
|
||||
$this->db->table($this->table)->truncate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a history to the table.
|
||||
*
|
||||
* @param object $migration
|
||||
*/
|
||||
protected function addHistory($migration, int $batch)
|
||||
{
|
||||
$this->db->table($this->table)->insert([
|
||||
'version' => $migration->version,
|
||||
'class' => $migration->class,
|
||||
'group' => $this->group,
|
||||
'namespace' => $migration->namespace,
|
||||
'time' => time(),
|
||||
'batch' => $batch,
|
||||
]);
|
||||
|
||||
if (is_cli()) {
|
||||
$this->cliMessages[] = sprintf(
|
||||
"\t%s(%s) %s_%s",
|
||||
CLI::color(lang('Migrations.added'), 'yellow'),
|
||||
$migration->namespace,
|
||||
$migration->version,
|
||||
$migration->class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single history
|
||||
*
|
||||
* @param object $history
|
||||
*/
|
||||
protected function removeHistory($history)
|
||||
{
|
||||
$this->db->table($this->table)->where('id', $history->id)->delete();
|
||||
|
||||
if (is_cli()) {
|
||||
$this->cliMessages[] = sprintf(
|
||||
"\t%s(%s) %s_%s",
|
||||
CLI::color(lang('Migrations.removed'), 'yellow'),
|
||||
$history->namespace,
|
||||
$history->version,
|
||||
$history->class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the full migration history from the database for a group
|
||||
*/
|
||||
public function getHistory(string $group = 'default'): array
|
||||
{
|
||||
$this->ensureTable();
|
||||
|
||||
$builder = $this->db->table($this->table);
|
||||
|
||||
// If group was specified then use it
|
||||
if (! empty($group)) {
|
||||
$builder->where('group', $group);
|
||||
}
|
||||
|
||||
// If a namespace was specified then use it
|
||||
if ($this->namespace) {
|
||||
$builder->where('namespace', $this->namespace);
|
||||
}
|
||||
|
||||
$query = $builder->orderBy('id', 'ASC')->get();
|
||||
|
||||
return ! empty($query) ? $query->getResultObject() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the migration history for a single batch.
|
||||
*
|
||||
* @param string $order
|
||||
*/
|
||||
public function getBatchHistory(int $batch, $order = 'asc'): array
|
||||
{
|
||||
$this->ensureTable();
|
||||
|
||||
$query = $this->db->table($this->table)
|
||||
->where('batch', $batch)
|
||||
->orderBy('id', $order)
|
||||
->get();
|
||||
|
||||
return ! empty($query) ? $query->getResultObject() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the batches from the database history in order
|
||||
*/
|
||||
public function getBatches(): array
|
||||
{
|
||||
$this->ensureTable();
|
||||
|
||||
$batches = $this->db->table($this->table)
|
||||
->select('batch')
|
||||
->distinct()
|
||||
->orderBy('batch', 'asc')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
return array_map('intval', array_column($batches, 'batch'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the last batch in the database.
|
||||
*/
|
||||
public function getLastBatch(): int
|
||||
{
|
||||
$this->ensureTable();
|
||||
|
||||
$batch = $this->db->table($this->table)
|
||||
->selectMax('batch')
|
||||
->get()
|
||||
->getResultObject();
|
||||
|
||||
$batch = is_array($batch) && count($batch)
|
||||
? end($batch)->batch
|
||||
: 0;
|
||||
|
||||
return (int) $batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version number of the first migration for a batch.
|
||||
* Mostly just for tests.
|
||||
*/
|
||||
public function getBatchStart(int $batch): string
|
||||
{
|
||||
if ($batch < 0) {
|
||||
$batches = $this->getBatches();
|
||||
$batch = $batches[count($batches) - 1] ?? 0;
|
||||
}
|
||||
|
||||
$migration = $this->db->table($this->table)
|
||||
->where('batch', $batch)
|
||||
->orderBy('id', 'asc')
|
||||
->limit(1)
|
||||
->get()
|
||||
->getResultObject();
|
||||
|
||||
return count($migration) ? $migration[0]->version : '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version number of the last migration for a batch.
|
||||
* Mostly just for tests.
|
||||
*/
|
||||
public function getBatchEnd(int $batch): string
|
||||
{
|
||||
if ($batch < 0) {
|
||||
$batches = $this->getBatches();
|
||||
$batch = $batches[count($batches) - 1] ?? 0;
|
||||
}
|
||||
|
||||
$migration = $this->db->table($this->table)
|
||||
->where('batch', $batch)
|
||||
->orderBy('id', 'desc')
|
||||
->limit(1)
|
||||
->get()
|
||||
->getResultObject();
|
||||
|
||||
return count($migration) ? $migration[0]->version : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that we have created our migrations table
|
||||
* in the database.
|
||||
*/
|
||||
public function ensureTable()
|
||||
{
|
||||
if ($this->tableChecked || $this->db->tableExists($this->table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$forge = Database::forge($this->db);
|
||||
|
||||
$forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'version' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => false,
|
||||
],
|
||||
'class' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => false,
|
||||
],
|
||||
'group' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => false,
|
||||
],
|
||||
'namespace' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => false,
|
||||
],
|
||||
'time' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'null' => false,
|
||||
],
|
||||
'batch' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
]);
|
||||
|
||||
$forge->addPrimaryKey('id');
|
||||
$forge->createTable($this->table, true);
|
||||
|
||||
$this->tableChecked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the actual running of a migration.
|
||||
*
|
||||
* @param string $direction "up" or "down"
|
||||
* @param object $migration The migration to run
|
||||
*/
|
||||
protected function migrate($direction, $migration): bool
|
||||
{
|
||||
include_once $migration->path;
|
||||
|
||||
$class = $migration->class;
|
||||
$this->setName($migration->name);
|
||||
|
||||
// Validate the migration file structure
|
||||
if (! class_exists($class, false)) {
|
||||
$message = sprintf(lang('Migrations.classNotFound'), $class);
|
||||
|
||||
if ($this->silent) {
|
||||
$this->cliMessages[] = "\t" . CLI::color($message, 'red');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$instance = new $class();
|
||||
$group = $instance->getDBGroup() ?? config('Database')->defaultGroup;
|
||||
|
||||
if (ENVIRONMENT !== 'testing' && $group === 'tests' && $this->groupFilter !== 'tests') {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->groupSkip = true;
|
||||
|
||||
return true;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ($direction === 'up' && $this->groupFilter !== null && $this->groupFilter !== $group) {
|
||||
$this->groupSkip = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->setGroup($group);
|
||||
|
||||
if (! is_callable([$instance, $direction])) {
|
||||
$message = sprintf(lang('Migrations.missingMethod'), $direction);
|
||||
|
||||
if ($this->silent) {
|
||||
$this->cliMessages[] = "\t" . CLI::color($message, 'red');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$instance->{$direction}();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use CodeIgniter\Config\Factories;
|
||||
|
||||
/**
|
||||
* Returns new or shared Model instances
|
||||
*
|
||||
* @deprecated Use CodeIgniter\Config\Factories::models()
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class ModelFactory
|
||||
{
|
||||
/**
|
||||
* Creates new Model instances or returns a shared instance
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get(string $name, bool $getShared = true, ?ConnectionInterface $connection = null)
|
||||
{
|
||||
return Factories::models($name, ['getShared' => $getShared], $connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for injecting mock instances while testing.
|
||||
*
|
||||
* @param object $instance
|
||||
*/
|
||||
public static function injectMock(string $name, $instance)
|
||||
{
|
||||
Factories::injectMock('models', $name, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the static arrays
|
||||
*/
|
||||
public static function reset()
|
||||
{
|
||||
Factories::reset('models');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\MySQLi;
|
||||
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
|
||||
/**
|
||||
* Builder for MySQLi
|
||||
*/
|
||||
class Builder extends BaseBuilder
|
||||
{
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $escapeChar = '`';
|
||||
|
||||
/**
|
||||
* Specifies which sql statements
|
||||
* support the ignore option.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $supportedIgnoreStatements = [
|
||||
'update' => 'IGNORE',
|
||||
'insert' => 'IGNORE',
|
||||
'delete' => 'IGNORE',
|
||||
];
|
||||
|
||||
/**
|
||||
* FROM tables
|
||||
*
|
||||
* Groups tables in FROM clauses if needed, so there is no confusion
|
||||
* about operator precedence.
|
||||
*
|
||||
* Note: This is only used (and overridden) by MySQL.
|
||||
*/
|
||||
protected function _fromTables(): string
|
||||
{
|
||||
if (! empty($this->QBJoin) && count($this->QBFrom) > 1) {
|
||||
return '(' . implode(', ', $this->QBFrom) . ')';
|
||||
}
|
||||
|
||||
return implode(', ', $this->QBFrom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\MySQLi;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use LogicException;
|
||||
use MySQLi;
|
||||
use mysqli_sql_exception;
|
||||
use stdClass;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Connection for MySQLi
|
||||
*/
|
||||
class Connection extends BaseConnection
|
||||
{
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $DBDriver = 'MySQLi';
|
||||
|
||||
/**
|
||||
* DELETE hack flag
|
||||
*
|
||||
* Whether to use the MySQL "delete hack" which allows the number
|
||||
* of affected rows to be shown. Uses a preg_replace when enabled,
|
||||
* adding a bit more processing to all queries.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteHack = true;
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $escapeChar = '`';
|
||||
|
||||
/**
|
||||
* MySQLi object
|
||||
*
|
||||
* Has to be preserved without being assigned to $conn_id.
|
||||
*
|
||||
* @var MySQLi
|
||||
*/
|
||||
public $mysqli;
|
||||
|
||||
/**
|
||||
* MySQLi constant
|
||||
*
|
||||
* For unbuffered queries use `MYSQLI_USE_RESULT`.
|
||||
*
|
||||
* Default mode for buffered queries uses `MYSQLI_STORE_RESULT`.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $resultMode = MYSQLI_STORE_RESULT;
|
||||
|
||||
/**
|
||||
* Connect to the database.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function connect(bool $persistent = false)
|
||||
{
|
||||
// Do we have a socket path?
|
||||
if ($this->hostname[0] === '/') {
|
||||
$hostname = null;
|
||||
$port = null;
|
||||
$socket = $this->hostname;
|
||||
} else {
|
||||
$hostname = ($persistent === true) ? 'p:' . $this->hostname : $this->hostname;
|
||||
$port = empty($this->port) ? null : $this->port;
|
||||
$socket = '';
|
||||
}
|
||||
|
||||
$clientFlags = ($this->compress === true) ? MYSQLI_CLIENT_COMPRESS : 0;
|
||||
$this->mysqli = mysqli_init();
|
||||
|
||||
mysqli_report(MYSQLI_REPORT_ALL & ~MYSQLI_REPORT_INDEX);
|
||||
|
||||
$this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 10);
|
||||
|
||||
if (isset($this->strictOn)) {
|
||||
if ($this->strictOn) {
|
||||
$this->mysqli->options(
|
||||
MYSQLI_INIT_COMMAND,
|
||||
"SET SESSION sql_mode = CONCAT(@@sql_mode, ',', 'STRICT_ALL_TABLES')"
|
||||
);
|
||||
} else {
|
||||
$this->mysqli->options(
|
||||
MYSQLI_INIT_COMMAND,
|
||||
"SET SESSION sql_mode = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||
@@sql_mode,
|
||||
'STRICT_ALL_TABLES,', ''),
|
||||
',STRICT_ALL_TABLES', ''),
|
||||
'STRICT_ALL_TABLES', ''),
|
||||
'STRICT_TRANS_TABLES,', ''),
|
||||
',STRICT_TRANS_TABLES', ''),
|
||||
'STRICT_TRANS_TABLES', '')"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($this->encrypt)) {
|
||||
$ssl = [];
|
||||
|
||||
if (! empty($this->encrypt['ssl_key'])) {
|
||||
$ssl['key'] = $this->encrypt['ssl_key'];
|
||||
}
|
||||
if (! empty($this->encrypt['ssl_cert'])) {
|
||||
$ssl['cert'] = $this->encrypt['ssl_cert'];
|
||||
}
|
||||
if (! empty($this->encrypt['ssl_ca'])) {
|
||||
$ssl['ca'] = $this->encrypt['ssl_ca'];
|
||||
}
|
||||
if (! empty($this->encrypt['ssl_capath'])) {
|
||||
$ssl['capath'] = $this->encrypt['ssl_capath'];
|
||||
}
|
||||
if (! empty($this->encrypt['ssl_cipher'])) {
|
||||
$ssl['cipher'] = $this->encrypt['ssl_cipher'];
|
||||
}
|
||||
|
||||
if (! empty($ssl)) {
|
||||
if (isset($this->encrypt['ssl_verify'])) {
|
||||
if ($this->encrypt['ssl_verify']) {
|
||||
if (defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT')) {
|
||||
$this->mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, 1);
|
||||
}
|
||||
}
|
||||
// Apparently (when it exists), setting MYSQLI_OPT_SSL_VERIFY_SERVER_CERT
|
||||
// to FALSE didn't do anything, so PHP 5.6.16 introduced yet another
|
||||
// constant ...
|
||||
//
|
||||
// https://secure.php.net/ChangeLog-5.php#5.6.16
|
||||
// https://bugs.php.net/bug.php?id=68344
|
||||
elseif (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT') && version_compare($this->mysqli->client_info, 'mysqlnd 5.6', '>=')) {
|
||||
$clientFlags += MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
|
||||
}
|
||||
}
|
||||
|
||||
$this->mysqli->ssl_set(
|
||||
$ssl['key'] ?? null,
|
||||
$ssl['cert'] ?? null,
|
||||
$ssl['ca'] ?? null,
|
||||
$ssl['capath'] ?? null,
|
||||
$ssl['cipher'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
$clientFlags += MYSQLI_CLIENT_SSL;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->mysqli->real_connect(
|
||||
$hostname,
|
||||
$this->username,
|
||||
$this->password,
|
||||
$this->database,
|
||||
$port,
|
||||
$socket,
|
||||
$clientFlags
|
||||
)) {
|
||||
// Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails
|
||||
if (($clientFlags & MYSQLI_CLIENT_SSL) && version_compare($this->mysqli->client_info, 'mysqlnd 5.7.3', '<=')
|
||||
&& empty($this->mysqli->query("SHOW STATUS LIKE 'ssl_cipher'")->fetch_object()->Value)
|
||||
) {
|
||||
$this->mysqli->close();
|
||||
$message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!';
|
||||
log_message('error', $message);
|
||||
|
||||
if ($this->DBDebug) {
|
||||
throw new DatabaseException($message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->mysqli->set_charset($this->charset)) {
|
||||
log_message('error', "Database: Unable to set the configured connection charset ('{$this->charset}').");
|
||||
|
||||
$this->mysqli->close();
|
||||
|
||||
if ($this->DBDebug) {
|
||||
throw new DatabaseException('Unable to set client connection character set: ' . $this->charset);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->mysqli;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// Clean sensitive information from errors.
|
||||
$msg = $e->getMessage();
|
||||
|
||||
$msg = str_replace($this->username, '****', $msg);
|
||||
$msg = str_replace($this->password, '****', $msg);
|
||||
|
||||
throw new DatabaseException($msg, $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep or establish the connection if no queries have been sent for
|
||||
* a length of time exceeding the server's idle timeout.
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
$this->close();
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
$this->connID->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific database table to use.
|
||||
*/
|
||||
public function setDatabase(string $databaseName): bool
|
||||
{
|
||||
if ($databaseName === '') {
|
||||
$databaseName = $this->database;
|
||||
}
|
||||
|
||||
if (empty($this->connID)) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if ($this->connID->select_db($databaseName)) {
|
||||
$this->database = $databaseName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the version of the database being used.
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
if (isset($this->dataCache['version'])) {
|
||||
return $this->dataCache['version'];
|
||||
}
|
||||
|
||||
if (empty($this->mysqli)) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return $this->dataCache['version'] = $this->mysqli->server_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query against the database.
|
||||
*
|
||||
* @return bool|object
|
||||
*/
|
||||
protected function execute(string $sql)
|
||||
{
|
||||
while ($this->connID->more_results()) {
|
||||
$this->connID->next_result();
|
||||
if ($res = $this->connID->store_result()) {
|
||||
$res->free();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->connID->query($this->prepQuery($sql), $this->resultMode);
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
log_message('error', (string) $e);
|
||||
|
||||
if ($this->DBDebug) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prep the query. If needed, each database adapter can prep the query string
|
||||
*/
|
||||
protected function prepQuery(string $sql): string
|
||||
{
|
||||
// mysqli_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack
|
||||
// modifies the query so that it a proper number of affected rows is returned.
|
||||
if ($this->deleteHack === true && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql)) {
|
||||
return trim($sql) . ' WHERE 1=1';
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows affected by this query.
|
||||
*/
|
||||
public function affectedRows(): int
|
||||
{
|
||||
return $this->connID->affected_rows ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-dependant string escape
|
||||
*/
|
||||
protected function _escapeString(string $str): string
|
||||
{
|
||||
if (! $this->connID) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return $this->connID->real_escape_string($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape Like String Direct
|
||||
* There are a few instances where MySQLi queries cannot take the
|
||||
* additional "ESCAPE x" parameter for specifying the escape character
|
||||
* in "LIKE" strings, and this handles those directly with a backslash.
|
||||
*
|
||||
* @param string|string[] $str Input string
|
||||
*
|
||||
* @return string|string[]
|
||||
*/
|
||||
public function escapeLikeStringDirect($str)
|
||||
{
|
||||
if (is_array($str)) {
|
||||
foreach ($str as $key => $val) {
|
||||
$str[$key] = $this->escapeLikeStringDirect($val);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
$str = $this->_escapeString($str);
|
||||
|
||||
// Escape LIKE condition wildcards
|
||||
return str_replace(
|
||||
[$this->likeEscapeChar, '%', '_'],
|
||||
['\\' . $this->likeEscapeChar, '\\%', '\\_'],
|
||||
$str
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SQL for listing tables in a platform-dependent manner.
|
||||
* Uses escapeLikeStringDirect().
|
||||
*
|
||||
* @param string|null $tableName If $tableName is provided will return only this table if exists.
|
||||
*/
|
||||
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
|
||||
{
|
||||
$sql = 'SHOW TABLES FROM ' . $this->escapeIdentifiers($this->database);
|
||||
|
||||
if ($tableName !== null) {
|
||||
return $sql . ' LIKE ' . $this->escape($tableName);
|
||||
}
|
||||
|
||||
if ($prefixLimit !== false && $this->DBPrefix !== '') {
|
||||
return $sql . " LIKE '" . $this->escapeLikeStringDirect($this->DBPrefix) . "%'";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific query string so that the column names can be fetched.
|
||||
*/
|
||||
protected function _listColumns(string $table = ''): string
|
||||
{
|
||||
return 'SHOW COLUMNS FROM ' . $this->protectIdentifiers($table, true, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with field data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _fieldData(string $table): array
|
||||
{
|
||||
$table = $this->protectIdentifiers($table, true, null, false);
|
||||
|
||||
if (($query = $this->query('SHOW COLUMNS FROM ' . $table)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetFieldData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++) {
|
||||
$retVal[$i] = new stdClass();
|
||||
$retVal[$i]->name = $query[$i]->Field;
|
||||
|
||||
sscanf($query[$i]->Type, '%[a-z](%d)', $retVal[$i]->type, $retVal[$i]->max_length);
|
||||
|
||||
$retVal[$i]->nullable = $query[$i]->Null === 'YES';
|
||||
$retVal[$i]->default = $query[$i]->Default;
|
||||
$retVal[$i]->primary_key = (int) ($query[$i]->Key === 'PRI');
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with index data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
* @throws LogicException
|
||||
*/
|
||||
protected function _indexData(string $table): array
|
||||
{
|
||||
$table = $this->protectIdentifiers($table, true, null, false);
|
||||
|
||||
if (($query = $this->query('SHOW INDEX FROM ' . $table)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetIndexData'));
|
||||
}
|
||||
|
||||
if (! $indexes = $query->getResultArray()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
if (empty($keys[$index['Key_name']])) {
|
||||
$keys[$index['Key_name']] = new stdClass();
|
||||
$keys[$index['Key_name']]->name = $index['Key_name'];
|
||||
|
||||
if ($index['Key_name'] === 'PRIMARY') {
|
||||
$type = 'PRIMARY';
|
||||
} elseif ($index['Index_type'] === 'FULLTEXT') {
|
||||
$type = 'FULLTEXT';
|
||||
} elseif ($index['Non_unique']) {
|
||||
$type = $index['Index_type'] === 'SPATIAL' ? 'SPATIAL' : 'INDEX';
|
||||
} else {
|
||||
$type = 'UNIQUE';
|
||||
}
|
||||
|
||||
$keys[$index['Key_name']]->type = $type;
|
||||
}
|
||||
|
||||
$keys[$index['Key_name']]->fields[] = $index['Column_name'];
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with Foreign key data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _foreignKeyData(string $table): array
|
||||
{
|
||||
$sql = '
|
||||
SELECT
|
||||
tc.CONSTRAINT_NAME,
|
||||
tc.TABLE_NAME,
|
||||
kcu.COLUMN_NAME,
|
||||
rc.REFERENCED_TABLE_NAME,
|
||||
kcu.REFERENCED_COLUMN_NAME
|
||||
FROM information_schema.TABLE_CONSTRAINTS AS tc
|
||||
INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc
|
||||
ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
||||
AND tc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
||||
INNER JOIN information_schema.KEY_COLUMN_USAGE AS kcu
|
||||
ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
|
||||
AND tc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
|
||||
WHERE
|
||||
tc.CONSTRAINT_TYPE = ' . $this->escape('FOREIGN KEY') . ' AND
|
||||
tc.TABLE_SCHEMA = ' . $this->escape($this->database) . ' AND
|
||||
tc.TABLE_NAME = ' . $this->escape($table);
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetForeignKeyData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
|
||||
foreach ($query as $row) {
|
||||
$obj = new stdClass();
|
||||
$obj->constraint_name = $row->CONSTRAINT_NAME;
|
||||
$obj->table_name = $row->TABLE_NAME;
|
||||
$obj->column_name = $row->COLUMN_NAME;
|
||||
$obj->foreign_table_name = $row->REFERENCED_TABLE_NAME;
|
||||
$obj->foreign_column_name = $row->REFERENCED_COLUMN_NAME;
|
||||
|
||||
$retVal[] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform-specific SQL to disable foreign key checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _disableForeignKeyChecks()
|
||||
{
|
||||
return 'SET FOREIGN_KEY_CHECKS=0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform-specific SQL to enable foreign key checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _enableForeignKeyChecks()
|
||||
{
|
||||
return 'SET FOREIGN_KEY_CHECKS=1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code and message.
|
||||
* Must return this format: ['code' => string|int, 'message' => string]
|
||||
* intval(code) === 0 means "no error".
|
||||
*
|
||||
* @return array<string, int|string>
|
||||
*/
|
||||
public function error(): array
|
||||
{
|
||||
if (! empty($this->mysqli->connect_errno)) {
|
||||
return [
|
||||
'code' => $this->mysqli->connect_errno,
|
||||
'message' => $this->mysqli->connect_error,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => $this->connID->errno,
|
||||
'message' => $this->connID->error,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*/
|
||||
public function insertID(): int
|
||||
{
|
||||
return $this->connID->insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*/
|
||||
protected function _transBegin(): bool
|
||||
{
|
||||
$this->connID->autocommit(false);
|
||||
|
||||
return $this->connID->begin_transaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*/
|
||||
protected function _transCommit(): bool
|
||||
{
|
||||
if ($this->connID->commit()) {
|
||||
$this->connID->autocommit(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*/
|
||||
protected function _transRollback(): bool
|
||||
{
|
||||
if ($this->connID->rollback()) {
|
||||
$this->connID->autocommit(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\MySQLi;
|
||||
|
||||
use CodeIgniter\Database\Forge as BaseForge;
|
||||
|
||||
/**
|
||||
* Forge for MySQLi
|
||||
*/
|
||||
class Forge extends BaseForge
|
||||
{
|
||||
/**
|
||||
* CREATE DATABASE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createDatabaseStr = 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s';
|
||||
|
||||
/**
|
||||
* CREATE DATABASE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createDatabaseIfStr = 'CREATE DATABASE IF NOT EXISTS %s CHARACTER SET %s COLLATE %s';
|
||||
|
||||
/**
|
||||
* DROP CONSTRAINT statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropConstraintStr = 'ALTER TABLE %s DROP FOREIGN KEY %s';
|
||||
|
||||
/**
|
||||
* CREATE TABLE keys flag
|
||||
*
|
||||
* Whether table keys are created from within the
|
||||
* CREATE TABLE statement.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $createTableKeys = true;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_unsigned = [
|
||||
'TINYINT',
|
||||
'SMALLINT',
|
||||
'MEDIUMINT',
|
||||
'INT',
|
||||
'INTEGER',
|
||||
'BIGINT',
|
||||
'REAL',
|
||||
'DOUBLE',
|
||||
'DOUBLE PRECISION',
|
||||
'FLOAT',
|
||||
'DECIMAL',
|
||||
'NUMERIC',
|
||||
];
|
||||
|
||||
/**
|
||||
* Table Options list which required to be quoted
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_quoted_table_options = [
|
||||
'COMMENT',
|
||||
'COMPRESSION',
|
||||
'CONNECTION',
|
||||
'DATA DIRECTORY',
|
||||
'INDEX DIRECTORY',
|
||||
'ENCRYPTION',
|
||||
'PASSWORD',
|
||||
];
|
||||
|
||||
/**
|
||||
* NULL value representation in CREATE/ALTER TABLE statements
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected $null = 'NULL';
|
||||
|
||||
/**
|
||||
* CREATE TABLE attributes
|
||||
*
|
||||
* @param array $attributes Associative array of table attributes
|
||||
*/
|
||||
protected function _createTableAttributes(array $attributes): string
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
foreach (array_keys($attributes) as $key) {
|
||||
if (is_string($key)) {
|
||||
$sql .= ' ' . strtoupper($key) . ' = ';
|
||||
|
||||
if (in_array(strtoupper($key), $this->_quoted_table_options, true)) {
|
||||
$sql .= $this->db->escape($attributes[$key]);
|
||||
} else {
|
||||
$sql .= $this->db->escapeString($attributes[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($this->db->charset) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET')) {
|
||||
$sql .= ' DEFAULT CHARACTER SET = ' . $this->db->escapeString($this->db->charset);
|
||||
}
|
||||
|
||||
if (! empty($this->db->DBCollat) && ! strpos($sql, 'COLLATE')) {
|
||||
$sql .= ' COLLATE = ' . $this->db->escapeString($this->db->DBCollat);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alterType ALTER type
|
||||
* @param string $table Table name
|
||||
* @param array|string $field Column definition
|
||||
*
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function _alterTable(string $alterType, string $table, $field)
|
||||
{
|
||||
if ($alterType === 'DROP') {
|
||||
return parent::_alterTable($alterType, $table, $field);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
|
||||
|
||||
foreach ($field as $i => $data) {
|
||||
if ($data['_literal'] !== false) {
|
||||
$field[$i] = ($alterType === 'ADD') ? "\n\tADD " . $data['_literal'] : "\n\tMODIFY " . $data['_literal'];
|
||||
} else {
|
||||
if ($alterType === 'ADD') {
|
||||
$field[$i]['_literal'] = "\n\tADD ";
|
||||
} else {
|
||||
$field[$i]['_literal'] = empty($data['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE ";
|
||||
}
|
||||
|
||||
$field[$i] = $field[$i]['_literal'] . $this->_processColumn($field[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return [$sql . implode(',', $field)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*/
|
||||
protected function _processColumn(array $field): string
|
||||
{
|
||||
$extraClause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : '';
|
||||
|
||||
if (empty($extraClause) && isset($field['first']) && $field['first'] === true) {
|
||||
$extraClause = ' FIRST';
|
||||
}
|
||||
|
||||
return $this->db->escapeIdentifiers($field['name'])
|
||||
. (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name']))
|
||||
. ' ' . $field['type'] . $field['length']
|
||||
. $field['unsigned']
|
||||
. $field['null']
|
||||
. $field['default']
|
||||
. $field['auto_increment']
|
||||
. $field['unique']
|
||||
. (empty($field['comment']) ? '' : ' COMMENT ' . $field['comment'])
|
||||
. $extraClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process indexes
|
||||
*
|
||||
* @param string $table (ignored)
|
||||
*/
|
||||
protected function _processIndexes(string $table): string
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
for ($i = 0, $c = count($this->keys); $i < $c; $i++) {
|
||||
if (is_array($this->keys[$i])) {
|
||||
for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) {
|
||||
if (! isset($this->fields[$this->keys[$i][$i2]])) {
|
||||
unset($this->keys[$i][$i2]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} elseif (! isset($this->fields[$this->keys[$i]])) {
|
||||
unset($this->keys[$i]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! is_array($this->keys[$i])) {
|
||||
$this->keys[$i] = [$this->keys[$i]];
|
||||
}
|
||||
|
||||
$unique = in_array($i, $this->uniqueKeys, true) ? 'UNIQUE ' : '';
|
||||
|
||||
$sql .= ",\n\t{$unique}KEY " . $this->db->escapeIdentifiers(implode('_', $this->keys[$i]))
|
||||
. ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ')';
|
||||
}
|
||||
|
||||
$this->keys = [];
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop Key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dropKey(string $table, string $keyName)
|
||||
{
|
||||
$sql = sprintf(
|
||||
$this->dropIndexStr,
|
||||
$this->db->escapeIdentifiers($keyName),
|
||||
$this->db->escapeIdentifiers($this->db->DBPrefix . $table),
|
||||
);
|
||||
|
||||
return $this->db->query($sql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\MySQLi;
|
||||
|
||||
use BadMethodCallException;
|
||||
use CodeIgniter\Database\BasePreparedQuery;
|
||||
|
||||
/**
|
||||
* Prepared query for MySQLi
|
||||
*/
|
||||
class PreparedQuery extends BasePreparedQuery
|
||||
{
|
||||
/**
|
||||
* Prepares the query against the database, and saves the connection
|
||||
* info necessary to execute the query later.
|
||||
*
|
||||
* NOTE: This version is based on SQL code. Child classes should
|
||||
* override this method.
|
||||
*
|
||||
* @param array $options Passed to the connection's prepare statement.
|
||||
* Unused in the MySQLi driver.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _prepare(string $sql, array $options = [])
|
||||
{
|
||||
// Mysqli driver doesn't like statements
|
||||
// with terminating semicolons.
|
||||
$sql = rtrim($sql, ';');
|
||||
|
||||
if (! $this->statement = $this->db->mysqli->prepare($sql)) {
|
||||
$this->errorCode = $this->db->mysqli->errno;
|
||||
$this->errorString = $this->db->mysqli->error;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a new set of data and runs it against the currently
|
||||
* prepared query. Upon success, will return a Results object.
|
||||
*/
|
||||
public function _execute(array $data): bool
|
||||
{
|
||||
if (! isset($this->statement)) {
|
||||
throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
|
||||
}
|
||||
|
||||
// First off -bind the parameters
|
||||
$bindTypes = '';
|
||||
|
||||
// Determine the type string
|
||||
foreach ($data as $item) {
|
||||
if (is_int($item)) {
|
||||
$bindTypes .= 'i';
|
||||
} elseif (is_numeric($item)) {
|
||||
$bindTypes .= 'd';
|
||||
} else {
|
||||
$bindTypes .= 's';
|
||||
}
|
||||
}
|
||||
|
||||
// Bind it
|
||||
$this->statement->bind_param($bindTypes, ...$data);
|
||||
|
||||
return $this->statement->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result object for the prepared query.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _getResult()
|
||||
{
|
||||
return $this->statement->get_result();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\MySQLi;
|
||||
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Result for MySQLi
|
||||
*/
|
||||
class Result extends BaseResult
|
||||
{
|
||||
/**
|
||||
* Gets the number of fields in the result set.
|
||||
*/
|
||||
public function getFieldCount(): int
|
||||
{
|
||||
return $this->resultID->field_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of column names in the result set.
|
||||
*/
|
||||
public function getFieldNames(): array
|
||||
{
|
||||
$fieldNames = [];
|
||||
$this->resultID->field_seek(0);
|
||||
|
||||
while ($field = $this->resultID->fetch_field()) {
|
||||
$fieldNames[] = $field->name;
|
||||
}
|
||||
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of objects representing field meta-data.
|
||||
*/
|
||||
public function getFieldData(): array
|
||||
{
|
||||
static $dataTypes = [
|
||||
MYSQLI_TYPE_DECIMAL => 'decimal',
|
||||
MYSQLI_TYPE_NEWDECIMAL => 'newdecimal',
|
||||
MYSQLI_TYPE_FLOAT => 'float',
|
||||
MYSQLI_TYPE_DOUBLE => 'double',
|
||||
|
||||
MYSQLI_TYPE_BIT => 'bit',
|
||||
MYSQLI_TYPE_SHORT => 'short',
|
||||
MYSQLI_TYPE_LONG => 'long',
|
||||
MYSQLI_TYPE_LONGLONG => 'longlong',
|
||||
MYSQLI_TYPE_INT24 => 'int24',
|
||||
|
||||
MYSQLI_TYPE_YEAR => 'year',
|
||||
|
||||
MYSQLI_TYPE_TIMESTAMP => 'timestamp',
|
||||
MYSQLI_TYPE_DATE => 'date',
|
||||
MYSQLI_TYPE_TIME => 'time',
|
||||
MYSQLI_TYPE_DATETIME => 'datetime',
|
||||
MYSQLI_TYPE_NEWDATE => 'newdate',
|
||||
|
||||
MYSQLI_TYPE_SET => 'set',
|
||||
|
||||
MYSQLI_TYPE_VAR_STRING => 'var_string',
|
||||
MYSQLI_TYPE_STRING => 'string',
|
||||
|
||||
MYSQLI_TYPE_GEOMETRY => 'geometry',
|
||||
MYSQLI_TYPE_TINY_BLOB => 'tiny_blob',
|
||||
MYSQLI_TYPE_MEDIUM_BLOB => 'medium_blob',
|
||||
MYSQLI_TYPE_LONG_BLOB => 'long_blob',
|
||||
MYSQLI_TYPE_BLOB => 'blob',
|
||||
];
|
||||
|
||||
$retVal = [];
|
||||
$fieldData = $this->resultID->fetch_fields();
|
||||
|
||||
foreach ($fieldData as $i => $data) {
|
||||
$retVal[$i] = new stdClass();
|
||||
$retVal[$i]->name = $data->name;
|
||||
$retVal[$i]->type = $data->type;
|
||||
$retVal[$i]->type_name = in_array($data->type, [1, 247], true) ? 'char' : ($dataTypes[$data->type] ?? null);
|
||||
$retVal[$i]->max_length = $data->max_length;
|
||||
$retVal[$i]->primary_key = $data->flags & 2;
|
||||
$retVal[$i]->length = $data->length;
|
||||
$retVal[$i]->default = $data->def;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the current result.
|
||||
*/
|
||||
public function freeResult()
|
||||
{
|
||||
if (is_object($this->resultID)) {
|
||||
$this->resultID->free();
|
||||
$this->resultID = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal pointer to the desired offset. This is called
|
||||
* internally before fetching results to make sure the result set
|
||||
* starts at zero.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function dataSeek(int $n = 0)
|
||||
{
|
||||
return $this->resultID->data_seek($n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an array.
|
||||
*
|
||||
* Overridden by driver classes.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetchAssoc()
|
||||
{
|
||||
return $this->resultID->fetch_assoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an object.
|
||||
*
|
||||
* Overridden by child classes.
|
||||
*
|
||||
* @return bool|Entity|object
|
||||
*/
|
||||
protected function fetchObject(string $className = 'stdClass')
|
||||
{
|
||||
if (is_subclass_of($className, Entity::class)) {
|
||||
return empty($data = $this->fetchAssoc()) ? false : (new $className())->setAttributes($data);
|
||||
}
|
||||
|
||||
return $this->resultID->fetch_object($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of rows in the resultID (i.e., mysqli_result object)
|
||||
*/
|
||||
public function getNumRows(): int
|
||||
{
|
||||
if (! is_int($this->numRows)) {
|
||||
$this->numRows = $this->resultID->num_rows;
|
||||
}
|
||||
|
||||
return $this->numRows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\MySQLi;
|
||||
|
||||
use CodeIgniter\Database\BaseUtils;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Utils for MySQLi
|
||||
*/
|
||||
class Utils extends BaseUtils
|
||||
{
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $listDatabases = 'SHOW DATABASES';
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $optimizeTable = 'OPTIMIZE TABLE %s';
|
||||
|
||||
/**
|
||||
* Platform dependent version of the backup function.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _backup(?array $prefs = null)
|
||||
{
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\OCI8;
|
||||
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Builder for OCI8
|
||||
*/
|
||||
class Builder extends BaseBuilder
|
||||
{
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $escapeChar = '"';
|
||||
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $randomKeyword = [
|
||||
'"DBMS_RANDOM"."RANDOM"',
|
||||
];
|
||||
|
||||
/**
|
||||
* COUNT string
|
||||
*
|
||||
* @used-by CI_DB_driver::count_all()
|
||||
* @used-by BaseBuilder::count_all_results()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $countString = 'SELECT COUNT(1) ';
|
||||
|
||||
/**
|
||||
* Limit used flag
|
||||
*
|
||||
* If we use LIMIT, we'll add a field that will
|
||||
* throw off num_fields later.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $limitUsed = false;
|
||||
|
||||
/**
|
||||
* A reference to the database connection.
|
||||
*
|
||||
* @var Connection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Generates a platform-specific insert string from the supplied data.
|
||||
*/
|
||||
protected function _insertBatch(string $table, array $keys, array $values): string
|
||||
{
|
||||
$insertKeys = implode(', ', $keys);
|
||||
$hasPrimaryKey = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true);
|
||||
|
||||
// ORA-00001 measures
|
||||
if ($hasPrimaryKey) {
|
||||
$sql = 'INSERT INTO ' . $table . ' (' . $insertKeys . ") \n SELECT * FROM (\n";
|
||||
$selectQueryValues = [];
|
||||
|
||||
foreach ($values as $value) {
|
||||
$selectValues = implode(',', array_map(static fn ($value, $key) => $value . ' as ' . $key, explode(',', substr(substr($value, 1), 0, -1)), $keys));
|
||||
$selectQueryValues[] = 'SELECT ' . $selectValues . ' FROM DUAL';
|
||||
}
|
||||
|
||||
return $sql . implode("\n UNION ALL \n", $selectQueryValues) . "\n)";
|
||||
}
|
||||
|
||||
$sql = "INSERT ALL\n";
|
||||
|
||||
foreach ($values as $value) {
|
||||
$sql .= ' INTO ' . $table . ' (' . $insertKeys . ') VALUES ' . $value . "\n";
|
||||
}
|
||||
|
||||
return $sql . 'SELECT * FROM DUAL';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific replace string from the supplied data
|
||||
*/
|
||||
protected function _replace(string $table, array $keys, array $values): string
|
||||
{
|
||||
$fieldNames = array_map(static fn ($columnName) => trim($columnName, '"'), $keys);
|
||||
|
||||
$uniqueIndexes = array_filter($this->db->getIndexData($table), static function ($index) use ($fieldNames) {
|
||||
$hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields);
|
||||
|
||||
return ($index->type === 'PRIMARY') && $hasAllFields;
|
||||
});
|
||||
$replaceableFields = array_filter($keys, static function ($columnName) use ($uniqueIndexes) {
|
||||
foreach ($uniqueIndexes as $index) {
|
||||
if (in_array(trim($columnName, '"'), $index->fields, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$sql = 'MERGE INTO ' . $table . "\n USING (SELECT ";
|
||||
|
||||
$sql .= implode(', ', array_map(static fn ($columnName, $value) => $value . ' ' . $columnName, $keys, $values));
|
||||
|
||||
$sql .= ' FROM DUAL) "_replace" ON ( ';
|
||||
|
||||
$onList = [];
|
||||
$onList[] = '1 != 1';
|
||||
|
||||
foreach ($uniqueIndexes as $index) {
|
||||
$onList[] = '(' . implode(' AND ', array_map(static fn ($columnName) => $table . '."' . $columnName . '" = "_replace"."' . $columnName . '"', $index->fields)) . ')';
|
||||
}
|
||||
|
||||
$sql .= implode(' OR ', $onList) . ') WHEN MATCHED THEN UPDATE SET ';
|
||||
|
||||
$sql .= implode(', ', array_map(static fn ($columnName) => $columnName . ' = "_replace".' . $columnName, $replaceableFields));
|
||||
|
||||
$sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceableFields) . ') VALUES ';
|
||||
|
||||
return $sql . (' (' . implode(', ', array_map(static fn ($columnName) => '"_replace".' . $columnName, $replaceableFields)) . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the truncate() command,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*/
|
||||
protected function _truncate(string $table): string
|
||||
{
|
||||
return 'TRUNCATE TABLE ' . $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a delete string and runs the query
|
||||
*
|
||||
* @param mixed $where
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function delete($where = '', ?int $limit = null, bool $resetData = true)
|
||||
{
|
||||
if (! empty($limit)) {
|
||||
$this->QBLimit = $limit;
|
||||
}
|
||||
|
||||
return parent::delete($where, null, $resetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific delete string from the supplied data
|
||||
*/
|
||||
protected function _delete(string $table): string
|
||||
{
|
||||
if ($this->QBLimit) {
|
||||
$this->where('rownum <= ', $this->QBLimit, false);
|
||||
$this->QBLimit = false;
|
||||
}
|
||||
|
||||
return parent::_delete($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific update string from the supplied data
|
||||
*/
|
||||
protected function _update(string $table, array $values): string
|
||||
{
|
||||
$valStr = [];
|
||||
|
||||
foreach ($values as $key => $val) {
|
||||
$valStr[] = $key . ' = ' . $val;
|
||||
}
|
||||
|
||||
if ($this->QBLimit) {
|
||||
$this->where('rownum <= ', $this->QBLimit, false);
|
||||
}
|
||||
|
||||
return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr)
|
||||
. $this->compileWhereHaving('QBWhere')
|
||||
. $this->compileOrderBy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific LIMIT clause.
|
||||
*/
|
||||
protected function _limit(string $sql, bool $offsetIgnore = false): string
|
||||
{
|
||||
$offset = (int) ($offsetIgnore === false ? $this->QBOffset : 0);
|
||||
if (version_compare($this->db->getVersion(), '12.1', '>=')) {
|
||||
// OFFSET-FETCH can be used only with the ORDER BY clause
|
||||
if (empty($this->QBOrderBy)) {
|
||||
$sql .= ' ORDER BY 1';
|
||||
}
|
||||
|
||||
return $sql . ' OFFSET ' . $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY';
|
||||
}
|
||||
|
||||
$this->limitUsed = true;
|
||||
$limitTemplateQuery = 'SELECT * FROM (SELECT INNER_QUERY.*, ROWNUM RNUM FROM (%s) INNER_QUERY WHERE ROWNUM < %d)' . ($offset ? ' WHERE RNUM >= %d' : '');
|
||||
|
||||
return sprintf($limitTemplateQuery, $sql, $offset + $this->QBLimit + 1, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the query builder values. Called by the get() function
|
||||
*/
|
||||
protected function resetSelect()
|
||||
{
|
||||
$this->limitUsed = false;
|
||||
parent::resetSelect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\OCI8;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Database\Query;
|
||||
use ErrorException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Connection for OCI8
|
||||
*/
|
||||
class Connection extends BaseConnection
|
||||
{
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $DBDriver = 'OCI8';
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $escapeChar = '"';
|
||||
|
||||
/**
|
||||
* List of reserved identifiers
|
||||
*
|
||||
* Identifiers that must NOT be escaped.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $reservedIdentifiers = [
|
||||
'*',
|
||||
'rownum',
|
||||
];
|
||||
|
||||
protected $validDSNs = [
|
||||
'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS
|
||||
// Easy Connect string (Oracle 10g+)
|
||||
'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i',
|
||||
'in' => '/^[a-z0-9$_]+$/i', // Instance name (defined in tnsnames.ora)
|
||||
];
|
||||
|
||||
/**
|
||||
* Reset $stmtId flag
|
||||
*
|
||||
* Used by storedProcedure() to prevent execute() from
|
||||
* re-setting the statement ID.
|
||||
*/
|
||||
protected $resetStmtId = true;
|
||||
|
||||
/**
|
||||
* Statement ID
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $stmtId;
|
||||
|
||||
/**
|
||||
* Commit mode flag
|
||||
*
|
||||
* @used-by PreparedQuery::_execute()
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $commitMode = OCI_COMMIT_ON_SUCCESS;
|
||||
|
||||
/**
|
||||
* Cursor ID
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $cursorId;
|
||||
|
||||
/**
|
||||
* Latest inserted table name.
|
||||
*
|
||||
* @used-by PreparedQuery::_execute()
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $lastInsertedTableName;
|
||||
|
||||
/**
|
||||
* confirm DNS format.
|
||||
*/
|
||||
private function isValidDSN(): bool
|
||||
{
|
||||
foreach ($this->validDSNs as $regexp) {
|
||||
if (preg_match($regexp, $this->DSN)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the database.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function connect(bool $persistent = false)
|
||||
{
|
||||
if (empty($this->DSN) && ! $this->isValidDSN()) {
|
||||
$this->buildDSN();
|
||||
}
|
||||
|
||||
$func = $persistent ? 'oci_pconnect' : 'oci_connect';
|
||||
|
||||
return empty($this->charset)
|
||||
? $func($this->username, $this->password, $this->DSN)
|
||||
: $func($this->username, $this->password, $this->DSN, $this->charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep or establish the connection if no queries have been sent for
|
||||
* a length of time exceeding the server's idle timeout.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
if (is_resource($this->cursorId)) {
|
||||
oci_free_statement($this->cursorId);
|
||||
}
|
||||
if (is_resource($this->stmtId)) {
|
||||
oci_free_statement($this->stmtId);
|
||||
}
|
||||
oci_close($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific database table to use.
|
||||
*/
|
||||
public function setDatabase(string $databaseName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the version of the database being used.
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
if (isset($this->dataCache['version'])) {
|
||||
return $this->dataCache['version'];
|
||||
}
|
||||
|
||||
if (! $this->connID || ($versionString = oci_server_version($this->connID)) === false) {
|
||||
return '';
|
||||
}
|
||||
if (preg_match('#Release\s(\d+(?:\.\d+)+)#', $versionString, $match)) {
|
||||
return $this->dataCache['version'] = $match[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query against the database.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function execute(string $sql)
|
||||
{
|
||||
try {
|
||||
if ($this->resetStmtId === true) {
|
||||
$this->stmtId = oci_parse($this->connID, $sql);
|
||||
}
|
||||
|
||||
oci_set_prefetch($this->stmtId, 1000);
|
||||
|
||||
$result = oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false;
|
||||
$insertTableName = $this->parseInsertTableName($sql);
|
||||
|
||||
if ($result && $insertTableName !== '') {
|
||||
$this->lastInsertedTableName = $insertTableName;
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (ErrorException $e) {
|
||||
log_message('error', (string) $e);
|
||||
|
||||
if ($this->DBDebug) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table name for the insert statement from sql.
|
||||
*/
|
||||
public function parseInsertTableName(string $sql): string
|
||||
{
|
||||
$commentStrippedSql = preg_replace(['/\/\*(.|\n)*?\*\//m', '/--.+/'], '', $sql);
|
||||
$isInsertQuery = strpos(strtoupper(ltrim($commentStrippedSql)), 'INSERT') === 0;
|
||||
|
||||
if (! $isInsertQuery) {
|
||||
return '';
|
||||
}
|
||||
|
||||
preg_match('/(?is)\b(?:into)\s+("?\w+"?)/', $commentStrippedSql, $match);
|
||||
$tableName = $match[1] ?? '';
|
||||
|
||||
return strpos($tableName, '"') === 0 ? trim($tableName, '"') : strtoupper($tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows affected by this query.
|
||||
*/
|
||||
public function affectedRows(): int
|
||||
{
|
||||
return oci_num_rows($this->stmtId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SQL for listing tables in a platform-dependent manner.
|
||||
*
|
||||
* @param string|null $tableName If $tableName is provided will return only this table if exists.
|
||||
*/
|
||||
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
|
||||
{
|
||||
$sql = 'SELECT "TABLE_NAME" FROM "USER_TABLES"';
|
||||
|
||||
if ($tableName !== null) {
|
||||
return $sql . ' WHERE "TABLE_NAME" LIKE ' . $this->escape($tableName);
|
||||
}
|
||||
|
||||
if ($prefixLimit !== false && $this->DBPrefix !== '') {
|
||||
return $sql . ' WHERE "TABLE_NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . "%' "
|
||||
. sprintf($this->likeEscapeStr, $this->likeEscapeChar);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific query string so that the column names can be fetched.
|
||||
*/
|
||||
protected function _listColumns(string $table = ''): string
|
||||
{
|
||||
if (strpos($table, '.') !== false) {
|
||||
sscanf($table, '%[^.].%s', $owner, $table);
|
||||
} else {
|
||||
$owner = $this->username;
|
||||
}
|
||||
|
||||
return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS
|
||||
WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . '
|
||||
AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($this->DBPrefix . $table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with field data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _fieldData(string $table): array
|
||||
{
|
||||
if (strpos($table, '.') !== false) {
|
||||
sscanf($table, '%[^.].%s', $owner, $table);
|
||||
} else {
|
||||
$owner = $this->username;
|
||||
}
|
||||
|
||||
$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE
|
||||
FROM ALL_TAB_COLUMNS
|
||||
WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . '
|
||||
AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($table));
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetFieldData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retval = [];
|
||||
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++) {
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $query[$i]->COLUMN_NAME;
|
||||
$retval[$i]->type = $query[$i]->DATA_TYPE;
|
||||
|
||||
$length = $query[$i]->CHAR_LENGTH > 0 ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION;
|
||||
$length ??= $query[$i]->DATA_LENGTH;
|
||||
|
||||
$retval[$i]->max_length = $length;
|
||||
|
||||
$default = $query[$i]->DATA_DEFAULT;
|
||||
if ($default === null && $query[$i]->NULLABLE === 'N') {
|
||||
$default = '';
|
||||
}
|
||||
$retval[$i]->default = $default;
|
||||
$retval[$i]->nullable = $query[$i]->NULLABLE === 'Y';
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with index data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _indexData(string $table): array
|
||||
{
|
||||
if (strpos($table, '.') !== false) {
|
||||
sscanf($table, '%[^.].%s', $owner, $table);
|
||||
} else {
|
||||
$owner = $this->username;
|
||||
}
|
||||
|
||||
$sql = 'SELECT AIC.INDEX_NAME, UC.CONSTRAINT_TYPE, AIC.COLUMN_NAME '
|
||||
. ' FROM ALL_IND_COLUMNS AIC '
|
||||
. ' LEFT JOIN USER_CONSTRAINTS UC ON AIC.INDEX_NAME = UC.CONSTRAINT_NAME AND AIC.TABLE_NAME = UC.TABLE_NAME '
|
||||
. 'WHERE AIC.TABLE_NAME = ' . $this->escape(strtolower($table)) . ' '
|
||||
. 'AND AIC.TABLE_OWNER = ' . $this->escape(strtoupper($owner)) . ' '
|
||||
. ' ORDER BY UC.CONSTRAINT_TYPE, AIC.COLUMN_POSITION';
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetIndexData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
$constraintTypes = [
|
||||
'P' => 'PRIMARY',
|
||||
'U' => 'UNIQUE',
|
||||
];
|
||||
|
||||
foreach ($query as $row) {
|
||||
if (isset($retVal[$row->INDEX_NAME])) {
|
||||
$retVal[$row->INDEX_NAME]->fields[] = $row->COLUMN_NAME;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$retVal[$row->INDEX_NAME] = new stdClass();
|
||||
$retVal[$row->INDEX_NAME]->name = $row->INDEX_NAME;
|
||||
$retVal[$row->INDEX_NAME]->fields = [$row->COLUMN_NAME];
|
||||
$retVal[$row->INDEX_NAME]->type = $constraintTypes[$row->CONSTRAINT_TYPE] ?? 'INDEX';
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with Foreign key data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _foreignKeyData(string $table): array
|
||||
{
|
||||
$sql = 'SELECT
|
||||
acc.constraint_name,
|
||||
acc.table_name,
|
||||
acc.column_name,
|
||||
ccu.table_name foreign_table_name,
|
||||
accu.column_name foreign_column_name
|
||||
FROM all_cons_columns acc
|
||||
JOIN all_constraints ac
|
||||
ON acc.owner = ac.owner
|
||||
AND acc.constraint_name = ac.constraint_name
|
||||
JOIN all_constraints ccu
|
||||
ON ac.r_owner = ccu.owner
|
||||
AND ac.r_constraint_name = ccu.constraint_name
|
||||
JOIN all_cons_columns accu
|
||||
ON accu.constraint_name = ccu.constraint_name
|
||||
AND accu.table_name = ccu.table_name
|
||||
WHERE ac.constraint_type = ' . $this->escape('R') . '
|
||||
AND acc.table_name = ' . $this->escape($table);
|
||||
$query = $this->query($sql);
|
||||
|
||||
if ($query === false) {
|
||||
throw new DatabaseException(lang('Database.failGetForeignKeyData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
|
||||
foreach ($query as $row) {
|
||||
$obj = new stdClass();
|
||||
$obj->constraint_name = $row->CONSTRAINT_NAME;
|
||||
$obj->table_name = $row->TABLE_NAME;
|
||||
$obj->column_name = $row->COLUMN_NAME;
|
||||
$obj->foreign_table_name = $row->FOREIGN_TABLE_NAME;
|
||||
$obj->foreign_column_name = $row->FOREIGN_COLUMN_NAME;
|
||||
$retVal[] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform-specific SQL to disable foreign key checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _disableForeignKeyChecks()
|
||||
{
|
||||
return <<<'SQL'
|
||||
BEGIN
|
||||
FOR c IN
|
||||
(SELECT c.owner, c.table_name, c.constraint_name
|
||||
FROM user_constraints c, user_tables t
|
||||
WHERE c.table_name = t.table_name
|
||||
AND c.status = 'ENABLED'
|
||||
AND c.constraint_type = 'R'
|
||||
AND t.iot_type IS NULL
|
||||
ORDER BY c.constraint_type DESC)
|
||||
LOOP
|
||||
dbms_utility.exec_ddl_statement('alter table "' || c.owner || '"."' || c.table_name || '" disable constraint "' || c.constraint_name || '"');
|
||||
END LOOP;
|
||||
END;
|
||||
SQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform-specific SQL to enable foreign key checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _enableForeignKeyChecks()
|
||||
{
|
||||
return <<<'SQL'
|
||||
BEGIN
|
||||
FOR c IN
|
||||
(SELECT c.owner, c.table_name, c.constraint_name
|
||||
FROM user_constraints c, user_tables t
|
||||
WHERE c.table_name = t.table_name
|
||||
AND c.status = 'DISABLED'
|
||||
AND c.constraint_type = 'R'
|
||||
AND t.iot_type IS NULL
|
||||
ORDER BY c.constraint_type DESC)
|
||||
LOOP
|
||||
dbms_utility.exec_ddl_statement('alter table "' || c.owner || '"."' || c.table_name || '" enable constraint "' || c.constraint_name || '"');
|
||||
END LOOP;
|
||||
END;
|
||||
SQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor. Returns a cursor from the database
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getCursor()
|
||||
{
|
||||
return $this->cursorId = oci_new_cursor($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a stored procedure
|
||||
*
|
||||
* @param string $procedureName procedure name to execute
|
||||
* @param array $params params array keys
|
||||
* KEY OPTIONAL NOTES
|
||||
* name no the name of the parameter should be in :<param_name> format
|
||||
* value no the value of the parameter. If this is an OUT or IN OUT parameter,
|
||||
* this should be a reference to a variable
|
||||
* type yes the type of the parameter
|
||||
* length yes the max size of the parameter
|
||||
*
|
||||
* @return bool|Query|Result
|
||||
*/
|
||||
public function storedProcedure(string $procedureName, array $params)
|
||||
{
|
||||
if ($procedureName === '') {
|
||||
throw new DatabaseException(lang('Database.invalidArgument', [$procedureName]));
|
||||
}
|
||||
|
||||
// Build the query string
|
||||
$sql = sprintf(
|
||||
'BEGIN %s (' . substr(str_repeat(',%s', count($params)), 1) . '); END;',
|
||||
$procedureName,
|
||||
...array_map(static fn ($row) => $row['name'], $params)
|
||||
);
|
||||
|
||||
$this->resetStmtId = false;
|
||||
$this->stmtId = oci_parse($this->connID, $sql);
|
||||
$this->bindParams($params);
|
||||
$result = $this->query($sql);
|
||||
$this->resetStmtId = true;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind parameters
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function bindParams($params)
|
||||
{
|
||||
if (! is_array($params) || ! is_resource($this->stmtId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($params as $param) {
|
||||
oci_bind_by_name(
|
||||
$this->stmtId,
|
||||
$param['name'],
|
||||
$param['value'],
|
||||
$param['length'] ?? -1,
|
||||
$param['type'] ?? SQLT_CHR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code and message.
|
||||
*
|
||||
* Must return an array with keys 'code' and 'message':
|
||||
*
|
||||
* return ['code' => null, 'message' => null);
|
||||
*/
|
||||
public function error(): array
|
||||
{
|
||||
// oci_error() returns an array that already contains
|
||||
// 'code' and 'message' keys, but it can return false
|
||||
// if there was no error ....
|
||||
$error = oci_error();
|
||||
$resources = [$this->cursorId, $this->stmtId, $this->connID];
|
||||
|
||||
foreach ($resources as $resource) {
|
||||
if (is_resource($resource)) {
|
||||
$error = oci_error($resource);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return is_array($error)
|
||||
? $error
|
||||
: [
|
||||
'code' => '',
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
|
||||
public function insertID(): int
|
||||
{
|
||||
if (empty($this->lastInsertedTableName)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$indexs = $this->getIndexData($this->lastInsertedTableName);
|
||||
$fieldDatas = $this->getFieldData($this->lastInsertedTableName);
|
||||
|
||||
if (! $indexs || ! $fieldDatas) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$columnTypeList = array_column($fieldDatas, 'type', 'name');
|
||||
$primaryColumnName = '';
|
||||
|
||||
foreach ($indexs as $index) {
|
||||
if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$primaryColumnName = $this->protectIdentifiers($index->fields[0], false, false);
|
||||
$primaryColumnType = $columnTypeList[$primaryColumnName];
|
||||
|
||||
if ($primaryColumnType !== 'NUMBER') {
|
||||
$primaryColumnName = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (! $primaryColumnName) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = $this->query('SELECT DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?', [$this->lastInsertedTableName, $primaryColumnName])->getRow();
|
||||
$lastInsertValue = str_replace('nextval', 'currval', $query->DATA_DEFAULT ?? '0');
|
||||
$query = $this->query(sprintf('SELECT %s SEQ FROM DUAL', $lastInsertValue))->getRow();
|
||||
|
||||
return (int) ($query->SEQ ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a DSN from the provided parameters
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function buildDSN()
|
||||
{
|
||||
if ($this->DSN !== '') {
|
||||
$this->DSN = '';
|
||||
}
|
||||
|
||||
// Legacy support for TNS in the hostname configuration field
|
||||
$this->hostname = str_replace(["\n", "\r", "\t", ' '], '', $this->hostname);
|
||||
|
||||
if (preg_match($this->validDSNs['tns'], $this->hostname)) {
|
||||
$this->DSN = $this->hostname;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$isEasyConnectableHostName = $this->hostname !== '' && strpos($this->hostname, '/') === false && strpos($this->hostname, ':') === false;
|
||||
$easyConnectablePort = ! empty($this->port) && ctype_digit($this->port) ? ':' . $this->port : '';
|
||||
$easyConnectableDatabase = $this->database !== '' ? '/' . ltrim($this->database, '/') : '';
|
||||
|
||||
if ($isEasyConnectableHostName && ($easyConnectablePort !== '' || $easyConnectableDatabase !== '')) {
|
||||
/* If the hostname field isn't empty, doesn't contain
|
||||
* ':' and/or '/' and if port and/or database aren't
|
||||
* empty, then the hostname field is most likely indeed
|
||||
* just a hostname. Therefore we'll try and build an
|
||||
* Easy Connect string from these 3 settings, assuming
|
||||
* that the database field is a service name.
|
||||
*/
|
||||
$this->DSN = $this->hostname . $easyConnectablePort . $easyConnectableDatabase;
|
||||
|
||||
if (preg_match($this->validDSNs['ec'], $this->DSN)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* At this point, we can only try and validate the hostname and
|
||||
* database fields separately as DSNs.
|
||||
*/
|
||||
if (preg_match($this->validDSNs['ec'], $this->hostname) || preg_match($this->validDSNs['in'], $this->hostname)) {
|
||||
$this->DSN = $this->hostname;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database = str_replace(["\n", "\r", "\t", ' '], '', $this->database);
|
||||
|
||||
foreach ($this->validDSNs as $regexp) {
|
||||
if (preg_match($regexp, $this->database)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Well - OK, an empty string should work as well.
|
||||
* PHP will try to use environment variables to
|
||||
* determine which Oracle instance to connect to.
|
||||
*/
|
||||
$this->DSN = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*/
|
||||
protected function _transBegin(): bool
|
||||
{
|
||||
$this->commitMode = OCI_NO_AUTO_COMMIT;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*/
|
||||
protected function _transCommit(): bool
|
||||
{
|
||||
$this->commitMode = OCI_COMMIT_ON_SUCCESS;
|
||||
|
||||
return oci_commit($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*/
|
||||
protected function _transRollback(): bool
|
||||
{
|
||||
$this->commitMode = OCI_COMMIT_ON_SUCCESS;
|
||||
|
||||
return oci_rollback($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current database being used.
|
||||
*/
|
||||
public function getDatabase(): string
|
||||
{
|
||||
if (! empty($this->database)) {
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
return $this->query('SELECT DEFAULT_TABLESPACE FROM USER_USERS')->getRow()->DEFAULT_TABLESPACE ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prefix of the function to access the DB.
|
||||
*/
|
||||
protected function getDriverFunctionPrefix(): string
|
||||
{
|
||||
return 'oci_';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\OCI8;
|
||||
|
||||
use CodeIgniter\Database\Forge as BaseForge;
|
||||
|
||||
/**
|
||||
* Forge for OCI8
|
||||
*/
|
||||
class Forge extends BaseForge
|
||||
{
|
||||
/**
|
||||
* DROP INDEX statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropIndexStr = 'DROP INDEX %s';
|
||||
|
||||
/**
|
||||
* CREATE DATABASE statement
|
||||
*
|
||||
* @var false
|
||||
*/
|
||||
protected $createDatabaseStr = false;
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var false
|
||||
*
|
||||
* @deprecated This is no longer used.
|
||||
*/
|
||||
protected $createTableIfStr = false;
|
||||
|
||||
/**
|
||||
* DROP TABLE IF EXISTS statement
|
||||
*
|
||||
* @var false
|
||||
*/
|
||||
protected $dropTableIfStr = false;
|
||||
|
||||
/**
|
||||
* DROP DATABASE statement
|
||||
*
|
||||
* @var false
|
||||
*/
|
||||
protected $dropDatabaseStr = false;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array|bool
|
||||
*/
|
||||
protected $unsigned = false;
|
||||
|
||||
/**
|
||||
* NULL value representation in CREATE/ALTER TABLE statements
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $null = 'NULL';
|
||||
|
||||
/**
|
||||
* RENAME TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $renameTableStr = 'ALTER TABLE %s RENAME TO %s';
|
||||
|
||||
/**
|
||||
* DROP CONSTRAINT statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropConstraintStr = 'ALTER TABLE %s DROP CONSTRAINT %s';
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alterType ALTER type
|
||||
* @param string $table Table name
|
||||
* @param array|string $field Column definition
|
||||
*
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function _alterTable(string $alterType, string $table, $field)
|
||||
{
|
||||
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
|
||||
|
||||
if ($alterType === 'DROP') {
|
||||
$fields = array_map(fn ($field) => $this->db->escapeIdentifiers(trim($field)), is_string($field) ? explode(',', $field) : $field);
|
||||
|
||||
return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE';
|
||||
}
|
||||
if ($alterType === 'CHANGE') {
|
||||
$alterType = 'MODIFY';
|
||||
}
|
||||
|
||||
$nullableMap = array_column($this->db->getFieldData($table), 'nullable', 'name');
|
||||
$sqls = [];
|
||||
|
||||
for ($i = 0, $c = count($field); $i < $c; $i++) {
|
||||
if ($alterType === 'MODIFY') {
|
||||
// If a null constraint is added to a column with a null constraint,
|
||||
// ORA-01451 will occur,
|
||||
// so add null constraint is used only when it is different from the current null constraint.
|
||||
$isWantToAddNull = strpos($field[$i]['null'], ' NOT') === false;
|
||||
$currentNullAddable = $nullableMap[$field[$i]['name']];
|
||||
|
||||
if ($isWantToAddNull === $currentNullAddable) {
|
||||
$field[$i]['null'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($field[$i]['_literal'] !== false) {
|
||||
$field[$i] = "\n\t" . $field[$i]['_literal'];
|
||||
} else {
|
||||
$field[$i]['_literal'] = "\n\t" . $this->_processColumn($field[$i]);
|
||||
|
||||
if (! empty($field[$i]['comment'])) {
|
||||
$sqls[] = 'COMMENT ON COLUMN '
|
||||
. $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($field[$i]['name'])
|
||||
. ' IS ' . $field[$i]['comment'];
|
||||
}
|
||||
|
||||
if ($alterType === 'MODIFY' && ! empty($field[$i]['new_name'])) {
|
||||
$sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name'])
|
||||
. ' TO ' . $this->db->escapeIdentifiers($field[$i]['new_name']);
|
||||
}
|
||||
|
||||
$field[$i] = "\n\t" . $field[$i]['_literal'];
|
||||
}
|
||||
}
|
||||
|
||||
$sql .= ' ' . $alterType . ' ';
|
||||
$sql .= count($field) === 1
|
||||
? $field[0]
|
||||
: '(' . implode(',', $field) . ')';
|
||||
|
||||
// RENAME COLUMN must be executed after MODIFY
|
||||
array_unshift($sqls, $sql);
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _attributeAutoIncrement(array &$attributes, array &$field)
|
||||
{
|
||||
if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true
|
||||
&& stripos($field['type'], 'NUMBER') !== false
|
||||
&& version_compare($this->db->getVersion(), '12.1', '>=')
|
||||
) {
|
||||
$field['auto_increment'] = ' GENERATED BY DEFAULT AS IDENTITY';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*/
|
||||
protected function _processColumn(array $field): string
|
||||
{
|
||||
$constraint = '';
|
||||
// @todo: can’t cover multi pattern when set type.
|
||||
if ($field['type'] === 'VARCHAR2' && strpos($field['length'], "('") === 0) {
|
||||
$constraint = ' CHECK(' . $this->db->escapeIdentifiers($field['name'])
|
||||
. ' IN ' . $field['length'] . ')';
|
||||
|
||||
$field['length'] = '(' . max(array_map('mb_strlen', explode("','", mb_substr($field['length'], 2, -2)))) . ')' . $constraint;
|
||||
} elseif (count($this->primaryKeys) === 1 && $field['name'] === $this->primaryKeys[0]) {
|
||||
$field['unique'] = '';
|
||||
}
|
||||
|
||||
return $this->db->escapeIdentifiers($field['name'])
|
||||
. ' ' . $field['type'] . $field['length']
|
||||
. $field['unsigned']
|
||||
. $field['default']
|
||||
. $field['auto_increment']
|
||||
. $field['null']
|
||||
. $field['unique'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a data type mapping between different databases.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _attributeType(array &$attributes)
|
||||
{
|
||||
// Reset field lengths for data types that don't support it
|
||||
// Usually overridden by drivers
|
||||
switch (strtoupper($attributes['TYPE'])) {
|
||||
case 'TINYINT':
|
||||
$attributes['CONSTRAINT'] ??= 3;
|
||||
// no break
|
||||
case 'SMALLINT':
|
||||
$attributes['CONSTRAINT'] ??= 5;
|
||||
// no break
|
||||
case 'MEDIUMINT':
|
||||
$attributes['CONSTRAINT'] ??= 7;
|
||||
// no break
|
||||
case 'INT':
|
||||
case 'INTEGER':
|
||||
$attributes['CONSTRAINT'] ??= 11;
|
||||
// no break
|
||||
case 'BIGINT':
|
||||
$attributes['CONSTRAINT'] ??= 19;
|
||||
// no break
|
||||
case 'NUMERIC':
|
||||
$attributes['TYPE'] = 'NUMBER';
|
||||
|
||||
return;
|
||||
|
||||
case 'BOOLEAN':
|
||||
$attributes['TYPE'] = 'NUMBER';
|
||||
$attributes['CONSTRAINT'] = 1;
|
||||
$attributes['UNSIGNED'] = true;
|
||||
$attributes['NULL'] = false;
|
||||
|
||||
return;
|
||||
|
||||
case 'DOUBLE':
|
||||
$attributes['TYPE'] = 'FLOAT';
|
||||
$attributes['CONSTRAINT'] ??= 126;
|
||||
|
||||
return;
|
||||
|
||||
case 'DATETIME':
|
||||
case 'TIME':
|
||||
$attributes['TYPE'] = 'DATE';
|
||||
|
||||
return;
|
||||
|
||||
case 'SET':
|
||||
case 'ENUM':
|
||||
case 'VARCHAR':
|
||||
$attributes['CONSTRAINT'] ??= 255;
|
||||
// no break
|
||||
case 'TEXT':
|
||||
case 'MEDIUMTEXT':
|
||||
$attributes['CONSTRAINT'] ??= 4000;
|
||||
$attributes['TYPE'] = 'VARCHAR2';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific DROP TABLE string
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function _dropTable(string $table, bool $ifExists, bool $cascade)
|
||||
{
|
||||
$sql = parent::_dropTable($table, $ifExists, $cascade);
|
||||
|
||||
if ($sql !== true && $cascade === true) {
|
||||
$sql .= ' CASCADE CONSTRAINTS PURGE';
|
||||
} elseif ($sql !== true) {
|
||||
$sql .= ' PURGE';
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
protected function _processForeignKeys(string $table): string
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
$allowActions = [
|
||||
'CASCADE',
|
||||
'SET NULL',
|
||||
'NO ACTION',
|
||||
];
|
||||
|
||||
foreach ($this->foreignKeys as $fkey) {
|
||||
$nameIndex = $table . '_' . implode('_', $fkey['field']) . '_fk';
|
||||
$nameIndexFilled = $this->db->escapeIdentifiers($nameIndex);
|
||||
$foreignKeyFilled = implode(', ', $this->db->escapeIdentifiers($fkey['field']));
|
||||
$referenceTableFilled = $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['referenceTable']);
|
||||
$referenceFieldFilled = implode(', ', $this->db->escapeIdentifiers($fkey['referenceField']));
|
||||
|
||||
$formatSql = ",\n\tCONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s)";
|
||||
$sql .= sprintf($formatSql, $nameIndexFilled, $foreignKeyFilled, $referenceTableFilled, $referenceFieldFilled);
|
||||
|
||||
if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true)) {
|
||||
$sql .= ' ON DELETE ' . $fkey['onDelete'];
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\OCI8;
|
||||
|
||||
use BadMethodCallException;
|
||||
use CodeIgniter\Database\BasePreparedQuery;
|
||||
|
||||
/**
|
||||
* Prepared query for OCI8
|
||||
*/
|
||||
class PreparedQuery extends BasePreparedQuery
|
||||
{
|
||||
/**
|
||||
* A reference to the db connection to use.
|
||||
*
|
||||
* @var Connection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Latest inserted table name.
|
||||
*/
|
||||
private ?string $lastInsertTableName = null;
|
||||
|
||||
/**
|
||||
* Prepares the query against the database, and saves the connection
|
||||
* info necessary to execute the query later.
|
||||
*
|
||||
* NOTE: This version is based on SQL code. Child classes should
|
||||
* override this method.
|
||||
*
|
||||
* @param array $options Passed to the connection's prepare statement.
|
||||
* Unused in the OCI8 driver.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _prepare(string $sql, array $options = [])
|
||||
{
|
||||
if (! $this->statement = oci_parse($this->db->connID, $this->parameterize($sql))) {
|
||||
$error = oci_error($this->db->connID);
|
||||
$this->errorCode = $error['code'] ?? 0;
|
||||
$this->errorString = $error['message'] ?? '';
|
||||
}
|
||||
|
||||
$this->lastInsertTableName = $this->db->parseInsertTableName($sql);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a new set of data and runs it against the currently
|
||||
* prepared query. Upon success, will return a Results object.
|
||||
*/
|
||||
public function _execute(array $data): bool
|
||||
{
|
||||
if (null === $this->statement) {
|
||||
throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
|
||||
}
|
||||
|
||||
$lastKey = 0;
|
||||
|
||||
foreach (array_keys($data) as $key) {
|
||||
oci_bind_by_name($this->statement, ':' . $key, $data[$key]);
|
||||
$lastKey = $key;
|
||||
}
|
||||
|
||||
$result = oci_execute($this->statement, $this->db->commitMode);
|
||||
|
||||
if ($result && $this->lastInsertTableName !== '') {
|
||||
$this->db->lastInsertedTableName = $this->lastInsertTableName;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result object for the prepared query.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _getResult()
|
||||
{
|
||||
return $this->statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the ? placeholders with :0, :1, etc parameters for use
|
||||
* within the prepared query.
|
||||
*/
|
||||
public function parameterize(string $sql): string
|
||||
{
|
||||
// Track our current value
|
||||
$count = 0;
|
||||
|
||||
return preg_replace_callback('/\?/', static function ($matches) use (&$count) {
|
||||
return ':' . ($count++);
|
||||
}, $sql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\OCI8;
|
||||
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
|
||||
/**
|
||||
* Result for OCI8
|
||||
*/
|
||||
class Result extends BaseResult
|
||||
{
|
||||
/**
|
||||
* Gets the number of fields in the result set.
|
||||
*/
|
||||
public function getFieldCount(): int
|
||||
{
|
||||
return oci_num_fields($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of column names in the result set.
|
||||
*/
|
||||
public function getFieldNames(): array
|
||||
{
|
||||
return array_map(fn ($fieldIndex) => oci_field_name($this->resultID, $fieldIndex), range(1, $this->getFieldCount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of objects representing field meta-data.
|
||||
*/
|
||||
public function getFieldData(): array
|
||||
{
|
||||
return array_map(fn ($fieldIndex) => (object) [
|
||||
'name' => oci_field_name($this->resultID, $fieldIndex),
|
||||
'type' => oci_field_type($this->resultID, $fieldIndex),
|
||||
'max_length' => oci_field_size($this->resultID, $fieldIndex),
|
||||
], range(1, $this->getFieldCount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the current result.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function freeResult()
|
||||
{
|
||||
if (is_resource($this->resultID)) {
|
||||
oci_free_statement($this->resultID);
|
||||
$this->resultID = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal pointer to the desired offset. This is called
|
||||
* internally before fetching results to make sure the result set
|
||||
* starts at zero.
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
public function dataSeek(int $n = 0)
|
||||
{
|
||||
// We can't support data seek by oci
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an array.
|
||||
*
|
||||
* Overridden by driver classes.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetchAssoc()
|
||||
{
|
||||
return oci_fetch_assoc($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an object.
|
||||
*
|
||||
* Overridden by child classes.
|
||||
*
|
||||
* @return bool|Entity|object
|
||||
*/
|
||||
protected function fetchObject(string $className = 'stdClass')
|
||||
{
|
||||
$row = oci_fetch_object($this->resultID);
|
||||
|
||||
if ($className === 'stdClass' || ! $row) {
|
||||
return $row;
|
||||
}
|
||||
if (is_subclass_of($className, Entity::class)) {
|
||||
return (new $className())->setAttributes((array) $row);
|
||||
}
|
||||
|
||||
$instance = new $className();
|
||||
|
||||
foreach (get_object_vars($row) as $key => $value) {
|
||||
$instance->{$key} = $value;
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\OCI8;
|
||||
|
||||
use CodeIgniter\Database\BaseUtils;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Utils for OCI8
|
||||
*/
|
||||
class Utils extends BaseUtils
|
||||
{
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $listDatabases = 'SELECT TABLESPACE_NAME FROM USER_TABLESPACES';
|
||||
|
||||
/**
|
||||
* Platform dependent version of the backup function.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _backup(?array $prefs = null)
|
||||
{
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Postgre;
|
||||
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Database\RawSql;
|
||||
|
||||
/**
|
||||
* Builder for Postgre
|
||||
*/
|
||||
class Builder extends BaseBuilder
|
||||
{
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $randomKeyword = [
|
||||
'RANDOM()',
|
||||
];
|
||||
|
||||
/**
|
||||
* Specifies which sql statements
|
||||
* support the ignore option.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $supportedIgnoreStatements = [
|
||||
'insert' => 'ON CONFLICT DO NOTHING',
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks if the ignore option is supported by
|
||||
* the Database Driver for the specific statement.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function compileIgnore(string $statement)
|
||||
{
|
||||
$sql = parent::compileIgnore($statement);
|
||||
|
||||
if (! empty($sql)) {
|
||||
$sql = ' ' . trim($sql);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* ORDER BY
|
||||
*
|
||||
* @param string $direction ASC, DESC or RANDOM
|
||||
*
|
||||
* @return BaseBuilder
|
||||
*/
|
||||
public function orderBy(string $orderBy, string $direction = '', ?bool $escape = null)
|
||||
{
|
||||
$direction = strtoupper(trim($direction));
|
||||
if ($direction === 'RANDOM') {
|
||||
if (ctype_digit($orderBy)) {
|
||||
$orderBy = (float) ($orderBy > 1 ? "0.{$orderBy}" : $orderBy);
|
||||
}
|
||||
|
||||
if (is_float($orderBy)) {
|
||||
$this->db->simpleQuery("SET SEED {$orderBy}");
|
||||
}
|
||||
|
||||
$orderBy = $this->randomKeyword[0];
|
||||
$direction = '';
|
||||
$escape = false;
|
||||
}
|
||||
|
||||
return parent::orderBy($orderBy, $direction, $escape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments a numeric column by the specified value.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function increment(string $column, int $value = 1)
|
||||
{
|
||||
$column = $this->db->protectIdentifiers($column);
|
||||
|
||||
$sql = $this->_update($this->QBFrom[0], [$column => "to_number({$column}, '9999999') + {$value}"]);
|
||||
|
||||
if (! $this->testMode) {
|
||||
$this->resetWrite();
|
||||
|
||||
return $this->db->query($sql, $this->binds, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements a numeric column by the specified value.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function decrement(string $column, int $value = 1)
|
||||
{
|
||||
$column = $this->db->protectIdentifiers($column);
|
||||
|
||||
$sql = $this->_update($this->QBFrom[0], [$column => "to_number({$column}, '9999999') - {$value}"]);
|
||||
|
||||
if (! $this->testMode) {
|
||||
$this->resetWrite();
|
||||
|
||||
return $this->db->query($sql, $this->binds, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles an replace into string and runs the query.
|
||||
* Because PostgreSQL doesn't support the replace into command,
|
||||
* we simply do a DELETE and an INSERT on the first key/value
|
||||
* combo, assuming that it's either the primary key or a unique key.
|
||||
*
|
||||
* @param array|null $set An associative array of insert values
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function replace(?array $set = null)
|
||||
{
|
||||
if ($set !== null) {
|
||||
$this->set($set);
|
||||
}
|
||||
|
||||
if (! $this->QBSet) {
|
||||
if (CI_DEBUG) {
|
||||
throw new DatabaseException('You must use the "set" method to update an entry.');
|
||||
}
|
||||
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$table = $this->QBFrom[0];
|
||||
$set = $this->binds;
|
||||
|
||||
array_walk($set, static function (array &$item) {
|
||||
$item = $item[0];
|
||||
});
|
||||
|
||||
$key = array_key_first($set);
|
||||
$value = $set[$key];
|
||||
|
||||
$builder = $this->db->table($table);
|
||||
$exists = $builder->where($key, $value, true)->get()->getFirstRow();
|
||||
|
||||
if (empty($exists) && $this->testMode) {
|
||||
$result = $this->getCompiledInsert();
|
||||
} elseif (empty($exists)) {
|
||||
$result = $builder->insert($set);
|
||||
} elseif ($this->testMode) {
|
||||
$result = $this->where($key, $value, true)->getCompiledUpdate();
|
||||
} else {
|
||||
array_shift($set);
|
||||
$result = $builder->where($key, $value, true)->update($set);
|
||||
}
|
||||
|
||||
unset($builder);
|
||||
$this->resetWrite();
|
||||
$this->binds = [];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific insert string from the supplied data
|
||||
*/
|
||||
protected function _insert(string $table, array $keys, array $unescapedKeys): string
|
||||
{
|
||||
return trim(sprintf('INSERT INTO %s (%s) VALUES (%s) %s', $table, implode(', ', $keys), implode(', ', $unescapedKeys), $this->compileIgnore('insert')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific insert string from the supplied data.
|
||||
*/
|
||||
protected function _insertBatch(string $table, array $keys, array $values): string
|
||||
{
|
||||
return trim(sprintf('INSERT INTO %s (%s) VALUES %s %s', $table, implode(', ', $keys), implode(', ', $values), $this->compileIgnore('insert')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a delete string and runs the query
|
||||
*
|
||||
* @param mixed $where
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function delete($where = '', ?int $limit = null, bool $resetData = true)
|
||||
{
|
||||
if (! empty($limit) || ! empty($this->QBLimit)) {
|
||||
throw new DatabaseException('PostgreSQL does not allow LIMITs on DELETE queries.');
|
||||
}
|
||||
|
||||
return parent::delete($where, $limit, $resetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific LIMIT clause.
|
||||
*/
|
||||
protected function _limit(string $sql, bool $offsetIgnore = false): string
|
||||
{
|
||||
return $sql . ' LIMIT ' . $this->QBLimit . ($this->QBOffset ? " OFFSET {$this->QBOffset}" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific update string from the supplied data
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _update(string $table, array $values): string
|
||||
{
|
||||
if (! empty($this->QBLimit)) {
|
||||
throw new DatabaseException('Postgres does not support LIMITs with UPDATE queries.');
|
||||
}
|
||||
|
||||
$this->QBOrderBy = [];
|
||||
|
||||
return parent::_update($table, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific batch update string from the supplied data
|
||||
*/
|
||||
protected function _updateBatch(string $table, array $values, string $index): string
|
||||
{
|
||||
$ids = [];
|
||||
$final = [];
|
||||
|
||||
foreach ($values as $val) {
|
||||
$ids[] = $val[$index];
|
||||
|
||||
foreach (array_keys($val) as $field) {
|
||||
if ($field !== $index) {
|
||||
$final[$field] ??= [];
|
||||
|
||||
$final[$field][] = "WHEN {$val[$index]} THEN {$val[$field]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$cases = '';
|
||||
|
||||
foreach ($final as $k => $v) {
|
||||
$cases .= "{$k} = (CASE {$index}\n"
|
||||
. implode("\n", $v)
|
||||
. "\nELSE {$k} END), ";
|
||||
}
|
||||
|
||||
$this->where("{$index} IN(" . implode(',', $ids) . ')', null, false);
|
||||
|
||||
return "UPDATE {$table} SET " . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific delete string from the supplied data
|
||||
*/
|
||||
protected function _delete(string $table): string
|
||||
{
|
||||
$this->QBLimit = false;
|
||||
|
||||
return parent::_delete($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the truncate() command,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*/
|
||||
protected function _truncate(string $table): string
|
||||
{
|
||||
return 'TRUNCATE ' . $table . ' RESTART IDENTITY';
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform independent LIKE statement builder.
|
||||
*
|
||||
* In PostgreSQL, the ILIKE operator will perform case insensitive
|
||||
* searches according to the current locale.
|
||||
*
|
||||
* @see https://www.postgresql.org/docs/9.2/static/functions-matching.html
|
||||
*/
|
||||
protected function _like_statement(?string $prefix, string $column, ?string $not, string $bind, bool $insensitiveSearch = false): string
|
||||
{
|
||||
$op = $insensitiveSearch === true ? 'ILIKE' : 'LIKE';
|
||||
|
||||
return "{$prefix} {$column} {$not} {$op} :{$bind}:";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the JOIN portion of the query
|
||||
*
|
||||
* @param RawSql|string $cond
|
||||
*
|
||||
* @return BaseBuilder
|
||||
*/
|
||||
public function join(string $table, $cond, string $type = '', ?bool $escape = null)
|
||||
{
|
||||
if (! in_array('FULL OUTER', $this->joinTypes, true)) {
|
||||
$this->joinTypes = array_merge($this->joinTypes, ['FULL OUTER']);
|
||||
}
|
||||
|
||||
return parent::join($table, $cond, $type, $escape);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Postgre;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use ErrorException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Connection for Postgre
|
||||
*/
|
||||
class Connection extends BaseConnection
|
||||
{
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $DBDriver = 'Postgre';
|
||||
|
||||
/**
|
||||
* Database schema
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $schema = 'public';
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $escapeChar = '"';
|
||||
|
||||
protected $connect_timeout;
|
||||
protected $options;
|
||||
protected $sslmode;
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* Connect to the database.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function connect(bool $persistent = false)
|
||||
{
|
||||
if (empty($this->DSN)) {
|
||||
$this->buildDSN();
|
||||
}
|
||||
|
||||
// Strip pgsql if exists
|
||||
if (mb_strpos($this->DSN, 'pgsql:') === 0) {
|
||||
$this->DSN = mb_substr($this->DSN, 6);
|
||||
}
|
||||
|
||||
// Convert semicolons to spaces.
|
||||
$this->DSN = str_replace(';', ' ', $this->DSN);
|
||||
|
||||
$this->connID = $persistent === true ? pg_pconnect($this->DSN) : pg_connect($this->DSN);
|
||||
|
||||
if ($this->connID !== false) {
|
||||
if ($persistent === true && pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD && pg_ping($this->connID) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! empty($this->schema)) {
|
||||
$this->simpleQuery("SET search_path TO {$this->schema},public");
|
||||
}
|
||||
|
||||
if ($this->setClientEncoding($this->charset) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->connID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep or establish the connection if no queries have been sent for
|
||||
* a length of time exceeding the server's idle timeout.
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
if (pg_ping($this->connID) === false) {
|
||||
$this->connID = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
pg_close($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific database table to use.
|
||||
*/
|
||||
public function setDatabase(string $databaseName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the version of the database being used.
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
if (isset($this->dataCache['version'])) {
|
||||
return $this->dataCache['version'];
|
||||
}
|
||||
|
||||
if (! $this->connID || ($pgVersion = pg_version($this->connID)) === false) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return isset($pgVersion['server']) ? $this->dataCache['version'] = $pgVersion['server'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query against the database.
|
||||
*
|
||||
* @return false|resource
|
||||
*/
|
||||
protected function execute(string $sql)
|
||||
{
|
||||
try {
|
||||
return pg_query($this->connID, $sql);
|
||||
} catch (ErrorException $e) {
|
||||
log_message('error', (string) $e);
|
||||
if ($this->DBDebug) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prefix of the function to access the DB.
|
||||
*/
|
||||
protected function getDriverFunctionPrefix(): string
|
||||
{
|
||||
return 'pg_';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows affected by this query.
|
||||
*/
|
||||
public function affectedRows(): int
|
||||
{
|
||||
return pg_affected_rows($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Smart" Escape String
|
||||
*
|
||||
* Escapes data based on type
|
||||
*
|
||||
* @param array|bool|float|int|object|string|null $str
|
||||
*
|
||||
* @return array|float|int|string
|
||||
* @phpstan-return ($str is array ? array : float|int|string)
|
||||
*/
|
||||
public function escape($str)
|
||||
{
|
||||
if (! $this->connID) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) {
|
||||
return pg_escape_literal($this->connID, $str);
|
||||
}
|
||||
|
||||
if (is_bool($str)) {
|
||||
return $str ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
|
||||
return parent::escape($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-dependant string escape
|
||||
*/
|
||||
protected function _escapeString(string $str): string
|
||||
{
|
||||
if (! $this->connID) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return pg_escape_string($this->connID, $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SQL for listing tables in a platform-dependent manner.
|
||||
*
|
||||
* @param string|null $tableName If $tableName is provided will return only this table if exists.
|
||||
*/
|
||||
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
|
||||
{
|
||||
$sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \'' . $this->schema . "'";
|
||||
|
||||
if ($tableName !== null) {
|
||||
return $sql . ' AND "table_name" LIKE ' . $this->escape($tableName);
|
||||
}
|
||||
|
||||
if ($prefixLimit !== false && $this->DBPrefix !== '') {
|
||||
return $sql . ' AND "table_name" LIKE \''
|
||||
. $this->escapeLikeString($this->DBPrefix) . "%' "
|
||||
. sprintf($this->likeEscapeStr, $this->likeEscapeChar);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific query string so that the column names can be fetched.
|
||||
*/
|
||||
protected function _listColumns(string $table = ''): string
|
||||
{
|
||||
return 'SELECT "column_name"
|
||||
FROM "information_schema"."columns"
|
||||
WHERE LOWER("table_name") = '
|
||||
. $this->escape($this->DBPrefix . strtolower($table))
|
||||
. ' ORDER BY "ordinal_position"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with field data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _fieldData(string $table): array
|
||||
{
|
||||
$sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default", "is_nullable"
|
||||
FROM "information_schema"."columns"
|
||||
WHERE LOWER("table_name") = '
|
||||
. $this->escape(strtolower($table))
|
||||
. ' ORDER BY "ordinal_position"';
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetFieldData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++) {
|
||||
$retVal[$i] = new stdClass();
|
||||
|
||||
$retVal[$i]->name = $query[$i]->column_name;
|
||||
$retVal[$i]->type = $query[$i]->data_type;
|
||||
$retVal[$i]->nullable = $query[$i]->is_nullable === 'YES';
|
||||
$retVal[$i]->default = $query[$i]->column_default;
|
||||
$retVal[$i]->max_length = $query[$i]->character_maximum_length > 0 ? $query[$i]->character_maximum_length : $query[$i]->numeric_precision;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with index data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _indexData(string $table): array
|
||||
{
|
||||
$sql = 'SELECT "indexname", "indexdef"
|
||||
FROM "pg_indexes"
|
||||
WHERE LOWER("tablename") = ' . $this->escape(strtolower($table)) . '
|
||||
AND "schemaname" = ' . $this->escape('public');
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetIndexData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
|
||||
foreach ($query as $row) {
|
||||
$obj = new stdClass();
|
||||
$obj->name = $row->indexname;
|
||||
$_fields = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef)));
|
||||
$obj->fields = array_map(static fn ($v) => trim($v), $_fields);
|
||||
|
||||
if (strpos($row->indexdef, 'CREATE UNIQUE INDEX pk') === 0) {
|
||||
$obj->type = 'PRIMARY';
|
||||
} else {
|
||||
$obj->type = (strpos($row->indexdef, 'CREATE UNIQUE') === 0) ? 'UNIQUE' : 'INDEX';
|
||||
}
|
||||
|
||||
$retVal[$obj->name] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with Foreign key data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _foreignKeyData(string $table): array
|
||||
{
|
||||
$sql = 'SELECT
|
||||
tc.constraint_name, tc.table_name, kcu.column_name,
|
||||
ccu.table_name AS foreign_table_name,
|
||||
ccu.column_name AS foreign_column_name
|
||||
FROM information_schema.table_constraints AS tc
|
||||
JOIN information_schema.key_column_usage AS kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
JOIN information_schema.constraint_column_usage AS ccu
|
||||
ON ccu.constraint_name = tc.constraint_name
|
||||
WHERE constraint_type = ' . $this->escape('FOREIGN KEY') . ' AND
|
||||
tc.table_name = ' . $this->escape($table);
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetForeignKeyData'));
|
||||
}
|
||||
|
||||
$query = $query->getResultObject();
|
||||
$retVal = [];
|
||||
|
||||
foreach ($query as $row) {
|
||||
$obj = new stdClass();
|
||||
|
||||
$obj->constraint_name = $row->constraint_name;
|
||||
$obj->table_name = $row->table_name;
|
||||
$obj->column_name = $row->column_name;
|
||||
$obj->foreign_table_name = $row->foreign_table_name;
|
||||
$obj->foreign_column_name = $row->foreign_column_name;
|
||||
|
||||
$retVal[] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform-specific SQL to disable foreign key checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _disableForeignKeyChecks()
|
||||
{
|
||||
return 'SET CONSTRAINTS ALL DEFERRED';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform-specific SQL to enable foreign key checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _enableForeignKeyChecks()
|
||||
{
|
||||
return 'SET CONSTRAINTS ALL IMMEDIATE;';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code and message.
|
||||
* Must return this format: ['code' => string|int, 'message' => string]
|
||||
* intval(code) === 0 means "no error".
|
||||
*
|
||||
* @return array<string, int|string>
|
||||
*/
|
||||
public function error(): array
|
||||
{
|
||||
return [
|
||||
'code' => '',
|
||||
'message' => pg_last_error($this->connID) ?: '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|string
|
||||
*/
|
||||
public function insertID()
|
||||
{
|
||||
$v = pg_version($this->connID);
|
||||
// 'server' key is only available since PostgreSQL 7.4
|
||||
$v = explode(' ', $v['server'])[0] ?? 0;
|
||||
|
||||
$table = func_num_args() > 0 ? func_get_arg(0) : null;
|
||||
$column = func_num_args() > 1 ? func_get_arg(1) : null;
|
||||
|
||||
if ($table === null && $v >= '8.1') {
|
||||
$sql = 'SELECT LASTVAL() AS ins_id';
|
||||
} elseif ($table !== null) {
|
||||
if ($column !== null && $v >= '8.0') {
|
||||
$sql = "SELECT pg_get_serial_sequence('{$table}', '{$column}') AS seq";
|
||||
$query = $this->query($sql);
|
||||
$query = $query->getRow();
|
||||
$seq = $query->seq;
|
||||
} else {
|
||||
// seq_name passed in table parameter
|
||||
$seq = $table;
|
||||
}
|
||||
|
||||
$sql = "SELECT CURRVAL('{$seq}') AS ins_id";
|
||||
} else {
|
||||
return pg_last_oid($this->resultID);
|
||||
}
|
||||
|
||||
$query = $this->query($sql);
|
||||
$query = $query->getRow();
|
||||
|
||||
return (int) $query->ins_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a DSN from the provided parameters
|
||||
*/
|
||||
protected function buildDSN()
|
||||
{
|
||||
if ($this->DSN !== '') {
|
||||
$this->DSN = '';
|
||||
}
|
||||
|
||||
// If UNIX sockets are used, we shouldn't set a port
|
||||
if (strpos($this->hostname, '/') !== false) {
|
||||
$this->port = '';
|
||||
}
|
||||
|
||||
if ($this->hostname !== '') {
|
||||
$this->DSN = "host={$this->hostname} ";
|
||||
}
|
||||
|
||||
// ctype_digit only accepts strings
|
||||
$port = (string) $this->port;
|
||||
|
||||
if ($port !== '' && ctype_digit($port)) {
|
||||
$this->DSN .= "port={$port} ";
|
||||
}
|
||||
|
||||
if ($this->username !== '') {
|
||||
$this->DSN .= "user={$this->username} ";
|
||||
|
||||
// An empty password is valid!
|
||||
// password must be set to null to ignore it.
|
||||
if ($this->password !== null) {
|
||||
$this->DSN .= "password='{$this->password}' ";
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->database !== '') {
|
||||
$this->DSN .= "dbname={$this->database} ";
|
||||
}
|
||||
|
||||
// We don't have these options as elements in our standard configuration
|
||||
// array, but they might be set by parse_url() if the configuration was
|
||||
// provided via string> Example:
|
||||
//
|
||||
// Postgre://username:password@localhost:5432/database?connect_timeout=5&sslmode=1
|
||||
foreach (['connect_timeout', 'options', 'sslmode', 'service'] as $key) {
|
||||
if (isset($this->{$key}) && is_string($this->{$key}) && $this->{$key} !== '') {
|
||||
$this->DSN .= "{$key}='{$this->{$key}}' ";
|
||||
}
|
||||
}
|
||||
|
||||
$this->DSN = rtrim($this->DSN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set client encoding
|
||||
*/
|
||||
protected function setClientEncoding(string $charset): bool
|
||||
{
|
||||
return pg_set_client_encoding($this->connID, $charset) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*/
|
||||
protected function _transBegin(): bool
|
||||
{
|
||||
return (bool) pg_query($this->connID, 'BEGIN');
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*/
|
||||
protected function _transCommit(): bool
|
||||
{
|
||||
return (bool) pg_query($this->connID, 'COMMIT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*/
|
||||
protected function _transRollback(): bool
|
||||
{
|
||||
return (bool) pg_query($this->connID, 'ROLLBACK');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a query is a "write" type.
|
||||
*
|
||||
* Overrides BaseConnection::isWriteType, adding additional read query types.
|
||||
*
|
||||
* @param mixed $sql
|
||||
*/
|
||||
public function isWriteType($sql): bool
|
||||
{
|
||||
if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#is', $sql)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::isWriteType($sql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Postgre;
|
||||
|
||||
use CodeIgniter\Database\Forge as BaseForge;
|
||||
|
||||
/**
|
||||
* Forge for Postgre
|
||||
*/
|
||||
class Forge extends BaseForge
|
||||
{
|
||||
/**
|
||||
* CHECK DATABASE EXIST statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $checkDatabaseExistStr = 'SELECT 1 FROM pg_database WHERE datname = ?';
|
||||
|
||||
/**
|
||||
* DROP CONSTRAINT statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropConstraintStr = 'ALTER TABLE %s DROP CONSTRAINT %s';
|
||||
|
||||
/**
|
||||
* DROP INDEX statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropIndexStr = 'DROP INDEX %s';
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_unsigned = [
|
||||
'INT2' => 'INTEGER',
|
||||
'SMALLINT' => 'INTEGER',
|
||||
'INT' => 'BIGINT',
|
||||
'INT4' => 'BIGINT',
|
||||
'INTEGER' => 'BIGINT',
|
||||
'INT8' => 'NUMERIC',
|
||||
'BIGINT' => 'NUMERIC',
|
||||
'REAL' => 'DOUBLE PRECISION',
|
||||
'FLOAT' => 'DOUBLE PRECISION',
|
||||
];
|
||||
|
||||
/**
|
||||
* NULL value representation in CREATE/ALTER TABLE statements
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected $null = 'NULL';
|
||||
|
||||
/**
|
||||
* CREATE TABLE attributes
|
||||
*
|
||||
* @param array $attributes Associative array of table attributes
|
||||
*/
|
||||
protected function _createTableAttributes(array $attributes): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $field
|
||||
*
|
||||
* @return array|bool|string
|
||||
*/
|
||||
protected function _alterTable(string $alterType, string $table, $field)
|
||||
{
|
||||
if (in_array($alterType, ['DROP', 'ADD'], true)) {
|
||||
return parent::_alterTable($alterType, $table, $field);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
|
||||
$sqls = [];
|
||||
|
||||
foreach ($field as $data) {
|
||||
if ($data['_literal'] !== false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (version_compare($this->db->getVersion(), '8', '>=') && isset($data['type'])) {
|
||||
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
|
||||
. " TYPE {$data['type']}{$data['length']}";
|
||||
}
|
||||
|
||||
if (! empty($data['default'])) {
|
||||
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
|
||||
. " SET DEFAULT {$data['default']}";
|
||||
}
|
||||
|
||||
if (isset($data['null'])) {
|
||||
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
|
||||
. ($data['null'] === true ? ' DROP' : ' SET') . ' NOT NULL';
|
||||
}
|
||||
|
||||
if (! empty($data['new_name'])) {
|
||||
$sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($data['name'])
|
||||
. ' TO ' . $this->db->escapeIdentifiers($data['new_name']);
|
||||
}
|
||||
|
||||
if (! empty($data['comment'])) {
|
||||
$sqls[] = 'COMMENT ON COLUMN' . $this->db->escapeIdentifiers($table)
|
||||
. '.' . $this->db->escapeIdentifiers($data['name'])
|
||||
. " IS {$data['comment']}";
|
||||
}
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*/
|
||||
protected function _processColumn(array $field): string
|
||||
{
|
||||
return $this->db->escapeIdentifiers($field['name'])
|
||||
. ' ' . $field['type'] . $field['length']
|
||||
. $field['default']
|
||||
. $field['null']
|
||||
. $field['auto_increment']
|
||||
. $field['unique'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a data type mapping between different databases.
|
||||
*/
|
||||
protected function _attributeType(array &$attributes)
|
||||
{
|
||||
// Reset field lengths for data types that don't support it
|
||||
if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== false) {
|
||||
$attributes['CONSTRAINT'] = null;
|
||||
}
|
||||
|
||||
switch (strtoupper($attributes['TYPE'])) {
|
||||
case 'TINYINT':
|
||||
$attributes['TYPE'] = 'SMALLINT';
|
||||
$attributes['UNSIGNED'] = false;
|
||||
break;
|
||||
|
||||
case 'MEDIUMINT':
|
||||
$attributes['TYPE'] = 'INTEGER';
|
||||
$attributes['UNSIGNED'] = false;
|
||||
break;
|
||||
|
||||
case 'DATETIME':
|
||||
$attributes['TYPE'] = 'TIMESTAMP';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*/
|
||||
protected function _attributeAutoIncrement(array &$attributes, array &$field)
|
||||
{
|
||||
if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true) {
|
||||
$field['type'] = $field['type'] === 'NUMERIC' || $field['type'] === 'BIGINT' ? 'BIGSERIAL' : 'SERIAL';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific DROP TABLE string
|
||||
*/
|
||||
protected function _dropTable(string $table, bool $ifExists, bool $cascade): string
|
||||
{
|
||||
$sql = parent::_dropTable($table, $ifExists, $cascade);
|
||||
|
||||
if ($cascade === true) {
|
||||
$sql .= ' CASCADE';
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Postgre;
|
||||
|
||||
use BadMethodCallException;
|
||||
use CodeIgniter\Database\BasePreparedQuery;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Prepared query for Postgre
|
||||
*/
|
||||
class PreparedQuery extends BasePreparedQuery
|
||||
{
|
||||
/**
|
||||
* Stores the name this query can be
|
||||
* used under by postgres. Only used internally.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The result resource from a successful
|
||||
* pg_exec. Or false.
|
||||
*
|
||||
* @var bool|Result
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* Prepares the query against the database, and saves the connection
|
||||
* info necessary to execute the query later.
|
||||
*
|
||||
* NOTE: This version is based on SQL code. Child classes should
|
||||
* override this method.
|
||||
*
|
||||
* @param array $options Passed to the connection's prepare statement.
|
||||
* Unused in the MySQLi driver.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _prepare(string $sql, array $options = [])
|
||||
{
|
||||
$this->name = (string) random_int(1, 10_000_000_000_000_000);
|
||||
|
||||
$sql = $this->parameterize($sql);
|
||||
|
||||
// Update the query object since the parameters are slightly different
|
||||
// than what was put in.
|
||||
$this->query->setQuery($sql);
|
||||
|
||||
if (! $this->statement = pg_prepare($this->db->connID, $this->name, $sql)) {
|
||||
$this->errorCode = 0;
|
||||
$this->errorString = pg_last_error($this->db->connID);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a new set of data and runs it against the currently
|
||||
* prepared query. Upon success, will return a Results object.
|
||||
*/
|
||||
public function _execute(array $data): bool
|
||||
{
|
||||
if (! isset($this->statement)) {
|
||||
throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
|
||||
}
|
||||
|
||||
$this->result = pg_execute($this->db->connID, $this->name, $data);
|
||||
|
||||
return (bool) $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result object for the prepared query.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _getResult()
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the ? placeholders with $1, $2, etc parameters for use
|
||||
* within the prepared query.
|
||||
*/
|
||||
public function parameterize(string $sql): string
|
||||
{
|
||||
// Track our current value
|
||||
$count = 0;
|
||||
|
||||
return preg_replace_callback('/\?/', static function () use (&$count) {
|
||||
$count++;
|
||||
|
||||
return "\${$count}";
|
||||
}, $sql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Postgre;
|
||||
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Result for Postgre
|
||||
*/
|
||||
class Result extends BaseResult
|
||||
{
|
||||
/**
|
||||
* Gets the number of fields in the result set.
|
||||
*/
|
||||
public function getFieldCount(): int
|
||||
{
|
||||
return pg_num_fields($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of column names in the result set.
|
||||
*/
|
||||
public function getFieldNames(): array
|
||||
{
|
||||
$fieldNames = [];
|
||||
|
||||
for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i++) {
|
||||
$fieldNames[] = pg_field_name($this->resultID, $i);
|
||||
}
|
||||
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of objects representing field meta-data.
|
||||
*/
|
||||
public function getFieldData(): array
|
||||
{
|
||||
$retVal = [];
|
||||
|
||||
for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i++) {
|
||||
$retVal[$i] = new stdClass();
|
||||
$retVal[$i]->name = pg_field_name($this->resultID, $i);
|
||||
$retVal[$i]->type = pg_field_type_oid($this->resultID, $i);
|
||||
$retVal[$i]->type_name = pg_field_type($this->resultID, $i);
|
||||
$retVal[$i]->max_length = pg_field_size($this->resultID, $i);
|
||||
$retVal[$i]->length = $retVal[$i]->max_length;
|
||||
// $retVal[$i]->primary_key = (int)($fieldData[$i]->flags & 2);
|
||||
// $retVal[$i]->default = $fieldData[$i]->def;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the current result.
|
||||
*/
|
||||
public function freeResult()
|
||||
{
|
||||
if ($this->resultID !== false) {
|
||||
pg_free_result($this->resultID);
|
||||
$this->resultID = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal pointer to the desired offset. This is called
|
||||
* internally before fetching results to make sure the result set
|
||||
* starts at zero.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function dataSeek(int $n = 0)
|
||||
{
|
||||
return pg_result_seek($this->resultID, $n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an array.
|
||||
*
|
||||
* Overridden by driver classes.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetchAssoc()
|
||||
{
|
||||
return pg_fetch_assoc($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an object.
|
||||
*
|
||||
* Overridden by child classes.
|
||||
*
|
||||
* @return bool|Entity|object
|
||||
*/
|
||||
protected function fetchObject(string $className = 'stdClass')
|
||||
{
|
||||
if (is_subclass_of($className, Entity::class)) {
|
||||
return empty($data = $this->fetchAssoc()) ? false : (new $className())->setAttributes($data);
|
||||
}
|
||||
|
||||
return pg_fetch_object($this->resultID, null, $className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of rows in the resultID (i.e., PostgreSQL query result resource)
|
||||
*/
|
||||
public function getNumRows(): int
|
||||
{
|
||||
if (! is_int($this->numRows)) {
|
||||
$this->numRows = pg_num_rows($this->resultID);
|
||||
}
|
||||
|
||||
return $this->numRows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Postgre;
|
||||
|
||||
use CodeIgniter\Database\BaseUtils;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Utils for Postgre
|
||||
*/
|
||||
class Utils extends BaseUtils
|
||||
{
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $listDatabases = 'SELECT datname FROM pg_database';
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $optimizeTable = 'REINDEX TABLE %s';
|
||||
|
||||
/**
|
||||
* Platform dependent version of the backup function.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _backup(?array $prefs = null)
|
||||
{
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
/**
|
||||
* Prepared query interface
|
||||
*/
|
||||
interface PreparedQueryInterface
|
||||
{
|
||||
/**
|
||||
* Takes a new set of data and runs it against the currently
|
||||
* prepared query. Upon success, will return a Results object.
|
||||
*
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function execute(...$data);
|
||||
|
||||
/**
|
||||
* Prepares the query against the database, and saves the connection
|
||||
* info necessary to execute the query later.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function prepare(string $sql, array $options = []);
|
||||
|
||||
/**
|
||||
* Explicity closes the statement.
|
||||
*/
|
||||
public function close();
|
||||
|
||||
/**
|
||||
* Returns the SQL that has been prepared.
|
||||
*/
|
||||
public function getQueryString(): string;
|
||||
|
||||
/**
|
||||
* Returns the error code created while executing this statement.
|
||||
*/
|
||||
public function getErrorCode(): int;
|
||||
|
||||
/**
|
||||
* Returns the error message created while executing this statement.
|
||||
*/
|
||||
public function getErrorMessage(): string;
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
/**
|
||||
* Query builder
|
||||
*/
|
||||
class Query implements QueryInterface
|
||||
{
|
||||
/**
|
||||
* The query string, as provided by the user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $originalQueryString;
|
||||
|
||||
/**
|
||||
* The query string if table prefix has been swapped.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $swappedQueryString;
|
||||
|
||||
/**
|
||||
* The final query string after binding, etc.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $finalQueryString;
|
||||
|
||||
/**
|
||||
* The binds and their values used for binding.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $binds = [];
|
||||
|
||||
/**
|
||||
* Bind marker
|
||||
*
|
||||
* Character used to identify values in a prepared statement.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bindMarker = '?';
|
||||
|
||||
/**
|
||||
* The start time in seconds with microseconds
|
||||
* for when this query was executed.
|
||||
*
|
||||
* @var float|string
|
||||
*/
|
||||
protected $startTime;
|
||||
|
||||
/**
|
||||
* The end time in seconds with microseconds
|
||||
* for when this query was executed.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $endTime;
|
||||
|
||||
/**
|
||||
* The error code, if any.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $errorCode;
|
||||
|
||||
/**
|
||||
* The error message, if any.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $errorString;
|
||||
|
||||
/**
|
||||
* Pointer to database connection.
|
||||
* Mainly for escaping features.
|
||||
*
|
||||
* @var ConnectionInterface
|
||||
*/
|
||||
public $db;
|
||||
|
||||
public function __construct(ConnectionInterface $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the raw query string to use for this statement.
|
||||
*
|
||||
* @param mixed $binds
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setQuery(string $sql, $binds = null, bool $setEscape = true)
|
||||
{
|
||||
$this->originalQueryString = $sql;
|
||||
unset($this->swappedQueryString);
|
||||
|
||||
if ($binds !== null) {
|
||||
if (! is_array($binds)) {
|
||||
$binds = [$binds];
|
||||
}
|
||||
|
||||
if ($setEscape) {
|
||||
array_walk($binds, static function (&$item) {
|
||||
$item = [
|
||||
$item,
|
||||
true,
|
||||
];
|
||||
});
|
||||
}
|
||||
$this->binds = $binds;
|
||||
}
|
||||
|
||||
unset($this->finalQueryString);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will store the variables to bind into the query later.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBinds(array $binds, bool $setEscape = true)
|
||||
{
|
||||
if ($setEscape) {
|
||||
array_walk($binds, static function (&$item) {
|
||||
$item = [$item, true];
|
||||
});
|
||||
}
|
||||
|
||||
$this->binds = $binds;
|
||||
|
||||
unset($this->finalQueryString);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final, processed query string after binding, etal
|
||||
* has been performed.
|
||||
*/
|
||||
public function getQuery(): string
|
||||
{
|
||||
if (empty($this->finalQueryString)) {
|
||||
$this->compileBinds();
|
||||
}
|
||||
|
||||
return $this->finalQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the execution time of the statement using microtime(true)
|
||||
* for it's start and end values. If no end value is present, will
|
||||
* use the current time to determine total duration.
|
||||
*
|
||||
* @param float $end
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDuration(float $start, ?float $end = null)
|
||||
{
|
||||
$this->startTime = $start;
|
||||
|
||||
if ($end === null) {
|
||||
$end = microtime(true);
|
||||
}
|
||||
|
||||
$this->endTime = $end;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start time in seconds with microseconds.
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public function getStartTime(bool $returnRaw = false, int $decimals = 6)
|
||||
{
|
||||
if ($returnRaw) {
|
||||
return $this->startTime;
|
||||
}
|
||||
|
||||
return number_format($this->startTime, $decimals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration of this query during execution, or null if
|
||||
* the query has not been executed yet.
|
||||
*
|
||||
* @param int $decimals The accuracy of the returned time.
|
||||
*/
|
||||
public function getDuration(int $decimals = 6): string
|
||||
{
|
||||
return number_format(($this->endTime - $this->startTime), $decimals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the error description that happened for this query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setError(int $code, string $error)
|
||||
{
|
||||
$this->errorCode = $code;
|
||||
$this->errorString = $error;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether this statement created an error not.
|
||||
*/
|
||||
public function hasError(): bool
|
||||
{
|
||||
return ! empty($this->errorString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error code created while executing this statement.
|
||||
*/
|
||||
public function getErrorCode(): int
|
||||
{
|
||||
return $this->errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error message created while executing this statement.
|
||||
*/
|
||||
public function getErrorMessage(): string
|
||||
{
|
||||
return $this->errorString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the statement is a write-type query or not.
|
||||
*/
|
||||
public function isWriteType(): bool
|
||||
{
|
||||
return $this->db->isWriteType($this->originalQueryString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps out one table prefix for a new one.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function swapPrefix(string $orig, string $swap)
|
||||
{
|
||||
$sql = $this->swappedQueryString ?? $this->originalQueryString;
|
||||
|
||||
$from = '/(\W)' . $orig . '(\S)/';
|
||||
$to = '\\1' . $swap . '\\2';
|
||||
|
||||
$this->swappedQueryString = preg_replace($from, $to, $sql);
|
||||
|
||||
unset($this->finalQueryString);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original SQL that was passed into the system.
|
||||
*/
|
||||
public function getOriginalQuery(): string
|
||||
{
|
||||
return $this->originalQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes and inserts any binds into the finalQueryString property.
|
||||
*
|
||||
* @see https://regex101.com/r/EUEhay/5
|
||||
*/
|
||||
protected function compileBinds()
|
||||
{
|
||||
$sql = $this->swappedQueryString ?? $this->originalQueryString;
|
||||
$binds = $this->binds;
|
||||
|
||||
if (empty($binds)) {
|
||||
$this->finalQueryString = $sql;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_int(array_key_first($binds))) {
|
||||
$bindCount = count($binds);
|
||||
$ml = strlen($this->bindMarker);
|
||||
|
||||
$this->finalQueryString = $this->matchSimpleBinds($sql, $binds, $bindCount, $ml);
|
||||
} else {
|
||||
// Reverse the binds so that duplicate named binds
|
||||
// will be processed prior to the original binds.
|
||||
$binds = array_reverse($binds);
|
||||
|
||||
$this->finalQueryString = $this->matchNamedBinds($sql, $binds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match bindings
|
||||
*/
|
||||
protected function matchNamedBinds(string $sql, array $binds): string
|
||||
{
|
||||
$replacers = [];
|
||||
|
||||
foreach ($binds as $placeholder => $value) {
|
||||
// $value[1] contains the boolean whether should be escaped or not
|
||||
$escapedValue = $value[1] ? $this->db->escape($value[0]) : $value[0];
|
||||
|
||||
// In order to correctly handle backlashes in saved strings
|
||||
// we will need to preg_quote, so remove the wrapping escape characters
|
||||
// otherwise it will get escaped.
|
||||
if (is_array($value[0])) {
|
||||
$escapedValue = '(' . implode(',', $escapedValue) . ')';
|
||||
}
|
||||
|
||||
$replacers[":{$placeholder}:"] = $escapedValue;
|
||||
}
|
||||
|
||||
return strtr($sql, $replacers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match bindings
|
||||
*/
|
||||
protected function matchSimpleBinds(string $sql, array $binds, int $bindCount, int $ml): string
|
||||
{
|
||||
if ($c = preg_match_all("/'[^']*'/", $sql, $matches)) {
|
||||
$c = preg_match_all('/' . preg_quote($this->bindMarker, '/') . '/i', str_replace($matches[0], str_replace($this->bindMarker, str_repeat(' ', $ml), $matches[0]), $sql, $c), $matches, PREG_OFFSET_CAPTURE);
|
||||
|
||||
// Bind values' count must match the count of markers in the query
|
||||
if ($bindCount !== $c) {
|
||||
return $sql;
|
||||
}
|
||||
} elseif (($c = preg_match_all('/' . preg_quote($this->bindMarker, '/') . '/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bindCount) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
do {
|
||||
$c--;
|
||||
$escapedValue = $binds[$c][1] ? $this->db->escape($binds[$c][0]) : $binds[$c][0];
|
||||
|
||||
if (is_array($escapedValue)) {
|
||||
$escapedValue = '(' . implode(',', $escapedValue) . ')';
|
||||
}
|
||||
|
||||
$sql = substr_replace($sql, $escapedValue, $matches[0][$c][1], $ml);
|
||||
} while ($c !== 0);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string to display in debug toolbar
|
||||
*/
|
||||
public function debugToolbarDisplay(): string
|
||||
{
|
||||
// Key words we want bolded
|
||||
static $highlight = [
|
||||
'AND',
|
||||
'AS',
|
||||
'ASC',
|
||||
'AVG',
|
||||
'BY',
|
||||
'COUNT',
|
||||
'DESC',
|
||||
'DISTINCT',
|
||||
'FROM',
|
||||
'GROUP',
|
||||
'HAVING',
|
||||
'IN',
|
||||
'INNER',
|
||||
'INSERT',
|
||||
'INTO',
|
||||
'IS',
|
||||
'JOIN',
|
||||
'LEFT',
|
||||
'LIKE',
|
||||
'LIMIT',
|
||||
'MAX',
|
||||
'MIN',
|
||||
'NOT',
|
||||
'NULL',
|
||||
'OFFSET',
|
||||
'ON',
|
||||
'OR',
|
||||
'ORDER',
|
||||
'RIGHT',
|
||||
'SELECT',
|
||||
'SUM',
|
||||
'UPDATE',
|
||||
'VALUES',
|
||||
'WHERE',
|
||||
];
|
||||
|
||||
$sql = esc($this->getQuery());
|
||||
|
||||
/**
|
||||
* @see https://stackoverflow.com/a/20767160
|
||||
* @see https://regex101.com/r/hUlrGN/4
|
||||
*/
|
||||
$search = '/\b(?:' . implode('|', $highlight) . ')\b(?![^(')]*'(?:(?:[^(')]*'){2})*[^(')]*$)/';
|
||||
|
||||
return preg_replace_callback($search, static fn ($matches) => '<strong>' . str_replace(' ', ' ', $matches[0]) . '</strong>', $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return text representation of the query
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getQuery();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
/**
|
||||
* Interface QueryInterface
|
||||
*
|
||||
* Represents a single statement that can be executed against the database.
|
||||
* Statements are platform-specific and can handle binding of binds.
|
||||
*/
|
||||
interface QueryInterface
|
||||
{
|
||||
/**
|
||||
* Sets the raw query string to use for this statement.
|
||||
*
|
||||
* @param mixed $binds
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setQuery(string $sql, $binds = null, bool $setEscape = true);
|
||||
|
||||
/**
|
||||
* Returns the final, processed query string after binding, etal
|
||||
* has been performed.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getQuery();
|
||||
|
||||
/**
|
||||
* Records the execution time of the statement using microtime(true)
|
||||
* for it's start and end values. If no end value is present, will
|
||||
* use the current time to determine total duration.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setDuration(float $start, ?float $end = null);
|
||||
|
||||
/**
|
||||
* Returns the duration of this query during execution, or null if
|
||||
* the query has not been executed yet.
|
||||
*
|
||||
* @param int $decimals The accuracy of the returned time.
|
||||
*/
|
||||
public function getDuration(int $decimals = 6): string;
|
||||
|
||||
/**
|
||||
* Stores the error description that happened for this query.
|
||||
*/
|
||||
public function setError(int $code, string $error);
|
||||
|
||||
/**
|
||||
* Reports whether this statement created an error not.
|
||||
*/
|
||||
public function hasError(): bool;
|
||||
|
||||
/**
|
||||
* Returns the error code created while executing this statement.
|
||||
*/
|
||||
public function getErrorCode(): int;
|
||||
|
||||
/**
|
||||
* Returns the error message created while executing this statement.
|
||||
*/
|
||||
public function getErrorMessage(): string;
|
||||
|
||||
/**
|
||||
* Determines if the statement is a write-type query or not.
|
||||
*/
|
||||
public function isWriteType(): bool;
|
||||
|
||||
/**
|
||||
* Swaps out one table prefix for a new one.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function swapPrefix(string $orig, string $swap);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
class RawSql
|
||||
{
|
||||
/**
|
||||
* @var string Raw SQL string
|
||||
*/
|
||||
private string $string;
|
||||
|
||||
public function __construct(string $sqlString)
|
||||
{
|
||||
$this->string = $sqlString;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance with new SQL string
|
||||
*/
|
||||
public function with(string $newSqlString): self
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->string = $newSqlString;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unique id for binding key
|
||||
*/
|
||||
public function getBindingKey(): string
|
||||
{
|
||||
return 'RawSql' . spl_object_id($this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
/**
|
||||
* Interface ResultInterface
|
||||
*/
|
||||
interface ResultInterface
|
||||
{
|
||||
/**
|
||||
* Retrieve the results of the query. Typically an array of
|
||||
* individual data rows, which can be either an 'array', an
|
||||
* 'object', or a custom class name.
|
||||
*
|
||||
* @param string $type The row type. Either 'array', 'object', or a class name to use
|
||||
*/
|
||||
public function getResult(string $type = 'object'): array;
|
||||
|
||||
/**
|
||||
* Returns the results as an array of custom objects.
|
||||
*
|
||||
* @param string $className The name of the class to use.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCustomResultObject(string $className);
|
||||
|
||||
/**
|
||||
* Returns the results as an array of arrays.
|
||||
*
|
||||
* If no results, an empty array is returned.
|
||||
*/
|
||||
public function getResultArray(): array;
|
||||
|
||||
/**
|
||||
* Returns the results as an array of objects.
|
||||
*
|
||||
* If no results, an empty array is returned.
|
||||
*/
|
||||
public function getResultObject(): array;
|
||||
|
||||
/**
|
||||
* Wrapper object to return a row as either an array, an object, or
|
||||
* a custom class.
|
||||
*
|
||||
* If row doesn't exist, returns null.
|
||||
*
|
||||
* @param mixed $n The index of the results to return
|
||||
* @param string $type The type of result object. 'array', 'object' or class name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRow($n = 0, string $type = 'object');
|
||||
|
||||
/**
|
||||
* Returns a row as a custom class instance.
|
||||
*
|
||||
* If row doesn't exists, returns null.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCustomRowObject(int $n, string $className);
|
||||
|
||||
/**
|
||||
* Returns a single row from the results as an array.
|
||||
*
|
||||
* If row doesn't exist, returns null.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRowArray(int $n = 0);
|
||||
|
||||
/**
|
||||
* Returns a single row from the results as an object.
|
||||
*
|
||||
* If row doesn't exist, returns null.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRowObject(int $n = 0);
|
||||
|
||||
/**
|
||||
* Assigns an item into a particular column slot.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setRow($key, $value = null);
|
||||
|
||||
/**
|
||||
* Returns the "first" row of the current results.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFirstRow(string $type = 'object');
|
||||
|
||||
/**
|
||||
* Returns the "last" row of the current results.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLastRow(string $type = 'object');
|
||||
|
||||
/**
|
||||
* Returns the "next" row of the current results.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNextRow(string $type = 'object');
|
||||
|
||||
/**
|
||||
* Returns the "previous" row of the current results.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPreviousRow(string $type = 'object');
|
||||
|
||||
/**
|
||||
* Returns an unbuffered row and move the pointer to the next row.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUnbufferedRow(string $type = 'object');
|
||||
|
||||
/**
|
||||
* Gets the number of fields in the result set.
|
||||
*/
|
||||
public function getFieldCount(): int;
|
||||
|
||||
/**
|
||||
* Generates an array of column names in the result set.
|
||||
*/
|
||||
public function getFieldNames(): array;
|
||||
|
||||
/**
|
||||
* Generates an array of objects representing field meta-data.
|
||||
*/
|
||||
public function getFieldData(): array;
|
||||
|
||||
/**
|
||||
* Frees the current result.
|
||||
*/
|
||||
public function freeResult();
|
||||
|
||||
/**
|
||||
* Moves the internal pointer to the desired offset. This is called
|
||||
* internally before fetching results to make sure the result set
|
||||
* starts at zero.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function dataSeek(int $n = 0);
|
||||
}
|
||||
Executable
+638
@@ -0,0 +1,638 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Database\Exceptions\DataException;
|
||||
use CodeIgniter\Database\RawSql;
|
||||
use CodeIgniter\Database\ResultInterface;
|
||||
|
||||
/**
|
||||
* Builder for SQLSRV
|
||||
*
|
||||
* @todo auto check for TextCastToInt
|
||||
* @todo auto check for InsertIndexValue
|
||||
* @todo replace: delete index entries before insert
|
||||
*/
|
||||
class Builder extends BaseBuilder
|
||||
{
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $randomKeyword = [
|
||||
'NEWID()',
|
||||
'RAND(%d)',
|
||||
];
|
||||
|
||||
/**
|
||||
* Quoted identifier flag
|
||||
*
|
||||
* Whether to use SQL-92 standard quoted identifier
|
||||
* (double quotes) or brackets for identifier escaping.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_quoted_identifier = true;
|
||||
|
||||
/**
|
||||
* Handle increment/decrement on text
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $castTextToInt = true;
|
||||
|
||||
/**
|
||||
* Handle IDENTITY_INSERT property/
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $keyPermission = false;
|
||||
|
||||
/**
|
||||
* Groups tables in FROM clauses if needed, so there is no confusion
|
||||
* about operator precedence.
|
||||
*/
|
||||
protected function _fromTables(): string
|
||||
{
|
||||
$from = [];
|
||||
|
||||
foreach ($this->QBFrom as $value) {
|
||||
$from[] = strpos($value, '(SELECT') === 0 ? $value : $this->getFullName($value);
|
||||
}
|
||||
|
||||
return implode(', ', $from);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the truncate() command,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*/
|
||||
protected function _truncate(string $table): string
|
||||
{
|
||||
return 'TRUNCATE TABLE ' . $this->getFullName($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the JOIN portion of the query
|
||||
*
|
||||
* @param RawSql|string $cond
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function join(string $table, $cond, string $type = '', ?bool $escape = null)
|
||||
{
|
||||
if ($type !== '') {
|
||||
$type = strtoupper(trim($type));
|
||||
|
||||
if (! in_array($type, $this->joinTypes, true)) {
|
||||
$type = '';
|
||||
} else {
|
||||
$type .= ' ';
|
||||
}
|
||||
}
|
||||
|
||||
// Extract any aliases that might exist. We use this information
|
||||
// in the protectIdentifiers to know whether to add a table prefix
|
||||
$this->trackAliases($table);
|
||||
|
||||
if (! is_bool($escape)) {
|
||||
$escape = $this->db->protectIdentifiers;
|
||||
}
|
||||
|
||||
if (! $this->hasOperator($cond)) {
|
||||
$cond = ' USING (' . ($escape ? $this->db->escapeIdentifiers($cond) : $cond) . ')';
|
||||
} elseif ($escape === false) {
|
||||
$cond = ' ON ' . $cond;
|
||||
} else {
|
||||
// Split multiple conditions
|
||||
if (preg_match_all('/\sAND\s|\sOR\s/i', $cond, $joints, PREG_OFFSET_CAPTURE)) {
|
||||
$conditions = [];
|
||||
$joints = $joints[0];
|
||||
array_unshift($joints, ['', 0]);
|
||||
|
||||
for ($i = count($joints) - 1, $pos = strlen($cond); $i >= 0; $i--) {
|
||||
$joints[$i][1] += strlen($joints[$i][0]); // offset
|
||||
$conditions[$i] = substr($cond, $joints[$i][1], $pos - $joints[$i][1]);
|
||||
$pos = $joints[$i][1] - strlen($joints[$i][0]);
|
||||
$joints[$i] = $joints[$i][0];
|
||||
}
|
||||
|
||||
ksort($conditions);
|
||||
} else {
|
||||
$conditions = [$cond];
|
||||
$joints = [''];
|
||||
}
|
||||
|
||||
$cond = ' ON ';
|
||||
|
||||
foreach ($conditions as $i => $condition) {
|
||||
$operator = $this->getOperator($condition);
|
||||
|
||||
$cond .= $joints[$i];
|
||||
$cond .= preg_match('/(\(*)?([\[\]\w\.\'-]+)' . preg_quote($operator, '/') . '(.*)/i', $condition, $match) ? $match[1] . $this->db->protectIdentifiers($match[2]) . $operator . $this->db->protectIdentifiers($match[3]) : $condition;
|
||||
}
|
||||
}
|
||||
|
||||
// Do we want to escape the table name?
|
||||
if ($escape === true) {
|
||||
$table = $this->db->protectIdentifiers($table, true, null, false);
|
||||
}
|
||||
|
||||
// Assemble the JOIN statement
|
||||
$this->QBJoin[] = $type . 'JOIN ' . $this->getFullName($table) . $cond;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific insert string from the supplied data
|
||||
*
|
||||
* @todo implement check for this instead static $insertKeyPermission
|
||||
*/
|
||||
protected function _insert(string $table, array $keys, array $unescapedKeys): string
|
||||
{
|
||||
$fullTableName = $this->getFullName($table);
|
||||
|
||||
// insert statement
|
||||
$statement = 'INSERT INTO ' . $fullTableName . ' (' . implode(',', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')';
|
||||
|
||||
return $this->keyPermission ? $this->addIdentity($fullTableName, $statement) : $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert batch statement
|
||||
*
|
||||
* Generates a platform-specific insert string from the supplied data.
|
||||
*/
|
||||
protected function _insertBatch(string $table, array $keys, array $values): string
|
||||
{
|
||||
return 'INSERT ' . $this->compileIgnore('insert') . 'INTO ' . $this->getFullName($table) . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific update string from the supplied data
|
||||
*/
|
||||
protected function _update(string $table, array $values): string
|
||||
{
|
||||
$valstr = [];
|
||||
|
||||
foreach ($values as $key => $val) {
|
||||
$valstr[] = $key . ' = ' . $val;
|
||||
}
|
||||
|
||||
$fullTableName = $this->getFullName($table);
|
||||
|
||||
$statement = sprintf('UPDATE %s%s SET ', empty($this->QBLimit) ? '' : 'TOP(' . $this->QBLimit . ') ', $fullTableName);
|
||||
|
||||
$statement .= implode(', ', $valstr)
|
||||
. $this->compileWhereHaving('QBWhere')
|
||||
. $this->compileOrderBy();
|
||||
|
||||
return $this->keyPermission ? $this->addIdentity($fullTableName, $statement) : $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update_Batch statement
|
||||
*
|
||||
* Generates a platform-specific batch update string from the supplied data
|
||||
*/
|
||||
protected function _updateBatch(string $table, array $values, string $index): string
|
||||
{
|
||||
$ids = [];
|
||||
$final = [];
|
||||
|
||||
foreach ($values as $val) {
|
||||
$ids[] = $val[$index];
|
||||
|
||||
foreach (array_keys($val) as $field) {
|
||||
if ($field !== $index) {
|
||||
$final[$field][] = 'WHEN ' . $index . ' = ' . $val[$index] . ' THEN ' . $val[$field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$cases = '';
|
||||
|
||||
foreach ($final as $k => $v) {
|
||||
$cases .= $k . " = CASE \n"
|
||||
. implode("\n", $v) . "\n"
|
||||
. 'ELSE ' . $k . ' END, ';
|
||||
}
|
||||
|
||||
$this->where($index . ' IN(' . implode(',', $ids) . ')', null, false);
|
||||
|
||||
return 'UPDATE ' . $this->compileIgnore('update') . ' ' . $this->getFullName($table) . ' SET ' . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere');
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments a numeric column by the specified value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function increment(string $column, int $value = 1)
|
||||
{
|
||||
$column = $this->db->protectIdentifiers($column);
|
||||
|
||||
if ($this->castTextToInt) {
|
||||
$values = [$column => "CONVERT(VARCHAR(MAX),CONVERT(INT,CONVERT(VARCHAR(MAX), {$column})) + {$value})"];
|
||||
} else {
|
||||
$values = [$column => "{$column} + {$value}"];
|
||||
}
|
||||
|
||||
$sql = $this->_update($this->QBFrom[0], $values);
|
||||
|
||||
if (! $this->testMode) {
|
||||
$this->resetWrite();
|
||||
|
||||
return $this->db->query($sql, $this->binds, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements a numeric column by the specified value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function decrement(string $column, int $value = 1)
|
||||
{
|
||||
$column = $this->db->protectIdentifiers($column);
|
||||
|
||||
if ($this->castTextToInt) {
|
||||
$values = [$column => "CONVERT(VARCHAR(MAX),CONVERT(INT,CONVERT(VARCHAR(MAX), {$column})) - {$value})"];
|
||||
} else {
|
||||
$values = [$column => "{$column} + {$value}"];
|
||||
}
|
||||
|
||||
$sql = $this->_update($this->QBFrom[0], $values);
|
||||
|
||||
if (! $this->testMode) {
|
||||
$this->resetWrite();
|
||||
|
||||
return $this->db->query($sql, $this->binds, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full name of the table
|
||||
*/
|
||||
private function getFullName(string $table): string
|
||||
{
|
||||
$alias = '';
|
||||
|
||||
if (strpos($table, ' ') !== false) {
|
||||
$alias = explode(' ', $table);
|
||||
$table = array_shift($alias);
|
||||
$alias = ' ' . implode(' ', $alias);
|
||||
}
|
||||
|
||||
if ($this->db->escapeChar === '"') {
|
||||
return '"' . $this->db->getDatabase() . '"."' . $this->db->schema . '"."' . str_replace('"', '', $table) . '"' . $alias;
|
||||
}
|
||||
|
||||
return '[' . $this->db->getDatabase() . '].[' . $this->db->schema . '].[' . str_replace('"', '', $table) . ']' . str_replace('"', '', $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add permision statements for index value inserts
|
||||
*/
|
||||
private function addIdentity(string $fullTable, string $insert): string
|
||||
{
|
||||
return 'SET IDENTITY_INSERT ' . $fullTable . " ON\n" . $insert . "\nSET IDENTITY_INSERT " . $fullTable . ' OFF';
|
||||
}
|
||||
|
||||
/**
|
||||
* Local implementation of limit
|
||||
*/
|
||||
protected function _limit(string $sql, bool $offsetIgnore = false): string
|
||||
{
|
||||
if (empty($this->QBOrderBy)) {
|
||||
$sql .= ' ORDER BY (SELECT NULL) ';
|
||||
}
|
||||
|
||||
if ($offsetIgnore) {
|
||||
$sql .= ' OFFSET 0 ';
|
||||
} else {
|
||||
$sql .= is_int($this->QBOffset) ? ' OFFSET ' . $this->QBOffset : ' OFFSET 0 ';
|
||||
}
|
||||
|
||||
return $sql . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a replace into string and runs the query
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function replace(?array $set = null)
|
||||
{
|
||||
if ($set !== null) {
|
||||
$this->set($set);
|
||||
}
|
||||
|
||||
if (empty($this->QBSet)) {
|
||||
if (CI_DEBUG) {
|
||||
throw new DatabaseException('You must use the "set" method to update an entry.');
|
||||
}
|
||||
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$table = $this->QBFrom[0];
|
||||
|
||||
$sql = $this->_replace($table, array_keys($this->QBSet), array_values($this->QBSet));
|
||||
|
||||
$this->resetWrite();
|
||||
|
||||
if ($this->testMode) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$this->db->simpleQuery('SET IDENTITY_INSERT ' . $this->getFullName($table) . ' ON');
|
||||
|
||||
$result = $this->db->query($sql, $this->binds, false);
|
||||
$this->db->simpleQuery('SET IDENTITY_INSERT ' . $this->getFullName($table) . ' OFF');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific replace string from the supplied data
|
||||
* on match delete and insert
|
||||
*/
|
||||
protected function _replace(string $table, array $keys, array $values): string
|
||||
{
|
||||
// check whether the existing keys are part of the primary key.
|
||||
// if so then use them for the "ON" part and exclude them from the $values and $keys
|
||||
$pKeys = $this->db->getIndexData($table);
|
||||
$keyFields = [];
|
||||
|
||||
foreach ($pKeys as $key) {
|
||||
if ($key->type === 'PRIMARY') {
|
||||
$keyFields = array_merge($keyFields, $key->fields);
|
||||
}
|
||||
|
||||
if ($key->type === 'UNIQUE') {
|
||||
$keyFields = array_merge($keyFields, $key->fields);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the unique field names
|
||||
$escKeyFields = array_map(fn (string $field): string => $this->db->protectIdentifiers($field), array_values(array_unique($keyFields)));
|
||||
|
||||
// Get the binds
|
||||
$binds = $this->binds;
|
||||
array_walk($binds, static function (&$item) {
|
||||
$item = $item[0];
|
||||
});
|
||||
|
||||
// Get the common field and values from the keys data and index fields
|
||||
$common = array_intersect($keys, $escKeyFields);
|
||||
$bingo = [];
|
||||
|
||||
foreach ($common as $v) {
|
||||
$k = array_search($v, $keys, true);
|
||||
|
||||
$bingo[$keys[$k]] = $binds[trim($values[$k], ':')];
|
||||
}
|
||||
|
||||
// Querying existing data
|
||||
$builder = $this->db->table($table);
|
||||
|
||||
foreach ($bingo as $k => $v) {
|
||||
$builder->where($k, $v);
|
||||
}
|
||||
|
||||
$q = $builder->get()->getResult();
|
||||
|
||||
// Delete entries if we find them
|
||||
if ($q !== []) {
|
||||
$delete = $this->db->table($table);
|
||||
|
||||
foreach ($bingo as $k => $v) {
|
||||
$delete->where($k, $v);
|
||||
}
|
||||
|
||||
$delete->delete();
|
||||
}
|
||||
|
||||
return sprintf('INSERT INTO %s (%s) VALUES (%s);', $this->getFullName($table), implode(',', $keys), implode(',', $values));
|
||||
}
|
||||
|
||||
/**
|
||||
* SELECT [MAX|MIN|AVG|SUM|COUNT]()
|
||||
*
|
||||
* Handle float return value
|
||||
*
|
||||
* @return BaseBuilder
|
||||
*/
|
||||
protected function maxMinAvgSum(string $select = '', string $alias = '', string $type = 'MAX')
|
||||
{
|
||||
// int functions can be handled by parent
|
||||
if ($type !== 'AVG') {
|
||||
return parent::maxMinAvgSum($select, $alias, $type);
|
||||
}
|
||||
|
||||
if ($select === '') {
|
||||
throw DataException::forEmptyInputGiven('Select');
|
||||
}
|
||||
|
||||
if (strpos($select, ',') !== false) {
|
||||
throw DataException::forInvalidArgument('Column name not separated by comma');
|
||||
}
|
||||
|
||||
if ($alias === '') {
|
||||
$alias = $this->createAliasFromTable(trim($select));
|
||||
}
|
||||
|
||||
$sql = $type . '( CAST( ' . $this->db->protectIdentifiers(trim($select)) . ' AS FLOAT ) ) AS ' . $this->db->escapeIdentifiers(trim($alias));
|
||||
|
||||
$this->QBSelect[] = $sql;
|
||||
$this->QBNoEscape[] = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Count All" query
|
||||
*
|
||||
* Generates a platform-specific query string that counts all records in
|
||||
* the particular table
|
||||
*
|
||||
* @param bool $reset Are we want to clear query builder values?
|
||||
*
|
||||
* @return int|string when $test = true
|
||||
*/
|
||||
public function countAll(bool $reset = true)
|
||||
{
|
||||
$table = $this->QBFrom[0];
|
||||
|
||||
$sql = $this->countString . $this->db->escapeIdentifiers('numrows') . ' FROM ' . $this->getFullName($table);
|
||||
|
||||
if ($this->testMode) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$query = $this->db->query($sql, null, false);
|
||||
if (empty($query->getResult())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = $query->getRow();
|
||||
|
||||
if ($reset === true) {
|
||||
$this->resetSelect();
|
||||
}
|
||||
|
||||
return (int) $query->numrows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete statement
|
||||
*/
|
||||
protected function _delete(string $table): string
|
||||
{
|
||||
return 'DELETE' . (empty($this->QBLimit) ? '' : ' TOP (' . $this->QBLimit . ') ') . ' FROM ' . $this->getFullName($table) . $this->compileWhereHaving('QBWhere');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a delete string and runs the query
|
||||
*
|
||||
* @param mixed $where
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function delete($where = '', ?int $limit = null, bool $resetData = true)
|
||||
{
|
||||
$table = $this->db->protectIdentifiers($this->QBFrom[0], true, null, false);
|
||||
|
||||
if ($where !== '') {
|
||||
$this->where($where);
|
||||
}
|
||||
|
||||
if (empty($this->QBWhere)) {
|
||||
if (CI_DEBUG) {
|
||||
throw new DatabaseException('Deletes are not allowed unless they contain a "where" or "like" clause.');
|
||||
}
|
||||
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if (! empty($limit)) {
|
||||
$this->QBLimit = $limit;
|
||||
}
|
||||
|
||||
$sql = $this->_delete($table);
|
||||
|
||||
if ($resetData) {
|
||||
$this->resetWrite();
|
||||
}
|
||||
|
||||
return $this->testMode ? $sql : $this->db->query($sql, $this->binds, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SELECT statement
|
||||
*
|
||||
* Generates a query string based on which functions were used.
|
||||
*
|
||||
* @param bool $selectOverride
|
||||
*/
|
||||
protected function compileSelect($selectOverride = false): string
|
||||
{
|
||||
// Write the "select" portion of the query
|
||||
if ($selectOverride !== false) {
|
||||
$sql = $selectOverride;
|
||||
} else {
|
||||
$sql = (! $this->QBDistinct) ? 'SELECT ' : 'SELECT DISTINCT ';
|
||||
|
||||
// SQL Server can't work with select * if group by is specified
|
||||
if (empty($this->QBSelect) && ! empty($this->QBGroupBy) && is_array($this->QBGroupBy)) {
|
||||
foreach ($this->QBGroupBy as $field) {
|
||||
$this->QBSelect[] = is_array($field) ? $field['field'] : $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->QBSelect)) {
|
||||
$sql .= '*';
|
||||
} else {
|
||||
// Cycle through the "select" portion of the query and prep each column name.
|
||||
// The reason we protect identifiers here rather than in the select() function
|
||||
// is because until the user calls the from() function we don't know if there are aliases
|
||||
foreach ($this->QBSelect as $key => $val) {
|
||||
$noEscape = $this->QBNoEscape[$key] ?? null;
|
||||
$this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $noEscape);
|
||||
}
|
||||
|
||||
$sql .= implode(', ', $this->QBSelect);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the "FROM" portion of the query
|
||||
if (! empty($this->QBFrom)) {
|
||||
$sql .= "\nFROM " . $this->_fromTables();
|
||||
}
|
||||
|
||||
// Write the "JOIN" portion of the query
|
||||
if (! empty($this->QBJoin)) {
|
||||
$sql .= "\n" . implode("\n", $this->QBJoin);
|
||||
}
|
||||
|
||||
$sql .= $this->compileWhereHaving('QBWhere')
|
||||
. $this->compileGroupBy()
|
||||
. $this->compileWhereHaving('QBHaving')
|
||||
. $this->compileOrderBy(); // ORDER BY
|
||||
|
||||
// LIMIT
|
||||
if ($this->QBLimit) {
|
||||
$sql = $this->_limit($sql . "\n");
|
||||
}
|
||||
|
||||
return $this->unionInjection($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the select statement based on the other functions called
|
||||
* and runs the query
|
||||
*
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function get(?int $limit = null, int $offset = 0, bool $reset = true)
|
||||
{
|
||||
if ($limit !== null) {
|
||||
$this->limit($limit, $offset);
|
||||
}
|
||||
|
||||
$result = $this->testMode ? $this->getCompiledSelect($reset) : $this->db->query($this->compileSelect(), $this->binds, false);
|
||||
|
||||
if ($reset) {
|
||||
$this->resetSelect();
|
||||
|
||||
// Clear our binds so we don't eat up memory
|
||||
$this->binds = [];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+547
@@ -0,0 +1,547 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use Exception;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Connection for SQLSRV
|
||||
*/
|
||||
class Connection extends BaseConnection
|
||||
{
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $DBDriver = 'SQLSRV';
|
||||
|
||||
/**
|
||||
* Database name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $database;
|
||||
|
||||
/**
|
||||
* Scrollable flag
|
||||
*
|
||||
* Determines what cursor type to use when executing queries.
|
||||
*
|
||||
* FALSE or SQLSRV_CURSOR_FORWARD would increase performance,
|
||||
* but would disable num_rows() (and possibly insert_id())
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $scrollable;
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $escapeChar = '"';
|
||||
|
||||
/**
|
||||
* Database schema
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $schema = 'dbo';
|
||||
|
||||
/**
|
||||
* Quoted identifier flag
|
||||
*
|
||||
* Whether to use SQL-92 standard quoted identifier
|
||||
* (double quotes) or brackets for identifier escaping.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_quoted_identifier = true;
|
||||
|
||||
/**
|
||||
* List of reserved identifiers
|
||||
*
|
||||
* Identifiers that must NOT be escaped.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $_reserved_identifiers = ['*'];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct(array $params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
// This is only supported as of SQLSRV 3.0
|
||||
if ($this->scrollable === null) {
|
||||
$this->scrollable = defined('SQLSRV_CURSOR_CLIENT_BUFFERED') ? SQLSRV_CURSOR_CLIENT_BUFFERED : false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the database.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function connect(bool $persistent = false)
|
||||
{
|
||||
$charset = in_array(strtolower($this->charset), ['utf-8', 'utf8'], true) ? 'UTF-8' : SQLSRV_ENC_CHAR;
|
||||
|
||||
$connection = [
|
||||
'UID' => empty($this->username) ? '' : $this->username,
|
||||
'PWD' => empty($this->password) ? '' : $this->password,
|
||||
'Database' => $this->database,
|
||||
'ConnectionPooling' => $persistent ? 1 : 0,
|
||||
'CharacterSet' => $charset,
|
||||
'Encrypt' => $this->encrypt === true ? 1 : 0,
|
||||
'ReturnDatesAsStrings' => 1,
|
||||
];
|
||||
|
||||
// If the username and password are both empty, assume this is a
|
||||
// 'Windows Authentication Mode' connection.
|
||||
if (empty($connection['UID']) && empty($connection['PWD'])) {
|
||||
unset($connection['UID'], $connection['PWD']);
|
||||
}
|
||||
|
||||
if (strpos($this->hostname, ',') === false && $this->port !== '') {
|
||||
$this->hostname .= ', ' . $this->port;
|
||||
}
|
||||
|
||||
sqlsrv_configure('WarningsReturnAsErrors', 0);
|
||||
$this->connID = sqlsrv_connect($this->hostname, $connection);
|
||||
|
||||
if ($this->connID !== false) {
|
||||
// Determine how identifiers are escaped
|
||||
$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$this->_quoted_identifier = empty($query) ? false : (bool) $query[0]->qi;
|
||||
$this->escapeChar = ($this->_quoted_identifier) ? '"' : ['[', ']'];
|
||||
|
||||
return $this->connID;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
|
||||
foreach (sqlsrv_errors(SQLSRV_ERR_ERRORS) as $error) {
|
||||
$errors[] = (string) preg_replace('/(\[.+\]\[.+\](?:\[.+\])?)(.+)/', '$2', $error['message']);
|
||||
}
|
||||
|
||||
throw new DatabaseException(implode("\n", $errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep or establish the connection if no queries have been sent for
|
||||
* a length of time exceeding the server's idle timeout.
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
$this->close();
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
sqlsrv_close($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-dependant string escape
|
||||
*/
|
||||
protected function _escapeString(string $str): string
|
||||
{
|
||||
return str_replace("'", "''", remove_invisible_characters($str, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*/
|
||||
public function insertID(): int
|
||||
{
|
||||
return $this->query('SELECT SCOPE_IDENTITY() AS insert_id')->getRow()->insert_id ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SQL for listing tables in a platform-dependent manner.
|
||||
*
|
||||
* @param string|null $tableName If $tableName is provided will return only this table if exists.
|
||||
*/
|
||||
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
|
||||
{
|
||||
$sql = 'SELECT [TABLE_NAME] AS "name"'
|
||||
. ' FROM [INFORMATION_SCHEMA].[TABLES] '
|
||||
. ' WHERE '
|
||||
. " [TABLE_SCHEMA] = '" . $this->schema . "' ";
|
||||
|
||||
if ($tableName !== null) {
|
||||
return $sql .= ' AND [TABLE_NAME] LIKE ' . $this->escape($tableName);
|
||||
}
|
||||
|
||||
if ($prefixLimit === true && $this->DBPrefix !== '') {
|
||||
$sql .= " AND [TABLE_NAME] LIKE '" . $this->escapeLikeString($this->DBPrefix) . "%' "
|
||||
. sprintf($this->likeEscapeStr, $this->likeEscapeChar);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific query string so that the column names can be fetched.
|
||||
*/
|
||||
protected function _listColumns(string $table = ''): string
|
||||
{
|
||||
return 'SELECT [COLUMN_NAME] '
|
||||
. ' FROM [INFORMATION_SCHEMA].[COLUMNS]'
|
||||
. ' WHERE [TABLE_NAME] = ' . $this->escape($this->DBPrefix . $table)
|
||||
. ' AND [TABLE_SCHEMA] = ' . $this->escape($this->schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with index data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _indexData(string $table): array
|
||||
{
|
||||
$sql = 'EXEC sp_helpindex ' . $this->escape($this->schema . '.' . $table);
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetIndexData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
|
||||
foreach ($query as $row) {
|
||||
$obj = new stdClass();
|
||||
$obj->name = $row->index_name;
|
||||
|
||||
$_fields = explode(',', trim($row->index_keys));
|
||||
$obj->fields = array_map(static fn ($v) => trim($v), $_fields);
|
||||
|
||||
if (strpos($row->index_description, 'primary key located on') !== false) {
|
||||
$obj->type = 'PRIMARY';
|
||||
} else {
|
||||
$obj->type = (strpos($row->index_description, 'nonclustered, unique') !== false) ? 'UNIQUE' : 'INDEX';
|
||||
}
|
||||
|
||||
$retVal[$obj->name] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with Foreign key data
|
||||
* referenced_object_id parent_object_id
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _foreignKeyData(string $table): array
|
||||
{
|
||||
$sql = 'SELECT '
|
||||
. 'f.name as constraint_name, '
|
||||
. 'OBJECT_NAME (f.parent_object_id) as table_name, '
|
||||
. 'COL_NAME(fc.parent_object_id,fc.parent_column_id) column_name, '
|
||||
. 'OBJECT_NAME(f.referenced_object_id) foreign_table_name, '
|
||||
. 'COL_NAME(fc.referenced_object_id,fc.referenced_column_id) foreign_column_name '
|
||||
. 'FROM '
|
||||
. 'sys.foreign_keys AS f '
|
||||
. 'INNER JOIN '
|
||||
. 'sys.foreign_key_columns AS fc '
|
||||
. 'ON f.OBJECT_ID = fc.constraint_object_id '
|
||||
. 'INNER JOIN '
|
||||
. 'sys.tables t '
|
||||
. 'ON t.OBJECT_ID = fc.referenced_object_id '
|
||||
. 'WHERE '
|
||||
. 'OBJECT_NAME (f.parent_object_id) = ' . $this->escape($table);
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetForeignKeyData'));
|
||||
}
|
||||
|
||||
$query = $query->getResultObject();
|
||||
$retVal = [];
|
||||
|
||||
foreach ($query as $row) {
|
||||
$obj = new stdClass();
|
||||
|
||||
$obj->constraint_name = $row->constraint_name;
|
||||
$obj->table_name = $row->table_name;
|
||||
$obj->column_name = $row->column_name;
|
||||
$obj->foreign_table_name = $row->foreign_table_name;
|
||||
$obj->foreign_column_name = $row->foreign_column_name;
|
||||
|
||||
$retVal[] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables foreign key checks temporarily.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _disableForeignKeyChecks()
|
||||
{
|
||||
return 'EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT ALL"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables foreign key checks temporarily.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _enableForeignKeyChecks()
|
||||
{
|
||||
return 'EXEC sp_MSforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with field data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _fieldData(string $table): array
|
||||
{
|
||||
$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME= ' . $this->escape(($table));
|
||||
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetFieldData'));
|
||||
}
|
||||
|
||||
$query = $query->getResultObject();
|
||||
$retVal = [];
|
||||
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++) {
|
||||
$retVal[$i] = new stdClass();
|
||||
|
||||
$retVal[$i]->name = $query[$i]->COLUMN_NAME;
|
||||
$retVal[$i]->type = $query[$i]->DATA_TYPE;
|
||||
$retVal[$i]->default = $query[$i]->COLUMN_DEFAULT;
|
||||
|
||||
$retVal[$i]->max_length = $query[$i]->CHARACTER_MAXIMUM_LENGTH > 0
|
||||
? $query[$i]->CHARACTER_MAXIMUM_LENGTH
|
||||
: $query[$i]->NUMERIC_PRECISION;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*/
|
||||
protected function _transBegin(): bool
|
||||
{
|
||||
return sqlsrv_begin_transaction($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*/
|
||||
protected function _transCommit(): bool
|
||||
{
|
||||
return sqlsrv_commit($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*/
|
||||
protected function _transRollback(): bool
|
||||
{
|
||||
return sqlsrv_rollback($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code and message.
|
||||
* Must return this format: ['code' => string|int, 'message' => string]
|
||||
* intval(code) === 0 means "no error".
|
||||
*
|
||||
* @return array<string, int|string>
|
||||
*/
|
||||
public function error(): array
|
||||
{
|
||||
$error = [
|
||||
'code' => '00000',
|
||||
'message' => '',
|
||||
];
|
||||
|
||||
$sqlsrvErrors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
|
||||
|
||||
if (! is_array($sqlsrvErrors)) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$sqlsrvError = array_shift($sqlsrvErrors);
|
||||
if (isset($sqlsrvError['SQLSTATE'])) {
|
||||
$error['code'] = isset($sqlsrvError['code']) ? $sqlsrvError['SQLSTATE'] . '/' . $sqlsrvError['code'] : $sqlsrvError['SQLSTATE'];
|
||||
} elseif (isset($sqlsrvError['code'])) {
|
||||
$error['code'] = $sqlsrvError['code'];
|
||||
}
|
||||
|
||||
if (isset($sqlsrvError['message'])) {
|
||||
$error['message'] = $sqlsrvError['message'];
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows affected by this query.
|
||||
*/
|
||||
public function affectedRows(): int
|
||||
{
|
||||
return sqlsrv_rows_affected($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific database table to use.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setDatabase(?string $databaseName = null)
|
||||
{
|
||||
if (empty($databaseName)) {
|
||||
$databaseName = $this->database;
|
||||
}
|
||||
|
||||
if (empty($this->connID)) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if ($this->execute('USE ' . $this->_escapeString($databaseName))) {
|
||||
$this->database = $databaseName;
|
||||
$this->dataCache = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query against the database.
|
||||
*
|
||||
* @return false|resource
|
||||
*/
|
||||
protected function execute(string $sql)
|
||||
{
|
||||
$stmt = ($this->scrollable === false || $this->isWriteType($sql)) ?
|
||||
sqlsrv_query($this->connID, $sql) :
|
||||
sqlsrv_query($this->connID, $sql, [], ['Scrollable' => $this->scrollable]);
|
||||
|
||||
if ($stmt === false) {
|
||||
$error = $this->error();
|
||||
|
||||
log_message('error', $error['message']);
|
||||
if ($this->DBDebug) {
|
||||
throw new Exception($error['message']);
|
||||
}
|
||||
}
|
||||
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error encountered by this connection.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated Use `error()` instead.
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
$error = [
|
||||
'code' => '00000',
|
||||
'message' => '',
|
||||
];
|
||||
|
||||
$sqlsrvErrors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
|
||||
|
||||
if (! is_array($sqlsrvErrors)) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$sqlsrvError = array_shift($sqlsrvErrors);
|
||||
if (isset($sqlsrvError['SQLSTATE'])) {
|
||||
$error['code'] = isset($sqlsrvError['code']) ? $sqlsrvError['SQLSTATE'] . '/' . $sqlsrvError['code'] : $sqlsrvError['SQLSTATE'];
|
||||
} elseif (isset($sqlsrvError['code'])) {
|
||||
$error['code'] = $sqlsrvError['code'];
|
||||
}
|
||||
|
||||
if (isset($sqlsrvError['message'])) {
|
||||
$error['message'] = $sqlsrvError['message'];
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the platform in use (MySQLi, mssql, etc)
|
||||
*/
|
||||
public function getPlatform(): string
|
||||
{
|
||||
return $this->DBDriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the version of the database being used.
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
if (isset($this->dataCache['version'])) {
|
||||
return $this->dataCache['version'];
|
||||
}
|
||||
|
||||
if (! $this->connID || empty($info = sqlsrv_server_info($this->connID))) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return isset($info['SQLServerVersion']) ? $this->dataCache['version'] = $info['SQLServerVersion'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a query is a "write" type.
|
||||
*
|
||||
* Overrides BaseConnection::isWriteType, adding additional read query types.
|
||||
*
|
||||
* @param mixed $sql
|
||||
*/
|
||||
public function isWriteType($sql): bool
|
||||
{
|
||||
if (preg_match('/^\s*"?(EXEC\s*sp_rename)\s/i', $sql)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::isWriteType($sql);
|
||||
}
|
||||
}
|
||||
Executable
+396
@@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Forge as BaseForge;
|
||||
|
||||
/**
|
||||
* Forge for SQLSRV
|
||||
*/
|
||||
class Forge extends BaseForge
|
||||
{
|
||||
/**
|
||||
* DROP CONSTRAINT statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropConstraintStr;
|
||||
|
||||
/**
|
||||
* DROP INDEX statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropIndexStr;
|
||||
|
||||
/**
|
||||
* CREATE DATABASE IF statement
|
||||
*
|
||||
* @todo missing charset, collat & check for existent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createDatabaseIfStr = "DECLARE @DBName VARCHAR(255) = '%s'\nDECLARE @SQL VARCHAR(max) = 'IF DB_ID( ''' + @DBName + ''' ) IS NULL CREATE DATABASE ' + @DBName\nEXEC( @SQL )";
|
||||
|
||||
/**
|
||||
* CREATE DATABASE IF statement
|
||||
*
|
||||
* @todo missing charset & collat
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createDatabaseStr = 'CREATE DATABASE %s ';
|
||||
|
||||
/**
|
||||
* CHECK DATABASE EXIST statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $checkDatabaseExistStr = 'IF DB_ID( %s ) IS NOT NULL SELECT 1';
|
||||
|
||||
/**
|
||||
* RENAME TABLE statement
|
||||
*
|
||||
* While the below statement would work, it returns an error.
|
||||
* Also MS recommends dropping and dropping and re-creating the table.
|
||||
*
|
||||
* @see https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-rename-transact-sql?view=sql-server-2017
|
||||
* 'EXEC sp_rename %s , %s ;'
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $renameTableStr;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $unsigned = [
|
||||
'TINYINT' => 'SMALLINT',
|
||||
'SMALLINT' => 'INT',
|
||||
'INT' => 'BIGINT',
|
||||
'REAL' => 'FLOAT',
|
||||
];
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @deprecated This is no longer used.
|
||||
*/
|
||||
protected $createTableIfStr;
|
||||
|
||||
/**
|
||||
* CREATE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createTableStr;
|
||||
|
||||
public function __construct(BaseConnection $db)
|
||||
{
|
||||
parent::__construct($db);
|
||||
|
||||
$this->createTableStr = '%s ' . $this->db->escapeIdentifiers($this->db->schema) . ".%s (%s\n) ";
|
||||
$this->renameTableStr = 'EXEC sp_rename [' . $this->db->escapeIdentifiers($this->db->schema) . '.%s] , %s ;';
|
||||
|
||||
$this->dropConstraintStr = 'ALTER TABLE ' . $this->db->escapeIdentifiers($this->db->schema) . '.%s DROP CONSTRAINT %s';
|
||||
$this->dropIndexStr = 'DROP INDEX %s ON ' . $this->db->escapeIdentifiers($this->db->schema) . '.%s';
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE attributes
|
||||
*/
|
||||
protected function _createTableAttributes(array $attributes): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $field
|
||||
*
|
||||
* @return false|string|string[]
|
||||
*/
|
||||
protected function _alterTable(string $alterType, string $table, $field)
|
||||
{
|
||||
// Handle DROP here
|
||||
if ($alterType === 'DROP') {
|
||||
// check if fields are part of any indexes
|
||||
$indexData = $this->db->getIndexData($table);
|
||||
|
||||
foreach ($indexData as $index) {
|
||||
if (is_string($field)) {
|
||||
$field = explode(',', $field);
|
||||
}
|
||||
|
||||
$fld = array_intersect($field, $index->fields);
|
||||
|
||||
// Drop index if field is part of an index
|
||||
if (! empty($fld)) {
|
||||
$this->_dropIndex($table, $index);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table) . ' DROP ';
|
||||
|
||||
$fields = array_map(static fn ($item) => 'COLUMN [' . trim($item) . ']', (array) $field);
|
||||
|
||||
return $sql . implode(',', $fields);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table);
|
||||
$sql .= ($alterType === 'ADD') ? 'ADD ' : ' ';
|
||||
|
||||
$sqls = [];
|
||||
|
||||
if ($alterType === 'ADD') {
|
||||
foreach ($field as $data) {
|
||||
$sqls[] = $sql . ($data['_literal'] !== false ? $data['_literal'] : $this->_processColumn($data));
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
foreach ($field as $data) {
|
||||
if ($data['_literal'] !== false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($data['type'])) {
|
||||
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
|
||||
. " {$data['type']}{$data['length']}";
|
||||
}
|
||||
|
||||
if (! empty($data['default'])) {
|
||||
$sqls[] = $sql . ' ALTER COLUMN ADD CONSTRAINT ' . $this->db->escapeIdentifiers($data['name']) . '_def'
|
||||
. " DEFAULT {$data['default']} FOR " . $this->db->escapeIdentifiers($data['name']);
|
||||
}
|
||||
|
||||
if (isset($data['null'])) {
|
||||
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
|
||||
. ($data['null'] === true ? ' DROP' : '') . " {$data['type']}{$data['length']} NOT NULL";
|
||||
}
|
||||
|
||||
if (! empty($data['comment'])) {
|
||||
$sqls[] = 'EXEC sys.sp_addextendedproperty '
|
||||
. "@name=N'Caption', @value=N'" . $data['comment'] . "' , "
|
||||
. "@level0type=N'SCHEMA',@level0name=N'" . $this->db->schema . "', "
|
||||
. "@level1type=N'TABLE',@level1name=N'" . $this->db->escapeIdentifiers($table) . "', "
|
||||
. "@level2type=N'COLUMN',@level2name=N'" . $this->db->escapeIdentifiers($data['name']) . "'";
|
||||
}
|
||||
|
||||
if (! empty($data['new_name'])) {
|
||||
$sqls[] = "EXEC sp_rename '[" . $this->db->schema . '].[' . $table . '].[' . $data['name'] . "]' , '" . $data['new_name'] . "', 'COLUMN';";
|
||||
}
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop index for table
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _dropIndex(string $table, object $indexData)
|
||||
{
|
||||
if ($indexData->type === 'PRIMARY') {
|
||||
$sql = 'ALTER TABLE [' . $this->db->schema . '].[' . $table . '] DROP [' . $indexData->name . ']';
|
||||
} else {
|
||||
$sql = 'DROP INDEX [' . $indexData->name . '] ON [' . $this->db->schema . '].[' . $table . ']';
|
||||
}
|
||||
|
||||
return $this->db->simpleQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process indexes
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
protected function _processIndexes(string $table)
|
||||
{
|
||||
$sqls = [];
|
||||
|
||||
for ($i = 0, $c = count($this->keys); $i < $c; $i++) {
|
||||
$this->keys[$i] = (array) $this->keys[$i];
|
||||
|
||||
for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) {
|
||||
if (! isset($this->fields[$this->keys[$i][$i2]])) {
|
||||
unset($this->keys[$i][$i2]);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($this->keys[$i]) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($i, $this->uniqueKeys, true)) {
|
||||
$sqls[] = 'ALTER TABLE '
|
||||
. $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table)
|
||||
. ' ADD CONSTRAINT ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
|
||||
. ' UNIQUE (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqls[] = 'CREATE INDEX '
|
||||
. $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
|
||||
. ' ON ' . $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table)
|
||||
. ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*/
|
||||
protected function _processColumn(array $field): string
|
||||
{
|
||||
return $this->db->escapeIdentifiers($field['name'])
|
||||
. (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name']))
|
||||
. ' ' . $field['type'] . $field['length']
|
||||
. $field['default']
|
||||
. $field['null']
|
||||
. $field['auto_increment']
|
||||
. ''
|
||||
. $field['unique'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process foreign keys
|
||||
*
|
||||
* @param string $table Table name
|
||||
*/
|
||||
protected function _processForeignKeys(string $table): string
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
$allowActions = ['CASCADE', 'SET NULL', 'NO ACTION', 'RESTRICT', 'SET DEFAULT'];
|
||||
|
||||
foreach ($this->foreignKeys as $fkey) {
|
||||
$nameIndex = $table . '_' . implode('_', $fkey['field']) . '_foreign';
|
||||
$nameIndexFilled = $this->db->escapeIdentifiers($nameIndex);
|
||||
$foreignKeyFilled = implode(', ', $this->db->escapeIdentifiers($fkey['field']));
|
||||
$referenceTableFilled = $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['referenceTable']);
|
||||
$referenceFieldFilled = implode(', ', $this->db->escapeIdentifiers($fkey['referenceField']));
|
||||
|
||||
$formatSql = ",\n\tCONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s)";
|
||||
$sql .= sprintf($formatSql, $nameIndexFilled, $foreignKeyFilled, $referenceTableFilled, $referenceFieldFilled);
|
||||
|
||||
if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true)) {
|
||||
$sql .= ' ON DELETE ' . $fkey['onDelete'];
|
||||
}
|
||||
|
||||
if ($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions, true)) {
|
||||
$sql .= ' ON UPDATE ' . $fkey['onUpdate'];
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process primary keys
|
||||
*/
|
||||
protected function _processPrimaryKeys(string $table): string
|
||||
{
|
||||
for ($i = 0, $c = count($this->primaryKeys); $i < $c; $i++) {
|
||||
if (! isset($this->fields[$this->primaryKeys[$i]])) {
|
||||
unset($this->primaryKeys[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->primaryKeys !== []) {
|
||||
$sql = ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers('pk_' . $table)
|
||||
. ' PRIMARY KEY(' . implode(', ', $this->db->escapeIdentifiers($this->primaryKeys)) . ')';
|
||||
}
|
||||
|
||||
return $sql ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a data type mapping between different databases.
|
||||
*/
|
||||
protected function _attributeType(array &$attributes)
|
||||
{
|
||||
// Reset field lengths for data types that don't support it
|
||||
if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== false) {
|
||||
$attributes['CONSTRAINT'] = null;
|
||||
}
|
||||
|
||||
switch (strtoupper($attributes['TYPE'])) {
|
||||
case 'MEDIUMINT':
|
||||
$attributes['TYPE'] = 'INTEGER';
|
||||
$attributes['UNSIGNED'] = false;
|
||||
break;
|
||||
|
||||
case 'INTEGER':
|
||||
$attributes['TYPE'] = 'INT';
|
||||
break;
|
||||
|
||||
case 'ENUM':
|
||||
$attributes['TYPE'] = 'TEXT';
|
||||
$attributes['CONSTRAINT'] = null;
|
||||
break;
|
||||
|
||||
case 'TIMESTAMP':
|
||||
$attributes['TYPE'] = 'DATETIME';
|
||||
break;
|
||||
|
||||
case 'BOOLEAN':
|
||||
$attributes['TYPE'] = 'BIT';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*/
|
||||
protected function _attributeAutoIncrement(array &$attributes, array &$field)
|
||||
{
|
||||
if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true && stripos($field['type'], 'INT') !== false) {
|
||||
$field['auto_increment'] = ' IDENTITY(1,1)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific DROP TABLE string
|
||||
*
|
||||
* @todo Support for cascade
|
||||
*/
|
||||
protected function _dropTable(string $table, bool $ifExists, bool $cascade): string
|
||||
{
|
||||
$sql = 'DROP TABLE';
|
||||
|
||||
if ($ifExists) {
|
||||
$sql .= ' IF EXISTS ';
|
||||
}
|
||||
|
||||
$table = ' [' . $this->db->database . '].[' . $this->db->schema . '].[' . $table . '] ';
|
||||
|
||||
$sql .= $table;
|
||||
|
||||
if ($cascade) {
|
||||
$sql .= '';
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use BadMethodCallException;
|
||||
use CodeIgniter\Database\BasePreparedQuery;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Prepared query for Postgre
|
||||
*/
|
||||
class PreparedQuery extends BasePreparedQuery
|
||||
{
|
||||
/**
|
||||
* Parameters array used to store the dynamic variables.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parameters = [];
|
||||
|
||||
/**
|
||||
* Prepares the query against the database, and saves the connection
|
||||
* info necessary to execute the query later.
|
||||
*
|
||||
* NOTE: This version is based on SQL code. Child classes should
|
||||
* override this method.
|
||||
*
|
||||
* @param array $options Options takes an associative array;
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _prepare(string $sql, array $options = [])
|
||||
{
|
||||
// Prepare parameters for the query
|
||||
$queryString = $this->getQueryString();
|
||||
|
||||
$parameters = $this->parameterize($queryString);
|
||||
|
||||
// Prepare the query
|
||||
$this->statement = sqlsrv_prepare($this->db->connID, $sql, $parameters);
|
||||
|
||||
if (! $this->statement) {
|
||||
$info = $this->db->error();
|
||||
$this->errorCode = $info['code'];
|
||||
$this->errorString = $info['message'];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a new set of data and runs it against the currently
|
||||
* prepared query.
|
||||
*/
|
||||
public function _execute(array $data): bool
|
||||
{
|
||||
if (! isset($this->statement)) {
|
||||
throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
|
||||
}
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$this->parameters[$key] = $value;
|
||||
}
|
||||
|
||||
return sqlsrv_execute($this->statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the statement resource for the prepared query or false when preparing failed.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _getResult()
|
||||
{
|
||||
return $this->statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle parameters
|
||||
*/
|
||||
protected function parameterize(string $queryString): array
|
||||
{
|
||||
$numberOfVariables = substr_count($queryString, '?');
|
||||
|
||||
$params = [];
|
||||
|
||||
for ($c = 0; $c < $numberOfVariables; $c++) {
|
||||
$this->parameters[$c] = null;
|
||||
$params[] = &$this->parameters[$c];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
Executable
+170
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Result for SQLSRV
|
||||
*/
|
||||
class Result extends BaseResult
|
||||
{
|
||||
/**
|
||||
* Gets the number of fields in the result set.
|
||||
*/
|
||||
public function getFieldCount(): int
|
||||
{
|
||||
return @sqlsrv_num_fields($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of column names in the result set.
|
||||
*/
|
||||
public function getFieldNames(): array
|
||||
{
|
||||
$fieldNames = [];
|
||||
|
||||
foreach (sqlsrv_field_metadata($this->resultID) as $field) {
|
||||
$fieldNames[] = $field['Name'];
|
||||
}
|
||||
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of objects representing field meta-data.
|
||||
*/
|
||||
public function getFieldData(): array
|
||||
{
|
||||
static $dataTypes = [
|
||||
SQLSRV_SQLTYPE_BIGINT => 'bigint',
|
||||
SQLSRV_SQLTYPE_BIT => 'bit',
|
||||
SQLSRV_SQLTYPE_CHAR => 'char',
|
||||
|
||||
SQLSRV_SQLTYPE_DATE => 'date',
|
||||
SQLSRV_SQLTYPE_DATETIME => 'datetime',
|
||||
SQLSRV_SQLTYPE_DATETIME2 => 'datetime2',
|
||||
SQLSRV_SQLTYPE_DATETIMEOFFSET => 'datetimeoffset',
|
||||
|
||||
SQLSRV_SQLTYPE_DECIMAL => 'decimal',
|
||||
SQLSRV_SQLTYPE_FLOAT => 'float',
|
||||
|
||||
SQLSRV_SQLTYPE_IMAGE => 'image',
|
||||
SQLSRV_SQLTYPE_INT => 'int',
|
||||
SQLSRV_SQLTYPE_MONEY => 'money',
|
||||
SQLSRV_SQLTYPE_NCHAR => 'nchar',
|
||||
SQLSRV_SQLTYPE_NUMERIC => 'numeric',
|
||||
|
||||
SQLSRV_SQLTYPE_NVARCHAR => 'nvarchar',
|
||||
SQLSRV_SQLTYPE_NTEXT => 'ntext',
|
||||
|
||||
SQLSRV_SQLTYPE_REAL => 'real',
|
||||
SQLSRV_SQLTYPE_SMALLDATETIME => 'smalldatetime',
|
||||
SQLSRV_SQLTYPE_SMALLINT => 'smallint',
|
||||
SQLSRV_SQLTYPE_SMALLMONEY => 'smallmoney',
|
||||
SQLSRV_SQLTYPE_TEXT => 'text',
|
||||
|
||||
SQLSRV_SQLTYPE_TIME => 'time',
|
||||
SQLSRV_SQLTYPE_TIMESTAMP => 'timestamp',
|
||||
SQLSRV_SQLTYPE_TINYINT => 'tinyint',
|
||||
SQLSRV_SQLTYPE_UNIQUEIDENTIFIER => 'uniqueidentifier',
|
||||
SQLSRV_SQLTYPE_UDT => 'udt',
|
||||
SQLSRV_SQLTYPE_VARBINARY => 'varbinary',
|
||||
SQLSRV_SQLTYPE_VARCHAR => 'varchar',
|
||||
SQLSRV_SQLTYPE_XML => 'xml',
|
||||
];
|
||||
|
||||
$retVal = [];
|
||||
|
||||
foreach (sqlsrv_field_metadata($this->resultID) as $i => $field) {
|
||||
$retVal[$i] = new stdClass();
|
||||
|
||||
$retVal[$i]->name = $field['Name'];
|
||||
$retVal[$i]->type = $field['Type'];
|
||||
$retVal[$i]->type_name = $dataTypes[$field['Type']] ?? null;
|
||||
$retVal[$i]->max_length = $field['Size'];
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the current result.
|
||||
*/
|
||||
public function freeResult()
|
||||
{
|
||||
if (is_resource($this->resultID)) {
|
||||
sqlsrv_free_stmt($this->resultID);
|
||||
$this->resultID = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal pointer to the desired offset. This is called
|
||||
* internally before fetching results to make sure the result set
|
||||
* starts at zero.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function dataSeek(int $n = 0)
|
||||
{
|
||||
if ($n > 0) {
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
if (sqlsrv_fetch($this->resultID) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an array.
|
||||
*
|
||||
* Overridden by driver classes.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetchAssoc()
|
||||
{
|
||||
return sqlsrv_fetch_array($this->resultID, SQLSRV_FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an object.
|
||||
*
|
||||
* @return bool|Entity|object
|
||||
*/
|
||||
protected function fetchObject(string $className = 'stdClass')
|
||||
{
|
||||
if (is_subclass_of($className, Entity::class)) {
|
||||
return empty($data = $this->fetchAssoc()) ? false : (new $className())->setAttributes($data);
|
||||
}
|
||||
|
||||
return sqlsrv_fetch_object($this->resultID, $className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of rows in the resultID (i.e., SQLSRV query result resource)
|
||||
*/
|
||||
public function getNumRows(): int
|
||||
{
|
||||
if (! is_int($this->numRows)) {
|
||||
$this->numRows = sqlsrv_num_rows($this->resultID);
|
||||
}
|
||||
|
||||
return $this->numRows;
|
||||
}
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\BaseUtils;
|
||||
use CodeIgniter\Database\ConnectionInterface;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Utils for SQLSRV
|
||||
*/
|
||||
class Utils extends BaseUtils
|
||||
{
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $listDatabases = 'EXEC sp_helpdb'; // Can also be: EXEC sp_databases
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $optimizeTable = 'ALTER INDEX all ON %s REORGANIZE';
|
||||
|
||||
public function __construct(ConnectionInterface $db)
|
||||
{
|
||||
parent::__construct($db);
|
||||
|
||||
$this->optimizeTable = 'ALTER INDEX all ON ' . $this->db->schema . '.%s REORGANIZE';
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform dependent version of the backup function.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _backup(?array $prefs = null)
|
||||
{
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLite3;
|
||||
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
|
||||
/**
|
||||
* Builder for SQLite3
|
||||
*/
|
||||
class Builder extends BaseBuilder
|
||||
{
|
||||
/**
|
||||
* Default installs of SQLite typically do not
|
||||
* support limiting delete clauses.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $canLimitDeletes = false;
|
||||
|
||||
/**
|
||||
* Default installs of SQLite do no support
|
||||
* limiting update queries in combo with WHERE.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $canLimitWhereUpdates = false;
|
||||
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $randomKeyword = [
|
||||
'RANDOM()',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $supportedIgnoreStatements = [
|
||||
'insert' => 'OR IGNORE',
|
||||
];
|
||||
|
||||
/**
|
||||
* Replace statement
|
||||
*
|
||||
* Generates a platform-specific replace string from the supplied data
|
||||
*/
|
||||
protected function _replace(string $table, array $keys, array $values): string
|
||||
{
|
||||
return 'INSERT OR ' . parent::_replace($table, $keys, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the TRUNCATE statement,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*/
|
||||
protected function _truncate(string $table): string
|
||||
{
|
||||
return 'DELETE FROM ' . $table;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLite3;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use SQLite3;
|
||||
use SQLite3Result;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Connection for SQLite3
|
||||
*/
|
||||
class Connection extends BaseConnection
|
||||
{
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $DBDriver = 'SQLite3';
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $escapeChar = '`';
|
||||
|
||||
/**
|
||||
* @var bool Enable Foreign Key constraint or not
|
||||
*/
|
||||
protected $foreignKeys = false;
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
if ($this->foreignKeys) {
|
||||
$this->enableForeignKeyChecks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the database.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function connect(bool $persistent = false)
|
||||
{
|
||||
if ($persistent && $this->DBDebug) {
|
||||
throw new DatabaseException('SQLite3 doesn\'t support persistent connections.');
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->database !== ':memory:' && strpos($this->database, DIRECTORY_SEPARATOR) === false) {
|
||||
$this->database = WRITEPATH . $this->database;
|
||||
}
|
||||
|
||||
return (! $this->password)
|
||||
? new SQLite3($this->database)
|
||||
: new SQLite3($this->database, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $this->password);
|
||||
} catch (Exception $e) {
|
||||
throw new DatabaseException('SQLite3 error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep or establish the connection if no queries have been sent for
|
||||
* a length of time exceeding the server's idle timeout.
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
$this->close();
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
$this->connID->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific database table to use.
|
||||
*/
|
||||
public function setDatabase(string $databaseName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the version of the database being used.
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
if (isset($this->dataCache['version'])) {
|
||||
return $this->dataCache['version'];
|
||||
}
|
||||
|
||||
$version = SQLite3::version();
|
||||
|
||||
return $this->dataCache['version'] = $version['versionString'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @return bool|SQLite3Result
|
||||
*/
|
||||
protected function execute(string $sql)
|
||||
{
|
||||
try {
|
||||
return $this->isWriteType($sql)
|
||||
? $this->connID->exec($sql)
|
||||
: $this->connID->query($sql);
|
||||
} catch (ErrorException $e) {
|
||||
log_message('error', (string) $e);
|
||||
if ($this->DBDebug) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows affected by this query.
|
||||
*/
|
||||
public function affectedRows(): int
|
||||
{
|
||||
return $this->connID->changes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-dependant string escape
|
||||
*/
|
||||
protected function _escapeString(string $str): string
|
||||
{
|
||||
if (! $this->connID) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return $this->connID->escapeString($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SQL for listing tables in a platform-dependent manner.
|
||||
*
|
||||
* @param string|null $tableName If $tableName is provided will return only this table if exists.
|
||||
*/
|
||||
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
|
||||
{
|
||||
if ($tableName !== null) {
|
||||
return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
|
||||
. ' AND "NAME" NOT LIKE \'sqlite!_%\' ESCAPE \'!\''
|
||||
. ' AND "NAME" LIKE ' . $this->escape($tableName);
|
||||
}
|
||||
|
||||
return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
|
||||
. ' AND "NAME" NOT LIKE \'sqlite!_%\' ESCAPE \'!\''
|
||||
. (($prefixLimit !== false && $this->DBPrefix !== '')
|
||||
? ' AND "NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . '%\' ' . sprintf($this->likeEscapeStr, $this->likeEscapeChar)
|
||||
: '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific query string so that the column names can be fetched.
|
||||
*/
|
||||
protected function _listColumns(string $table = ''): string
|
||||
{
|
||||
return 'PRAGMA TABLE_INFO(' . $this->protectIdentifiers($table, true, null, false) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|false
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function getFieldNames(string $table)
|
||||
{
|
||||
// Is there a cached result?
|
||||
if (isset($this->dataCache['field_names'][$table])) {
|
||||
return $this->dataCache['field_names'][$table];
|
||||
}
|
||||
|
||||
if (empty($this->connID)) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
$sql = $this->_listColumns($table);
|
||||
|
||||
$query = $this->query($sql);
|
||||
$this->dataCache['field_names'][$table] = [];
|
||||
|
||||
foreach ($query->getResultArray() as $row) {
|
||||
// Do we know from where to get the column's name?
|
||||
if (! isset($key)) {
|
||||
if (isset($row['column_name'])) {
|
||||
$key = 'column_name';
|
||||
} elseif (isset($row['COLUMN_NAME'])) {
|
||||
$key = 'COLUMN_NAME';
|
||||
} elseif (isset($row['name'])) {
|
||||
$key = 'name';
|
||||
} else {
|
||||
// We have no other choice but to just get the first element's key.
|
||||
$key = key($row);
|
||||
}
|
||||
}
|
||||
|
||||
$this->dataCache['field_names'][$table][] = $row[$key];
|
||||
}
|
||||
|
||||
return $this->dataCache['field_names'][$table];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with field data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _fieldData(string $table): array
|
||||
{
|
||||
if (false === $query = $this->query('PRAGMA TABLE_INFO(' . $this->protectIdentifiers($table, true, null, false) . ')')) {
|
||||
throw new DatabaseException(lang('Database.failGetFieldData'));
|
||||
}
|
||||
|
||||
$query = $query->getResultObject();
|
||||
|
||||
if (empty($query)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$retVal = [];
|
||||
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++) {
|
||||
$retVal[$i] = new stdClass();
|
||||
|
||||
$retVal[$i]->name = $query[$i]->name;
|
||||
$retVal[$i]->type = $query[$i]->type;
|
||||
$retVal[$i]->max_length = null;
|
||||
$retVal[$i]->default = $query[$i]->dflt_value;
|
||||
$retVal[$i]->primary_key = isset($query[$i]->pk) && (bool) $query[$i]->pk;
|
||||
$retVal[$i]->nullable = isset($query[$i]->notnull) && ! (bool) $query[$i]->notnull;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with index data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function _indexData(string $table): array
|
||||
{
|
||||
// Get indexes
|
||||
// Don't use PRAGMA index_list, so we can preserve index order
|
||||
$sql = "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name=" . $this->escape(strtolower($table));
|
||||
if (($query = $this->query($sql)) === false) {
|
||||
throw new DatabaseException(lang('Database.failGetIndexData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
|
||||
foreach ($query as $row) {
|
||||
$obj = new stdClass();
|
||||
|
||||
$obj->name = $row->name;
|
||||
|
||||
// Get fields for index
|
||||
$obj->fields = [];
|
||||
|
||||
if (false === $fields = $this->query('PRAGMA index_info(' . $this->escape(strtolower($row->name)) . ')')) {
|
||||
throw new DatabaseException(lang('Database.failGetIndexData'));
|
||||
}
|
||||
|
||||
$fields = $fields->getResultObject();
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$obj->fields[] = $field->name;
|
||||
}
|
||||
|
||||
$retVal[$obj->name] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with Foreign key data
|
||||
*
|
||||
* @return stdClass[]
|
||||
*/
|
||||
protected function _foreignKeyData(string $table): array
|
||||
{
|
||||
if ($this->supportsForeignKeys() !== true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tables = $this->listTables();
|
||||
|
||||
if (empty($tables)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$retVal = [];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$query = $this->query("PRAGMA foreign_key_list({$table})")->getResult();
|
||||
|
||||
foreach ($query as $row) {
|
||||
$obj = new stdClass();
|
||||
$obj->constraint_name = $row->from . ' to ' . $row->table . '.' . $row->to;
|
||||
$obj->table_name = $table;
|
||||
$obj->foreign_table_name = $row->table;
|
||||
$obj->sequence = $row->seq;
|
||||
|
||||
$retVal[] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform-specific SQL to disable foreign key checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _disableForeignKeyChecks()
|
||||
{
|
||||
return 'PRAGMA foreign_keys = OFF';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform-specific SQL to enable foreign key checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _enableForeignKeyChecks()
|
||||
{
|
||||
return 'PRAGMA foreign_keys = ON';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code and message.
|
||||
* Must return this format: ['code' => string|int, 'message' => string]
|
||||
* intval(code) === 0 means "no error".
|
||||
*
|
||||
* @return array<string, int|string>
|
||||
*/
|
||||
public function error(): array
|
||||
{
|
||||
return [
|
||||
'code' => $this->connID->lastErrorCode(),
|
||||
'message' => $this->connID->lastErrorMsg(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*/
|
||||
public function insertID(): int
|
||||
{
|
||||
return $this->connID->lastInsertRowID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*/
|
||||
protected function _transBegin(): bool
|
||||
{
|
||||
return $this->connID->exec('BEGIN TRANSACTION');
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*/
|
||||
protected function _transCommit(): bool
|
||||
{
|
||||
return $this->connID->exec('END TRANSACTION');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*/
|
||||
protected function _transRollback(): bool
|
||||
{
|
||||
return $this->connID->exec('ROLLBACK');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the current install supports Foreign Keys
|
||||
* and has them enabled.
|
||||
*/
|
||||
public function supportsForeignKeys(): bool
|
||||
{
|
||||
$result = $this->simpleQuery('PRAGMA foreign_keys');
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLite3;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Database\Forge as BaseForge;
|
||||
|
||||
/**
|
||||
* Forge for SQLite3
|
||||
*/
|
||||
class Forge extends BaseForge
|
||||
{
|
||||
/**
|
||||
* DROP INDEX statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropIndexStr = 'DROP INDEX %s';
|
||||
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array|bool
|
||||
*/
|
||||
protected $_unsigned = false;
|
||||
|
||||
/**
|
||||
* NULL value representation in CREATE/ALTER TABLE statements
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected $null = 'NULL';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(BaseConnection $db)
|
||||
{
|
||||
parent::__construct($db);
|
||||
|
||||
if (version_compare($this->db->getVersion(), '3.3', '<')) {
|
||||
$this->dropTableIfStr = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create database
|
||||
*
|
||||
* @param bool $ifNotExists Whether to add IF NOT EXISTS condition
|
||||
*/
|
||||
public function createDatabase(string $dbName, bool $ifNotExists = false): bool
|
||||
{
|
||||
// In SQLite, a database is created when you connect to the database.
|
||||
// We'll return TRUE so that an error isn't generated.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop database
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function dropDatabase(string $dbName): bool
|
||||
{
|
||||
// In SQLite, a database is dropped when we delete a file
|
||||
if (! is_file($dbName)) {
|
||||
if ($this->db->DBDebug) {
|
||||
throw new DatabaseException('Unable to drop the specified database.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// We need to close the pseudo-connection first
|
||||
$this->db->close();
|
||||
if (! @unlink($dbName)) {
|
||||
if ($this->db->DBDebug) {
|
||||
throw new DatabaseException('Unable to drop the specified database.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! empty($this->db->dataCache['db_names'])) {
|
||||
$key = array_search(strtolower($dbName), array_map('strtolower', $this->db->dataCache['db_names']), true);
|
||||
if ($key !== false) {
|
||||
unset($this->db->dataCache['db_names'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $field
|
||||
*
|
||||
* @return array|string|null
|
||||
*/
|
||||
protected function _alterTable(string $alterType, string $table, $field)
|
||||
{
|
||||
switch ($alterType) {
|
||||
case 'DROP':
|
||||
$sqlTable = new Table($this->db, $this);
|
||||
|
||||
$sqlTable->fromTable($table)
|
||||
->dropColumn($field)
|
||||
->run();
|
||||
|
||||
return '';
|
||||
|
||||
case 'CHANGE':
|
||||
(new Table($this->db, $this))
|
||||
->fromTable($table)
|
||||
->modifyColumn($field)
|
||||
->run();
|
||||
|
||||
return null;
|
||||
|
||||
default:
|
||||
return parent::_alterTable($alterType, $table, $field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*/
|
||||
protected function _processColumn(array $field): string
|
||||
{
|
||||
if ($field['type'] === 'TEXT' && strpos($field['length'], "('") === 0) {
|
||||
$field['type'] .= ' CHECK(' . $this->db->escapeIdentifiers($field['name'])
|
||||
. ' IN ' . $field['length'] . ')';
|
||||
}
|
||||
|
||||
return $this->db->escapeIdentifiers($field['name'])
|
||||
. ' ' . $field['type']
|
||||
. $field['auto_increment']
|
||||
. $field['null']
|
||||
. $field['unique']
|
||||
. $field['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process indexes
|
||||
*/
|
||||
protected function _processIndexes(string $table): array
|
||||
{
|
||||
$sqls = [];
|
||||
|
||||
for ($i = 0, $c = count($this->keys); $i < $c; $i++) {
|
||||
$this->keys[$i] = (array) $this->keys[$i];
|
||||
|
||||
for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) {
|
||||
if (! isset($this->fields[$this->keys[$i][$i2]])) {
|
||||
unset($this->keys[$i][$i2]);
|
||||
}
|
||||
}
|
||||
if (count($this->keys[$i]) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($i, $this->uniqueKeys, true)) {
|
||||
$sqls[] = 'CREATE UNIQUE INDEX ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
|
||||
. ' ON ' . $this->db->escapeIdentifiers($table)
|
||||
. ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqls[] = 'CREATE INDEX ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
|
||||
. ' ON ' . $this->db->escapeIdentifiers($table)
|
||||
. ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Field attribute TYPE
|
||||
*
|
||||
* Performs a data type mapping between different databases.
|
||||
*/
|
||||
protected function _attributeType(array &$attributes)
|
||||
{
|
||||
switch (strtoupper($attributes['TYPE'])) {
|
||||
case 'ENUM':
|
||||
case 'SET':
|
||||
$attributes['TYPE'] = 'TEXT';
|
||||
break;
|
||||
|
||||
case 'BOOLEAN':
|
||||
$attributes['TYPE'] = 'INT';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*/
|
||||
protected function _attributeAutoIncrement(array &$attributes, array &$field)
|
||||
{
|
||||
if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true
|
||||
&& stripos($field['type'], 'int') !== false) {
|
||||
$field['type'] = 'INTEGER PRIMARY KEY';
|
||||
$field['default'] = '';
|
||||
$field['null'] = '';
|
||||
$field['unique'] = '';
|
||||
$field['auto_increment'] = ' AUTOINCREMENT';
|
||||
|
||||
$this->primaryKeys = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Foreign Key Drop
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function dropForeignKey(string $table, string $foreignName): bool
|
||||
{
|
||||
// If this version of SQLite doesn't support it, we're done here
|
||||
if ($this->db->supportsForeignKeys() !== true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise we have to copy the table and recreate
|
||||
// without the foreign key being involved now
|
||||
$sqlTable = new Table($this->db, $this);
|
||||
|
||||
return $sqlTable->fromTable($this->db->DBPrefix . $table)
|
||||
->dropForeignKey($foreignName)
|
||||
->run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLite3;
|
||||
|
||||
use BadMethodCallException;
|
||||
use CodeIgniter\Database\BasePreparedQuery;
|
||||
|
||||
/**
|
||||
* Prepared query for SQLite3
|
||||
*/
|
||||
class PreparedQuery extends BasePreparedQuery
|
||||
{
|
||||
/**
|
||||
* The SQLite3Result resource, or false.
|
||||
*
|
||||
* @var bool|Result
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* Prepares the query against the database, and saves the connection
|
||||
* info necessary to execute the query later.
|
||||
*
|
||||
* NOTE: This version is based on SQL code. Child classes should
|
||||
* override this method.
|
||||
*
|
||||
* @param array $options Passed to the connection's prepare statement.
|
||||
* Unused in the MySQLi driver.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function _prepare(string $sql, array $options = [])
|
||||
{
|
||||
if (! ($this->statement = $this->db->connID->prepare($sql))) {
|
||||
$this->errorCode = $this->db->connID->lastErrorCode();
|
||||
$this->errorString = $this->db->connID->lastErrorMsg();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a new set of data and runs it against the currently
|
||||
* prepared query. Upon success, will return a Results object.
|
||||
*
|
||||
* @todo finalize()
|
||||
*/
|
||||
public function _execute(array $data): bool
|
||||
{
|
||||
if (! isset($this->statement)) {
|
||||
throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
|
||||
}
|
||||
|
||||
foreach ($data as $key => $item) {
|
||||
// Determine the type string
|
||||
if (is_int($item)) {
|
||||
$bindType = SQLITE3_INTEGER;
|
||||
} elseif (is_float($item)) {
|
||||
$bindType = SQLITE3_FLOAT;
|
||||
} else {
|
||||
$bindType = SQLITE3_TEXT;
|
||||
}
|
||||
|
||||
// Bind it
|
||||
$this->statement->bindValue($key + 1, $item, $bindType);
|
||||
}
|
||||
|
||||
$this->result = $this->statement->execute();
|
||||
|
||||
return $this->result !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result object for the prepared query.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _getResult()
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLite3;
|
||||
|
||||
use Closure;
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Result for SQLite3
|
||||
*/
|
||||
class Result extends BaseResult
|
||||
{
|
||||
/**
|
||||
* Gets the number of fields in the result set.
|
||||
*/
|
||||
public function getFieldCount(): int
|
||||
{
|
||||
return $this->resultID->numColumns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of column names in the result set.
|
||||
*/
|
||||
public function getFieldNames(): array
|
||||
{
|
||||
$fieldNames = [];
|
||||
|
||||
for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i++) {
|
||||
$fieldNames[] = $this->resultID->columnName($i);
|
||||
}
|
||||
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of objects representing field meta-data.
|
||||
*/
|
||||
public function getFieldData(): array
|
||||
{
|
||||
static $dataTypes = [
|
||||
SQLITE3_INTEGER => 'integer',
|
||||
SQLITE3_FLOAT => 'float',
|
||||
SQLITE3_TEXT => 'text',
|
||||
SQLITE3_BLOB => 'blob',
|
||||
SQLITE3_NULL => 'null',
|
||||
];
|
||||
|
||||
$retVal = [];
|
||||
$this->resultID->fetchArray(SQLITE3_NUM);
|
||||
|
||||
for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i++) {
|
||||
$retVal[$i] = new stdClass();
|
||||
$retVal[$i]->name = $this->resultID->columnName($i);
|
||||
$type = $this->resultID->columnType($i);
|
||||
$retVal[$i]->type = $type;
|
||||
$retVal[$i]->type_name = $dataTypes[$type] ?? null;
|
||||
$retVal[$i]->max_length = null;
|
||||
$retVal[$i]->length = null;
|
||||
}
|
||||
$this->resultID->reset();
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the current result.
|
||||
*/
|
||||
public function freeResult()
|
||||
{
|
||||
if (is_object($this->resultID)) {
|
||||
$this->resultID->finalize();
|
||||
$this->resultID = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal pointer to the desired offset. This is called
|
||||
* internally before fetching results to make sure the result set
|
||||
* starts at zero.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function dataSeek(int $n = 0)
|
||||
{
|
||||
if ($n !== 0) {
|
||||
throw new DatabaseException('SQLite3 doesn\'t support seeking to other offset.');
|
||||
}
|
||||
|
||||
return $this->resultID->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an array.
|
||||
*
|
||||
* Overridden by driver classes.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetchAssoc()
|
||||
{
|
||||
return $this->resultID->fetchArray(SQLITE3_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an object.
|
||||
*
|
||||
* Overridden by child classes.
|
||||
*
|
||||
* @return bool|object
|
||||
*/
|
||||
protected function fetchObject(string $className = 'stdClass')
|
||||
{
|
||||
// No native support for fetching rows as objects
|
||||
if (($row = $this->fetchAssoc()) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($className === 'stdClass') {
|
||||
return (object) $row;
|
||||
}
|
||||
|
||||
$classObj = new $className();
|
||||
|
||||
if (is_subclass_of($className, Entity::class)) {
|
||||
return $classObj->setAttributes($row);
|
||||
}
|
||||
|
||||
$classSet = Closure::bind(function ($key, $value) {
|
||||
$this->{$key} = $value;
|
||||
}, $classObj, $className);
|
||||
|
||||
foreach (array_keys($row) as $key) {
|
||||
$classSet($key, $row[$key]);
|
||||
}
|
||||
|
||||
return $classObj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLite3;
|
||||
|
||||
use CodeIgniter\Database\Exceptions\DataException;
|
||||
|
||||
/**
|
||||
* Class Table
|
||||
*
|
||||
* Provides missing features for altering tables that are common
|
||||
* in other supported databases, but are missing from SQLite.
|
||||
* These are needed in order to support migrations during testing
|
||||
* when another database is used as the primary engine, but
|
||||
* SQLite in memory databases are used for faster test execution.
|
||||
*/
|
||||
class Table
|
||||
{
|
||||
/**
|
||||
* All of the fields this table represents.
|
||||
*
|
||||
* @var array
|
||||
* @phpstan-var array<string, array<string, bool|int|string|null>>
|
||||
*/
|
||||
protected $fields = [];
|
||||
|
||||
/**
|
||||
* All of the unique/primary keys in the table.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $keys = [];
|
||||
|
||||
/**
|
||||
* All of the foreign keys in the table.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $foreignKeys = [];
|
||||
|
||||
/**
|
||||
* The name of the table we're working with.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName;
|
||||
|
||||
/**
|
||||
* The name of the table, with database prefix
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prefixedTableName;
|
||||
|
||||
/**
|
||||
* Database connection.
|
||||
*
|
||||
* @var Connection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Handle to our forge.
|
||||
*
|
||||
* @var Forge
|
||||
*/
|
||||
protected $forge;
|
||||
|
||||
/**
|
||||
* Table constructor.
|
||||
*/
|
||||
public function __construct(Connection $db, Forge $forge)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->forge = $forge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an existing database table and
|
||||
* collects all of the information needed to
|
||||
* recreate this table.
|
||||
*
|
||||
* @return Table
|
||||
*/
|
||||
public function fromTable(string $table)
|
||||
{
|
||||
$this->prefixedTableName = $table;
|
||||
|
||||
$prefix = $this->db->DBPrefix;
|
||||
|
||||
if (! empty($prefix) && strpos($table, $prefix) === 0) {
|
||||
$table = substr($table, strlen($prefix));
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists($this->prefixedTableName)) {
|
||||
throw DataException::forTableNotFound($this->prefixedTableName);
|
||||
}
|
||||
|
||||
$this->tableName = $table;
|
||||
|
||||
$this->fields = $this->formatFields($this->db->getFieldData($table));
|
||||
|
||||
$this->keys = array_merge($this->keys, $this->formatKeys($this->db->getIndexData($table)));
|
||||
|
||||
$this->foreignKeys = $this->db->getForeignKeyData($table);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after `fromTable` and any actions, like `dropColumn`, etc,
|
||||
* to finalize the action. It creates a temp table, creates the new
|
||||
* table with modifications, and copies the data over to the new table.
|
||||
* Resets the connection dataCache to be sure changes are collected.
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
$this->db->query('PRAGMA foreign_keys = OFF');
|
||||
|
||||
$this->db->transStart();
|
||||
|
||||
$this->forge->renameTable($this->tableName, "temp_{$this->tableName}");
|
||||
|
||||
$this->forge->reset();
|
||||
|
||||
$this->createTable();
|
||||
|
||||
$this->copyData();
|
||||
|
||||
$this->forge->dropTable("temp_{$this->tableName}");
|
||||
|
||||
$success = $this->db->transComplete();
|
||||
|
||||
$this->db->query('PRAGMA foreign_keys = ON');
|
||||
|
||||
$this->db->resetDataCache();
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops columns from the table.
|
||||
*
|
||||
* @param array|string $columns
|
||||
*
|
||||
* @return Table
|
||||
*/
|
||||
public function dropColumn($columns)
|
||||
{
|
||||
if (is_string($columns)) {
|
||||
$columns = explode(',', $columns);
|
||||
}
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$column = trim($column);
|
||||
if (isset($this->fields[$column])) {
|
||||
unset($this->fields[$column]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies a field, including changing data type,
|
||||
* renaming, etc.
|
||||
*
|
||||
* @return Table
|
||||
*/
|
||||
public function modifyColumn(array $field)
|
||||
{
|
||||
$field = $field[0];
|
||||
|
||||
$oldName = $field['name'];
|
||||
unset($field['name']);
|
||||
|
||||
$this->fields[$oldName] = $field;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a foreign key from this table so that
|
||||
* it won't be recreated in the future.
|
||||
*
|
||||
* @return Table
|
||||
*/
|
||||
public function dropForeignKey(string $column)
|
||||
{
|
||||
if (empty($this->foreignKeys)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < count($this->foreignKeys); $i++) {
|
||||
if ($this->foreignKeys[$i]->table_name !== $this->tableName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The column name should be the first thing in the constraint name
|
||||
if (strpos($this->foreignKeys[$i]->constraint_name, $column) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($this->foreignKeys[$i]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the new table based on our current fields.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function createTable()
|
||||
{
|
||||
$this->dropIndexes();
|
||||
$this->db->resetDataCache();
|
||||
|
||||
// Handle any modified columns.
|
||||
$fields = [];
|
||||
|
||||
foreach ($this->fields as $name => $field) {
|
||||
if (isset($field['new_name'])) {
|
||||
$fields[$field['new_name']] = $field;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$fields[$name] = $field;
|
||||
}
|
||||
|
||||
$this->forge->addField($fields);
|
||||
|
||||
$fieldNames = array_keys($fields);
|
||||
|
||||
$this->keys = array_filter(
|
||||
$this->keys,
|
||||
static fn ($index) => count(array_intersect($index['fields'], $fieldNames)) === count($index['fields'])
|
||||
);
|
||||
|
||||
// Unique/Index keys
|
||||
if (is_array($this->keys)) {
|
||||
foreach ($this->keys as $key) {
|
||||
switch ($key['type']) {
|
||||
case 'primary':
|
||||
$this->forge->addPrimaryKey($key['fields']);
|
||||
break;
|
||||
|
||||
case 'unique':
|
||||
$this->forge->addUniqueKey($key['fields']);
|
||||
break;
|
||||
|
||||
case 'index':
|
||||
$this->forge->addKey($key['fields']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->forge->createTable($this->tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies data from our old table to the new one,
|
||||
* taking care map data correctly based on any columns
|
||||
* that have been renamed.
|
||||
*/
|
||||
protected function copyData()
|
||||
{
|
||||
$exFields = [];
|
||||
$newFields = [];
|
||||
|
||||
foreach ($this->fields as $name => $details) {
|
||||
$newFields[] = $details['new_name'] ?? $name;
|
||||
$exFields[] = $name;
|
||||
}
|
||||
|
||||
$exFields = implode(
|
||||
', ',
|
||||
array_map(fn ($item) => $this->db->protectIdentifiers($item), $exFields)
|
||||
);
|
||||
$newFields = implode(
|
||||
', ',
|
||||
array_map(fn ($item) => $this->db->protectIdentifiers($item), $newFields)
|
||||
);
|
||||
|
||||
$this->db->query(
|
||||
"INSERT INTO {$this->prefixedTableName}({$newFields}) SELECT {$exFields} FROM {$this->db->DBPrefix}temp_{$this->tableName}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts fields retrieved from the database to
|
||||
* the format needed for creating fields with Forge.
|
||||
*
|
||||
* @param array|bool $fields
|
||||
*
|
||||
* @return mixed
|
||||
* @phpstan-return ($fields is array ? array : mixed)
|
||||
*/
|
||||
protected function formatFields($fields)
|
||||
{
|
||||
if (! is_array($fields)) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$return = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$return[$field->name] = [
|
||||
'type' => $field->type,
|
||||
'default' => $field->default,
|
||||
'null' => $field->nullable,
|
||||
];
|
||||
|
||||
if ($field->primary_key) {
|
||||
$this->keys[$field->name] = [
|
||||
'fields' => [$field->name],
|
||||
'type' => 'primary',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts keys retrieved from the database to
|
||||
* the format needed to create later.
|
||||
*
|
||||
* @param mixed $keys
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function formatKeys($keys)
|
||||
{
|
||||
if (! is_array($keys)) {
|
||||
return $keys;
|
||||
}
|
||||
|
||||
$return = [];
|
||||
|
||||
foreach ($keys as $name => $key) {
|
||||
$return[$name] = [
|
||||
'fields' => $key->fields,
|
||||
'type' => 'index',
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to drop all indexes and constraints
|
||||
* from the database for this table.
|
||||
*/
|
||||
protected function dropIndexes()
|
||||
{
|
||||
if (! is_array($this->keys) || $this->keys === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->keys as $name => $key) {
|
||||
if ($key['type'] === 'primary' || $key['type'] === 'unique') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->query("DROP INDEX IF EXISTS '{$name}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLite3;
|
||||
|
||||
use CodeIgniter\Database\BaseUtils;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Utils for SQLite3
|
||||
*/
|
||||
class Utils extends BaseUtils
|
||||
{
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $optimizeTable = 'REINDEX %s';
|
||||
|
||||
/**
|
||||
* Platform dependent version of the backup function.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _backup(?array $prefs = null)
|
||||
{
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database;
|
||||
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use Config\Database;
|
||||
use Faker\Factory;
|
||||
use Faker\Generator;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Seeder
|
||||
*/
|
||||
class Seeder
|
||||
{
|
||||
/**
|
||||
* The name of the database group to use.
|
||||
*
|
||||
* @var string
|
||||
* @phpstan-var non-empty-string
|
||||
*/
|
||||
protected $DBGroup;
|
||||
|
||||
/**
|
||||
* Where we can find the Seed files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $seedPath;
|
||||
|
||||
/**
|
||||
* An instance of the main Database configuration
|
||||
*
|
||||
* @var Database
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Database Connection instance
|
||||
*
|
||||
* @var BaseConnection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Database Forge instance.
|
||||
*
|
||||
* @var Forge
|
||||
*/
|
||||
protected $forge;
|
||||
|
||||
/**
|
||||
* If true, will not display CLI messages.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $silent = false;
|
||||
|
||||
/**
|
||||
* Faker Generator instance.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
private static ?Generator $faker = null;
|
||||
|
||||
/**
|
||||
* Seeder constructor.
|
||||
*/
|
||||
public function __construct(Database $config, ?BaseConnection $db = null)
|
||||
{
|
||||
$this->seedPath = $config->filesPath ?? APPPATH . 'Database/';
|
||||
|
||||
if (empty($this->seedPath)) {
|
||||
throw new InvalidArgumentException('Invalid filesPath set in the Config\Database.');
|
||||
}
|
||||
|
||||
$this->seedPath = rtrim($this->seedPath, '\\/') . '/Seeds/';
|
||||
|
||||
if (! is_dir($this->seedPath)) {
|
||||
throw new InvalidArgumentException('Unable to locate the seeds directory. Please check Config\Database::filesPath');
|
||||
}
|
||||
|
||||
$this->config = &$config;
|
||||
|
||||
$db ??= Database::connect($this->DBGroup);
|
||||
|
||||
$this->db = $db;
|
||||
$this->forge = Database::forge($this->DBGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Faker Generator instance.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static function faker(): ?Generator
|
||||
{
|
||||
if (self::$faker === null && class_exists(Factory::class)) {
|
||||
self::$faker = Factory::create();
|
||||
}
|
||||
|
||||
return self::$faker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the specified seeder and runs it.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function call(string $class)
|
||||
{
|
||||
$class = trim($class);
|
||||
|
||||
if ($class === '') {
|
||||
throw new InvalidArgumentException('No seeder was specified.');
|
||||
}
|
||||
|
||||
if (strpos($class, '\\') === false) {
|
||||
$path = $this->seedPath . str_replace('.php', '', $class) . '.php';
|
||||
|
||||
if (! is_file($path)) {
|
||||
throw new InvalidArgumentException('The specified seeder is not a valid file: ' . $path);
|
||||
}
|
||||
|
||||
// Assume the class has the correct namespace
|
||||
// @codeCoverageIgnoreStart
|
||||
$class = APP_NAMESPACE . '\Database\Seeds\\' . $class;
|
||||
|
||||
if (! class_exists($class, false)) {
|
||||
require_once $path;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/** @var Seeder $seeder */
|
||||
$seeder = new $class($this->config);
|
||||
$seeder->setSilent($this->silent)->run();
|
||||
|
||||
unset($seeder);
|
||||
|
||||
if (is_cli() && ! $this->silent) {
|
||||
CLI::write("Seeded: {$class}", 'green');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location of the directory that seed files can be located in.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPath(string $path)
|
||||
{
|
||||
$this->seedPath = rtrim($path, '\\/') . '/';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the silent treatment.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSilent(bool $silent)
|
||||
{
|
||||
$this->silent = $silent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database seeds. This is where the magic happens.
|
||||
*
|
||||
* Child classes must implement this method and take care
|
||||
* of inserting their data here.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 2.1.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* CUBRID Database Adapter Class
|
||||
*
|
||||
* Note: _DB is an extender class that the app controller
|
||||
* creates dynamically based on whether the query builder
|
||||
* class is being used or not.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author Esen Sagynov
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_cubrid_driver extends CI_DB {
|
||||
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $dbdriver = 'cubrid';
|
||||
|
||||
/**
|
||||
* Auto-commit flag
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $auto_commit = TRUE;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_escape_char = '`';
|
||||
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_random_keyword = array('RANDOM()', 'RANDOM(%d)');
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
if (preg_match('/^CUBRID:[^:]+(:[0-9][1-9]{0,4})?:[^:]+:[^:]*:[^:]*:(\?.+)?$/', $this->dsn, $matches))
|
||||
{
|
||||
if (stripos($matches[2], 'autocommit=off') !== FALSE)
|
||||
{
|
||||
$this->auto_commit = FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no port is defined by the user, use the default value
|
||||
empty($this->port) OR $this->port = 33000;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Non-persistent database connection
|
||||
*
|
||||
* @param bool $persistent
|
||||
* @return resource
|
||||
*/
|
||||
public function db_connect($persistent = FALSE)
|
||||
{
|
||||
if (preg_match('/^CUBRID:[^:]+(:[0-9][1-9]{0,4})?:[^:]+:([^:]*):([^:]*):(\?.+)?$/', $this->dsn, $matches))
|
||||
{
|
||||
$func = ($persistent !== TRUE) ? 'cubrid_connect_with_url' : 'cubrid_pconnect_with_url';
|
||||
return ($matches[2] === '' && $matches[3] === '' && $this->username !== '' && $this->password !== '')
|
||||
? $func($this->dsn, $this->username, $this->password)
|
||||
: $func($this->dsn);
|
||||
}
|
||||
|
||||
$func = ($persistent !== TRUE) ? 'cubrid_connect' : 'cubrid_pconnect';
|
||||
return ($this->username !== '')
|
||||
? $func($this->hostname, $this->port, $this->database, $this->username, $this->password)
|
||||
: $func($this->hostname, $this->port, $this->database);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reconnect
|
||||
*
|
||||
* Keep / reestablish the db connection if no queries have been
|
||||
* sent for a length of time exceeding the server's idle timeout
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
if (cubrid_ping($this->conn_id) === FALSE)
|
||||
{
|
||||
$this->conn_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database version number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
if (isset($this->data_cache['version']))
|
||||
{
|
||||
return $this->data_cache['version'];
|
||||
}
|
||||
|
||||
return ( ! $this->conn_id OR ($version = cubrid_get_server_info($this->conn_id)) === FALSE)
|
||||
? FALSE
|
||||
: $this->data_cache['version'] = $version;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return resource
|
||||
*/
|
||||
protected function _execute($sql)
|
||||
{
|
||||
return cubrid_query($sql, $this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_begin()
|
||||
{
|
||||
if (($autocommit = cubrid_get_autocommit($this->conn_id)) === NULL)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
elseif ($autocommit === TRUE)
|
||||
{
|
||||
return cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_FALSE);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_commit()
|
||||
{
|
||||
if ( ! cubrid_commit($this->conn_id))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id))
|
||||
{
|
||||
return cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_rollback()
|
||||
{
|
||||
if ( ! cubrid_rollback($this->conn_id))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id))
|
||||
{
|
||||
cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Platform-dependent string escape
|
||||
*
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
protected function _escape_str($str)
|
||||
{
|
||||
return cubrid_real_escape_string($str, $this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Affected Rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function affected_rows()
|
||||
{
|
||||
return cubrid_affected_rows();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function insert_id()
|
||||
{
|
||||
return cubrid_insert_id($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* List table query
|
||||
*
|
||||
* Generates a platform-specific query string so that the table names can be fetched
|
||||
*
|
||||
* @param bool $prefix_limit
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_tables($prefix_limit = FALSE)
|
||||
{
|
||||
$sql = 'SHOW TABLES';
|
||||
|
||||
if ($prefix_limit !== FALSE && $this->dbprefix !== '')
|
||||
{
|
||||
return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show column query
|
||||
*
|
||||
* Generates a platform-specific query string so that the column names can be fetched
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_columns($table = '')
|
||||
{
|
||||
return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns an object with field data
|
||||
*
|
||||
* @param string $table
|
||||
* @return array
|
||||
*/
|
||||
public function field_data($table)
|
||||
{
|
||||
if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
$query = $query->result_object();
|
||||
|
||||
$retval = array();
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $query[$i]->Field;
|
||||
|
||||
sscanf($query[$i]->Type, '%[a-z](%d)',
|
||||
$retval[$i]->type,
|
||||
$retval[$i]->max_length
|
||||
);
|
||||
|
||||
$retval[$i]->default = $query[$i]->Default;
|
||||
$retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI');
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* Returns an array containing code and message of the last
|
||||
* database error that has occurred.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
return array('code' => cubrid_errno($this->conn_id), 'message' => cubrid_error($this->conn_id));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* FROM tables
|
||||
*
|
||||
* Groups tables in FROM clauses if needed, so there is no confusion
|
||||
* about operator precedence.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _from_tables()
|
||||
{
|
||||
if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
|
||||
{
|
||||
return '('.implode(', ', $this->qb_from).')';
|
||||
}
|
||||
|
||||
return implode(', ', $this->qb_from);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Close DB Connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
cubrid_close($this->conn_id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 2.1.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* CUBRID Forge Class
|
||||
*
|
||||
* @category Database
|
||||
* @author Esen Sagynov
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_cubrid_forge extends CI_DB_forge {
|
||||
|
||||
/**
|
||||
* CREATE DATABASE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_database = FALSE;
|
||||
|
||||
/**
|
||||
* CREATE TABLE keys flag
|
||||
*
|
||||
* Whether table keys are created from within the
|
||||
* CREATE TABLE statement.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_create_table_keys = TRUE;
|
||||
|
||||
/**
|
||||
* DROP DATABASE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_drop_database = FALSE;
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_table_if = FALSE;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_unsigned = array(
|
||||
'SHORT' => 'INTEGER',
|
||||
'SMALLINT' => 'INTEGER',
|
||||
'INT' => 'BIGINT',
|
||||
'INTEGER' => 'BIGINT',
|
||||
'BIGINT' => 'NUMERIC',
|
||||
'FLOAT' => 'DOUBLE',
|
||||
'REAL' => 'DOUBLE'
|
||||
);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alter_type ALTER type
|
||||
* @param string $table Table name
|
||||
* @param mixed $field Column definition
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function _alter_table($alter_type, $table, $field)
|
||||
{
|
||||
if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
|
||||
{
|
||||
return parent::_alter_table($alter_type, $table, $field);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
|
||||
$sqls = array();
|
||||
for ($i = 0, $c = count($field); $i < $c; $i++)
|
||||
{
|
||||
if ($field[$i]['_literal'] !== FALSE)
|
||||
{
|
||||
$sqls[] = $sql.' CHANGE '.$field[$i]['_literal'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$alter_type = empty($field[$i]['new_name']) ? ' MODIFY ' : ' CHANGE ';
|
||||
$sqls[] = $sql.$alter_type.$this->_process_column($field[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*
|
||||
* @param array $field
|
||||
* @return string
|
||||
*/
|
||||
protected function _process_column($field)
|
||||
{
|
||||
$extra_clause = isset($field['after'])
|
||||
? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
|
||||
|
||||
if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
|
||||
{
|
||||
$extra_clause = ' FIRST';
|
||||
}
|
||||
|
||||
return $this->db->escape_identifiers($field['name'])
|
||||
.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
|
||||
.' '.$field['type'].$field['length']
|
||||
.$field['unsigned']
|
||||
.$field['null']
|
||||
.$field['default']
|
||||
.$field['auto_increment']
|
||||
.$field['unique']
|
||||
.$extra_clause;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field attribute TYPE
|
||||
*
|
||||
* Performs a data type mapping between different databases.
|
||||
*
|
||||
* @param array &$attributes
|
||||
* @return void
|
||||
*/
|
||||
protected function _attr_type(&$attributes)
|
||||
{
|
||||
switch (strtoupper($attributes['TYPE']))
|
||||
{
|
||||
case 'TINYINT':
|
||||
$attributes['TYPE'] = 'SMALLINT';
|
||||
$attributes['UNSIGNED'] = FALSE;
|
||||
return;
|
||||
case 'MEDIUMINT':
|
||||
$attributes['TYPE'] = 'INTEGER';
|
||||
$attributes['UNSIGNED'] = FALSE;
|
||||
return;
|
||||
case 'LONGTEXT':
|
||||
$attributes['TYPE'] = 'STRING';
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process indexes
|
||||
*
|
||||
* @param string $table (ignored)
|
||||
* @return string
|
||||
*/
|
||||
protected function _process_indexes($table)
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
for ($i = 0, $c = count($this->keys); $i < $c; $i++)
|
||||
{
|
||||
if (is_array($this->keys[$i]))
|
||||
{
|
||||
for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
|
||||
{
|
||||
if ( ! isset($this->fields[$this->keys[$i][$i2]]))
|
||||
{
|
||||
unset($this->keys[$i][$i2]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ( ! isset($this->fields[$this->keys[$i]]))
|
||||
{
|
||||
unset($this->keys[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
|
||||
|
||||
$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
|
||||
.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
|
||||
}
|
||||
|
||||
$this->keys = array();
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 2.1.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* CUBRID Result Class
|
||||
*
|
||||
* This class extends the parent result class: CI_DB_result
|
||||
*
|
||||
* @category Database
|
||||
* @author Esen Sagynov
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_cubrid_result extends CI_DB_result {
|
||||
|
||||
/**
|
||||
* Number of rows in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_rows()
|
||||
{
|
||||
return is_int($this->num_rows)
|
||||
? $this->num_rows
|
||||
: $this->num_rows = cubrid_num_rows($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
return cubrid_num_fields($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
return cubrid_column_names($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
$retval = array();
|
||||
|
||||
for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = cubrid_field_name($this->result_id, $i);
|
||||
$retval[$i]->type = cubrid_field_type($this->result_id, $i);
|
||||
$retval[$i]->max_length = cubrid_field_len($this->result_id, $i);
|
||||
$retval[$i]->primary_key = (int) (strpos(cubrid_field_flags($this->result_id, $i), 'primary_key') !== FALSE);
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
if (is_resource($this->result_id) OR
|
||||
(get_resource_type($this->result_id) === 'Unknown' && preg_match('/Resource id #/', strval($this->result_id))))
|
||||
{
|
||||
cubrid_close_request($this->result_id);
|
||||
$this->result_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Data Seek
|
||||
*
|
||||
* Moves the internal pointer to the desired offset. We call
|
||||
* this internally before fetching results to make sure the
|
||||
* result set starts at zero.
|
||||
*
|
||||
* @param int $n
|
||||
* @return bool
|
||||
*/
|
||||
public function data_seek($n = 0)
|
||||
{
|
||||
return cubrid_data_seek($this->result_id, $n);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
return cubrid_fetch_assoc($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
return cubrid_fetch_object($this->result_id, $class_name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 2.1.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* CUBRID Utility Class
|
||||
*
|
||||
* @category Database
|
||||
* @author Esen Sagynov
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_cubrid_utility extends CI_DB_utility {
|
||||
|
||||
/**
|
||||
* List databases
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_databases()
|
||||
{
|
||||
if (isset($this->db->data_cache['db_names']))
|
||||
{
|
||||
return $this->db->data_cache['db_names'];
|
||||
}
|
||||
|
||||
return $this->db->data_cache['db_names'] = cubrid_list_dbs($this->db->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* CUBRID Export
|
||||
*
|
||||
* @param array Preferences
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _backup($params = array())
|
||||
{
|
||||
// No SQL based support in CUBRID as of version 8.4.0. Database or
|
||||
// table backup can be performed using CUBRID Manager
|
||||
// database administration tool.
|
||||
return $this->db->display_error('db_unsupported_feature');
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,414 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 3.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Firebird/Interbase Database Adapter Class
|
||||
*
|
||||
* Note: _DB is an extender class that the app controller
|
||||
* creates dynamically based on whether the query builder
|
||||
* class is being used or not.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_ibase_driver extends CI_DB {
|
||||
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $dbdriver = 'ibase';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_random_keyword = array('RAND()', 'RAND()');
|
||||
|
||||
/**
|
||||
* IBase Transaction status flag
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $_ibase_trans;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Non-persistent database connection
|
||||
*
|
||||
* @param bool $persistent
|
||||
* @return resource
|
||||
*/
|
||||
public function db_connect($persistent = FALSE)
|
||||
{
|
||||
return ($persistent === TRUE)
|
||||
? ibase_pconnect($this->hostname.':'.$this->database, $this->username, $this->password, $this->char_set)
|
||||
: ibase_connect($this->hostname.':'.$this->database, $this->username, $this->password, $this->char_set);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database version number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
if (isset($this->data_cache['version']))
|
||||
{
|
||||
return $this->data_cache['version'];
|
||||
}
|
||||
|
||||
if (($service = ibase_service_attach($this->hostname, $this->username, $this->password)))
|
||||
{
|
||||
$this->data_cache['version'] = ibase_server_info($service, IBASE_SVC_SERVER_VERSION);
|
||||
|
||||
// Don't keep the service open
|
||||
ibase_service_detach($service);
|
||||
return $this->data_cache['version'];
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return resource
|
||||
*/
|
||||
protected function _execute($sql)
|
||||
{
|
||||
return ibase_query(isset($this->_ibase_trans) ? $this->_ibase_trans : $this->conn_id, $sql);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_begin()
|
||||
{
|
||||
if (($trans_handle = ibase_trans($this->conn_id)) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->_ibase_trans = $trans_handle;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_commit()
|
||||
{
|
||||
if (ibase_commit($this->_ibase_trans))
|
||||
{
|
||||
$this->_ibase_trans = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_rollback()
|
||||
{
|
||||
if (ibase_rollback($this->_ibase_trans))
|
||||
{
|
||||
$this->_ibase_trans = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Affected Rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function affected_rows()
|
||||
{
|
||||
return ibase_affected_rows($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* @param string $generator_name
|
||||
* @param int $inc_by
|
||||
* @return int
|
||||
*/
|
||||
public function insert_id($generator_name, $inc_by = 0)
|
||||
{
|
||||
//If a generator hasn't been used before it will return 0
|
||||
return ibase_gen_id('"'.$generator_name.'"', $inc_by);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* List table query
|
||||
*
|
||||
* Generates a platform-specific query string so that the table names can be fetched
|
||||
*
|
||||
* @param bool $prefix_limit
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_tables($prefix_limit = FALSE)
|
||||
{
|
||||
$sql = 'SELECT TRIM("RDB$RELATION_NAME") AS TABLE_NAME FROM "RDB$RELATIONS" WHERE "RDB$RELATION_NAME" NOT LIKE \'RDB$%\' AND "RDB$RELATION_NAME" NOT LIKE \'MON$%\'';
|
||||
|
||||
if ($prefix_limit !== FALSE && $this->dbprefix !== '')
|
||||
{
|
||||
return $sql.' AND TRIM("RDB$RELATION_NAME") AS TABLE_NAME LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
|
||||
.sprintf($this->_like_escape_str, $this->_like_escape_chr);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show column query
|
||||
*
|
||||
* Generates a platform-specific query string so that the column names can be fetched
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_columns($table = '')
|
||||
{
|
||||
return 'SELECT TRIM("RDB$FIELD_NAME") AS COLUMN_NAME FROM "RDB$RELATION_FIELDS" WHERE "RDB$RELATION_NAME" = '.$this->escape($table);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns an object with field data
|
||||
*
|
||||
* @param string $table
|
||||
* @return array
|
||||
*/
|
||||
public function field_data($table)
|
||||
{
|
||||
$sql = 'SELECT "rfields"."RDB$FIELD_NAME" AS "name",
|
||||
CASE "fields"."RDB$FIELD_TYPE"
|
||||
WHEN 7 THEN \'SMALLINT\'
|
||||
WHEN 8 THEN \'INTEGER\'
|
||||
WHEN 9 THEN \'QUAD\'
|
||||
WHEN 10 THEN \'FLOAT\'
|
||||
WHEN 11 THEN \'DFLOAT\'
|
||||
WHEN 12 THEN \'DATE\'
|
||||
WHEN 13 THEN \'TIME\'
|
||||
WHEN 14 THEN \'CHAR\'
|
||||
WHEN 16 THEN \'INT64\'
|
||||
WHEN 27 THEN \'DOUBLE\'
|
||||
WHEN 35 THEN \'TIMESTAMP\'
|
||||
WHEN 37 THEN \'VARCHAR\'
|
||||
WHEN 40 THEN \'CSTRING\'
|
||||
WHEN 261 THEN \'BLOB\'
|
||||
ELSE NULL
|
||||
END AS "type",
|
||||
"fields"."RDB$FIELD_LENGTH" AS "max_length",
|
||||
"rfields"."RDB$DEFAULT_VALUE" AS "default"
|
||||
FROM "RDB$RELATION_FIELDS" "rfields"
|
||||
JOIN "RDB$FIELDS" "fields" ON "rfields"."RDB$FIELD_SOURCE" = "fields"."RDB$FIELD_NAME"
|
||||
WHERE "rfields"."RDB$RELATION_NAME" = '.$this->escape($table).'
|
||||
ORDER BY "rfields"."RDB$FIELD_POSITION"';
|
||||
|
||||
return (($query = $this->query($sql)) !== FALSE)
|
||||
? $query->result_object()
|
||||
: FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* Returns an array containing code and message of the last
|
||||
* database error that has occurred.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
return array('code' => ibase_errcode(), 'message' => ibase_errmsg());
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Update statement
|
||||
*
|
||||
* Generates a platform-specific update string from the supplied data
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $values
|
||||
* @return string
|
||||
*/
|
||||
protected function _update($table, $values)
|
||||
{
|
||||
$this->qb_limit = FALSE;
|
||||
return parent::_update($table, $values);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Truncate statement
|
||||
*
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the TRUNCATE statement,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _truncate($table)
|
||||
{
|
||||
return 'DELETE FROM '.$table;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Delete statement
|
||||
*
|
||||
* Generates a platform-specific delete string from the supplied data
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _delete($table)
|
||||
{
|
||||
$this->qb_limit = FALSE;
|
||||
return parent::_delete($table);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* LIMIT
|
||||
*
|
||||
* Generates a platform-specific LIMIT clause
|
||||
*
|
||||
* @param string $sql SQL Query
|
||||
* @return string
|
||||
*/
|
||||
protected function _limit($sql)
|
||||
{
|
||||
// Limit clause depends on if Interbase or Firebird
|
||||
if (stripos($this->version(), 'firebird') !== FALSE)
|
||||
{
|
||||
$select = 'FIRST '.$this->qb_limit
|
||||
.($this->qb_offset ? ' SKIP '.$this->qb_offset : '');
|
||||
}
|
||||
else
|
||||
{
|
||||
$select = 'ROWS '
|
||||
.($this->qb_offset ? $this->qb_offset.' TO '.($this->qb_limit + $this->qb_offset) : $this->qb_limit);
|
||||
}
|
||||
|
||||
return preg_replace('`SELECT`i', 'SELECT '.$select, $sql, 1);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert batch statement
|
||||
*
|
||||
* Generates a platform-specific insert string from the supplied data.
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @param array $keys INSERT keys
|
||||
* @param array $values INSERT values
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function _insert_batch($table, $keys, $values)
|
||||
{
|
||||
return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Close DB Connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
ibase_close($this->conn_id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 3.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Interbase/Firebird Forge Class
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_ibase_forge extends CI_DB_forge {
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_table_if = FALSE;
|
||||
|
||||
/**
|
||||
* RENAME TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_rename_table = FALSE;
|
||||
|
||||
/**
|
||||
* DROP TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_drop_table_if = FALSE;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_unsigned = array(
|
||||
'SMALLINT' => 'INTEGER',
|
||||
'INTEGER' => 'INT64',
|
||||
'FLOAT' => 'DOUBLE PRECISION'
|
||||
);
|
||||
|
||||
/**
|
||||
* NULL value representation in CREATE/ALTER TABLE statements
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_null = 'NULL';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create database
|
||||
*
|
||||
* @param string $db_name
|
||||
* @return bool
|
||||
*/
|
||||
public function create_database($db_name)
|
||||
{
|
||||
// Firebird databases are flat files, so a path is required
|
||||
|
||||
// Hostname is needed for remote access
|
||||
empty($this->db->hostname) OR $db_name = $this->hostname.':'.$db_name;
|
||||
|
||||
return parent::create_database('"'.$db_name.'"');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Drop database
|
||||
*
|
||||
* @param string $db_name (ignored)
|
||||
* @return bool
|
||||
*/
|
||||
public function drop_database($db_name)
|
||||
{
|
||||
if ( ! ibase_drop_db($this->conn_id))
|
||||
{
|
||||
return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE;
|
||||
}
|
||||
elseif ( ! empty($this->db->data_cache['db_names']))
|
||||
{
|
||||
$key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE);
|
||||
if ($key !== FALSE)
|
||||
{
|
||||
unset($this->db->data_cache['db_names'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alter_type ALTER type
|
||||
* @param string $table Table name
|
||||
* @param mixed $field Column definition
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function _alter_table($alter_type, $table, $field)
|
||||
{
|
||||
if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
|
||||
{
|
||||
return parent::_alter_table($alter_type, $table, $field);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
|
||||
$sqls = array();
|
||||
for ($i = 0, $c = count($field); $i < $c; $i++)
|
||||
{
|
||||
if ($field[$i]['_literal'] !== FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (isset($field[$i]['type']))
|
||||
{
|
||||
$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identififers($field[$i]['name'])
|
||||
.' TYPE '.$field[$i]['type'].$field[$i]['length'];
|
||||
}
|
||||
|
||||
if ( ! empty($field[$i]['default']))
|
||||
{
|
||||
$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
|
||||
.' SET DEFAULT '.$field[$i]['default'];
|
||||
}
|
||||
|
||||
if (isset($field[$i]['null']))
|
||||
{
|
||||
$sqls[] = 'UPDATE "RDB$RELATION_FIELDS" SET "RDB$NULL_FLAG" = '
|
||||
.($field[$i]['null'] === TRUE ? 'NULL' : '1')
|
||||
.' WHERE "RDB$FIELD_NAME" = '.$this->db->escape($field[$i]['name'])
|
||||
.' AND "RDB$RELATION_NAME" = '.$this->db->escape($table);
|
||||
}
|
||||
|
||||
if ( ! empty($field[$i]['new_name']))
|
||||
{
|
||||
$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
|
||||
.' TO '.$this->db->escape_identifiers($field[$i]['new_name']);
|
||||
}
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*
|
||||
* @param array $field
|
||||
* @return string
|
||||
*/
|
||||
protected function _process_column($field)
|
||||
{
|
||||
return $this->db->escape_identifiers($field['name'])
|
||||
.' '.$field['type'].$field['length']
|
||||
.$field['null']
|
||||
.$field['unique']
|
||||
.$field['default'];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field attribute TYPE
|
||||
*
|
||||
* Performs a data type mapping between different databases.
|
||||
*
|
||||
* @param array &$attributes
|
||||
* @return void
|
||||
*/
|
||||
protected function _attr_type(&$attributes)
|
||||
{
|
||||
switch (strtoupper($attributes['TYPE']))
|
||||
{
|
||||
case 'TINYINT':
|
||||
$attributes['TYPE'] = 'SMALLINT';
|
||||
$attributes['UNSIGNED'] = FALSE;
|
||||
return;
|
||||
case 'MEDIUMINT':
|
||||
$attributes['TYPE'] = 'INTEGER';
|
||||
$attributes['UNSIGNED'] = FALSE;
|
||||
return;
|
||||
case 'INT':
|
||||
$attributes['TYPE'] = 'INTEGER';
|
||||
return;
|
||||
case 'BIGINT':
|
||||
$attributes['TYPE'] = 'INT64';
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*
|
||||
* @param array &$attributes
|
||||
* @param array &$field
|
||||
* @return void
|
||||
*/
|
||||
protected function _attr_auto_increment(&$attributes, &$field)
|
||||
{
|
||||
// Not supported
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 3.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Interbase/Firebird Result Class
|
||||
*
|
||||
* This class extends the parent result class: CI_DB_result
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_ibase_result extends CI_DB_result {
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
return ibase_num_fields($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
$field_names = array();
|
||||
for ($i = 0, $num_fields = $this->num_fields(); $i < $num_fields; $i++)
|
||||
{
|
||||
$info = ibase_field_info($this->result_id, $i);
|
||||
$field_names[] = $info['name'];
|
||||
}
|
||||
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
$retval = array();
|
||||
for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
|
||||
{
|
||||
$info = ibase_field_info($this->result_id, $i);
|
||||
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $info['name'];
|
||||
$retval[$i]->type = $info['type'];
|
||||
$retval[$i]->max_length = $info['length'];
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
ibase_free_result($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
return ibase_fetch_assoc($this->result_id, IBASE_FETCH_BLOBS);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
$row = ibase_fetch_object($this->result_id, IBASE_FETCH_BLOBS);
|
||||
|
||||
if ($class_name === 'stdClass' OR ! $row)
|
||||
{
|
||||
return $row;
|
||||
}
|
||||
|
||||
$class_name = new $class_name();
|
||||
foreach ($row as $key => $value)
|
||||
{
|
||||
$class_name->$key = $value;
|
||||
}
|
||||
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 3.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Interbase/Firebird Utility Class
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_ibase_utility extends CI_DB_utility {
|
||||
|
||||
/**
|
||||
* Export
|
||||
*
|
||||
* @param string $filename
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _backup($filename)
|
||||
{
|
||||
if ($service = ibase_service_attach($this->db->hostname, $this->db->username, $this->db->password))
|
||||
{
|
||||
$res = ibase_backup($service, $this->db->database, $filename.'.fbk');
|
||||
|
||||
// Close the service connection
|
||||
ibase_service_detach($service);
|
||||
return $res;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,519 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MS SQL Database Adapter Class
|
||||
*
|
||||
* Note: _DB is an extender class that the app controller
|
||||
* creates dynamically based on whether the query builder
|
||||
* class is being used or not.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mssql_driver extends CI_DB {
|
||||
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $dbdriver = 'mssql';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_random_keyword = array('NEWID()', 'RAND(%d)');
|
||||
|
||||
/**
|
||||
* Quoted identifier flag
|
||||
*
|
||||
* Whether to use SQL-92 standard quoted identifier
|
||||
* (double quotes) or brackets for identifier escaping.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_quoted_identifier = TRUE;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* Appends the port number to the hostname, if needed.
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
if ( ! empty($this->port))
|
||||
{
|
||||
$this->hostname .= (DIRECTORY_SEPARATOR === '\\' ? ',' : ':').$this->port;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Non-persistent database connection
|
||||
*
|
||||
* @param bool $persistent
|
||||
* @return resource
|
||||
*/
|
||||
public function db_connect($persistent = FALSE)
|
||||
{
|
||||
$this->conn_id = ($persistent)
|
||||
? mssql_pconnect($this->hostname, $this->username, $this->password)
|
||||
: mssql_connect($this->hostname, $this->username, $this->password);
|
||||
|
||||
if ( ! $this->conn_id)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// Select the DB... assuming a database name is specified in the config file
|
||||
if ($this->database !== '' && ! $this->db_select())
|
||||
{
|
||||
log_message('error', 'Unable to select database: '.$this->database);
|
||||
|
||||
return ($this->db_debug === TRUE)
|
||||
? $this->display_error('db_unable_to_select', $this->database)
|
||||
: FALSE;
|
||||
}
|
||||
|
||||
// Determine how identifiers are escaped
|
||||
$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
|
||||
$query = $query->row_array();
|
||||
$this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi'];
|
||||
$this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
|
||||
|
||||
return $this->conn_id;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Select the database
|
||||
*
|
||||
* @param string $database
|
||||
* @return bool
|
||||
*/
|
||||
public function db_select($database = '')
|
||||
{
|
||||
if ($database === '')
|
||||
{
|
||||
$database = $this->database;
|
||||
}
|
||||
|
||||
// Note: Escaping is required in the event that the DB name
|
||||
// contains reserved characters.
|
||||
if (mssql_select_db('['.$database.']', $this->conn_id))
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->data_cache = array();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return mixed resource if rows are returned, bool otherwise
|
||||
*/
|
||||
protected function _execute($sql)
|
||||
{
|
||||
return mssql_query($sql, $this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_begin()
|
||||
{
|
||||
return $this->simple_query('BEGIN TRAN');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_commit()
|
||||
{
|
||||
return $this->simple_query('COMMIT TRAN');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_rollback()
|
||||
{
|
||||
return $this->simple_query('ROLLBACK TRAN');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Affected Rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function affected_rows()
|
||||
{
|
||||
return mssql_rows_affected($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* Returns the last id created in the Identity column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function insert_id()
|
||||
{
|
||||
$query = version_compare($this->version(), '8', '>=')
|
||||
? 'SELECT SCOPE_IDENTITY() AS last_id'
|
||||
: 'SELECT @@IDENTITY AS last_id';
|
||||
|
||||
$query = $this->query($query);
|
||||
$query = $query->row();
|
||||
return $query->last_id;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set client character set
|
||||
*
|
||||
* @param string $charset
|
||||
* @return bool
|
||||
*/
|
||||
protected function _db_set_charset($charset)
|
||||
{
|
||||
return (ini_set('mssql.charset', $charset) !== FALSE);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Version number query string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _version()
|
||||
{
|
||||
return "SELECT SERVERPROPERTY('ProductVersion') AS ver";
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* List table query
|
||||
*
|
||||
* Generates a platform-specific query string so that the table names can be fetched
|
||||
*
|
||||
* @param bool $prefix_limit
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_tables($prefix_limit = FALSE)
|
||||
{
|
||||
$sql = 'SELECT '.$this->escape_identifiers('name')
|
||||
.' FROM '.$this->escape_identifiers('sysobjects')
|
||||
.' WHERE '.$this->escape_identifiers('type')." = 'U'";
|
||||
|
||||
if ($prefix_limit !== FALSE && $this->dbprefix !== '')
|
||||
{
|
||||
$sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' "
|
||||
.sprintf($this->_like_escape_str, $this->_like_escape_chr);
|
||||
}
|
||||
|
||||
return $sql.' ORDER BY '.$this->escape_identifiers('name');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* List column query
|
||||
*
|
||||
* Generates a platform-specific query string so that the column names can be fetched
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_columns($table = '')
|
||||
{
|
||||
return 'SELECT COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.Columns
|
||||
WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns an object with field data
|
||||
*
|
||||
* @param string $table
|
||||
* @return array
|
||||
*/
|
||||
public function field_data($table)
|
||||
{
|
||||
$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
|
||||
FROM INFORMATION_SCHEMA.Columns
|
||||
WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
|
||||
|
||||
if (($query = $this->query($sql)) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
$query = $query->result_object();
|
||||
|
||||
$retval = array();
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $query[$i]->COLUMN_NAME;
|
||||
$retval[$i]->type = $query[$i]->DATA_TYPE;
|
||||
$retval[$i]->max_length = ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION;
|
||||
$retval[$i]->default = $query[$i]->COLUMN_DEFAULT;
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* Returns an array containing code and message of the last
|
||||
* database error that has occurred.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
// We need this because the error info is discarded by the
|
||||
// server the first time you request it, and query() already
|
||||
// calls error() once for logging purposes when a query fails.
|
||||
static $error = array('code' => 0, 'message' => NULL);
|
||||
|
||||
$message = mssql_get_last_message();
|
||||
if ( ! empty($message))
|
||||
{
|
||||
$error['code'] = $this->query('SELECT @@ERROR AS code')->row()->code;
|
||||
$error['message'] = $message;
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Update statement
|
||||
*
|
||||
* Generates a platform-specific update string from the supplied data
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $values
|
||||
* @return string
|
||||
*/
|
||||
protected function _update($table, $values)
|
||||
{
|
||||
$this->qb_limit = FALSE;
|
||||
$this->qb_orderby = array();
|
||||
return parent::_update($table, $values);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Truncate statement
|
||||
*
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the TRUNCATE statement,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _truncate($table)
|
||||
{
|
||||
return 'TRUNCATE TABLE '.$table;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Delete statement
|
||||
*
|
||||
* Generates a platform-specific delete string from the supplied data
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _delete($table)
|
||||
{
|
||||
if ($this->qb_limit)
|
||||
{
|
||||
return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
|
||||
}
|
||||
|
||||
return parent::_delete($table);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* LIMIT
|
||||
*
|
||||
* Generates a platform-specific LIMIT clause
|
||||
*
|
||||
* @param string $sql SQL Query
|
||||
* @return string
|
||||
*/
|
||||
protected function _limit($sql)
|
||||
{
|
||||
$limit = $this->qb_offset + $this->qb_limit;
|
||||
|
||||
// As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported,
|
||||
// however an ORDER BY clause is required for it to work
|
||||
if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby))
|
||||
{
|
||||
$orderby = $this->_compile_order_by();
|
||||
|
||||
// We have to strip the ORDER BY clause
|
||||
$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
|
||||
|
||||
// Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
|
||||
if (count($this->qb_select) === 0 OR strpos(implode(',', $this->qb_select), '*') !== FALSE)
|
||||
{
|
||||
$select = '*'; // Inevitable
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use only field names and their aliases, everything else is out of our scope.
|
||||
$select = array();
|
||||
$field_regexp = ($this->_quoted_identifier)
|
||||
? '("[^\"]+")' : '(\[[^\]]+\])';
|
||||
for ($i = 0, $c = count($this->qb_select); $i < $c; $i++)
|
||||
{
|
||||
$select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m)
|
||||
? $m[1] : $this->qb_select[$i];
|
||||
}
|
||||
$select = implode(', ', $select);
|
||||
}
|
||||
|
||||
return 'SELECT '.$select." FROM (\n\n"
|
||||
.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
|
||||
."\n\n) ".$this->escape_identifiers('CI_subquery')
|
||||
."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
|
||||
}
|
||||
|
||||
return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert batch statement
|
||||
*
|
||||
* Generates a platform-specific insert string from the supplied data.
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @param array $keys INSERT keys
|
||||
* @param array $values INSERT values
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function _insert_batch($table, $keys, $values)
|
||||
{
|
||||
// Multiple-value inserts are only supported as of SQL Server 2008
|
||||
if (version_compare($this->version(), '10', '>='))
|
||||
{
|
||||
return parent::_insert_batch($table, $keys, $values);
|
||||
}
|
||||
|
||||
return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Close DB Connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
mssql_close($this->conn_id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MS SQL Forge Class
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mssql_forge extends CI_DB_forge {
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_table_if = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nCREATE TABLE";
|
||||
|
||||
/**
|
||||
* DROP TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_drop_table_if = "IF EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nDROP TABLE";
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_unsigned = array(
|
||||
'TINYINT' => 'SMALLINT',
|
||||
'SMALLINT' => 'INT',
|
||||
'INT' => 'BIGINT',
|
||||
'REAL' => 'FLOAT'
|
||||
);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alter_type ALTER type
|
||||
* @param string $table Table name
|
||||
* @param mixed $field Column definition
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function _alter_table($alter_type, $table, $field)
|
||||
{
|
||||
if (in_array($alter_type, array('ADD', 'DROP'), TRUE))
|
||||
{
|
||||
return parent::_alter_table($alter_type, $table, $field);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN ';
|
||||
$sqls = array();
|
||||
for ($i = 0, $c = count($field); $i < $c; $i++)
|
||||
{
|
||||
$sqls[] = $sql.$this->_process_column($field[$i]);
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field attribute TYPE
|
||||
*
|
||||
* Performs a data type mapping between different databases.
|
||||
*
|
||||
* @param array &$attributes
|
||||
* @return void
|
||||
*/
|
||||
protected function _attr_type(&$attributes)
|
||||
{
|
||||
if (isset($attributes['CONSTRAINT']) && strpos($attributes['TYPE'], 'INT') !== FALSE)
|
||||
{
|
||||
unset($attributes['CONSTRAINT']);
|
||||
}
|
||||
|
||||
switch (strtoupper($attributes['TYPE']))
|
||||
{
|
||||
case 'MEDIUMINT':
|
||||
$attributes['TYPE'] = 'INTEGER';
|
||||
$attributes['UNSIGNED'] = FALSE;
|
||||
return;
|
||||
case 'INTEGER':
|
||||
$attributes['TYPE'] = 'INT';
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*
|
||||
* @param array &$attributes
|
||||
* @param array &$field
|
||||
* @return void
|
||||
*/
|
||||
protected function _attr_auto_increment(&$attributes, &$field)
|
||||
{
|
||||
if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
|
||||
{
|
||||
$field['auto_increment'] = ' IDENTITY(1,1)';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MSSQL Result Class
|
||||
*
|
||||
* This class extends the parent result class: CI_DB_result
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mssql_result extends CI_DB_result {
|
||||
|
||||
/**
|
||||
* Number of rows in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_rows()
|
||||
{
|
||||
return is_int($this->num_rows)
|
||||
? $this->num_rows
|
||||
: $this->num_rows = mssql_num_rows($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
return mssql_num_fields($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
$field_names = array();
|
||||
mssql_field_seek($this->result_id, 0);
|
||||
while ($field = mssql_fetch_field($this->result_id))
|
||||
{
|
||||
$field_names[] = $field->name;
|
||||
}
|
||||
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
$retval = array();
|
||||
for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
|
||||
{
|
||||
$field = mssql_fetch_field($this->result_id, $i);
|
||||
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $field->name;
|
||||
$retval[$i]->type = $field->type;
|
||||
$retval[$i]->max_length = $field->max_length;
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
if (is_resource($this->result_id))
|
||||
{
|
||||
mssql_free_result($this->result_id);
|
||||
$this->result_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Data Seek
|
||||
*
|
||||
* Moves the internal pointer to the desired offset. We call
|
||||
* this internally before fetching results to make sure the
|
||||
* result set starts at zero.
|
||||
*
|
||||
* @param int $n
|
||||
* @return bool
|
||||
*/
|
||||
public function data_seek($n = 0)
|
||||
{
|
||||
return mssql_data_seek($this->result_id, $n);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
return mssql_fetch_assoc($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
$row = mssql_fetch_object($this->result_id);
|
||||
|
||||
if ($class_name === 'stdClass' OR ! $row)
|
||||
{
|
||||
return $row;
|
||||
}
|
||||
|
||||
$class_name = new $class_name();
|
||||
foreach ($row as $key => $value)
|
||||
{
|
||||
$class_name->$key = $value;
|
||||
}
|
||||
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MS SQL Utility Class
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mssql_utility extends CI_DB_utility {
|
||||
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_list_databases = 'EXEC sp_helpdb'; // Can also be: EXEC sp_databases
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_optimize_table = 'ALTER INDEX all ON %s REORGANIZE';
|
||||
|
||||
/**
|
||||
* Export
|
||||
*
|
||||
* @param array $params Preferences
|
||||
* @return bool
|
||||
*/
|
||||
protected function _backup($params = array())
|
||||
{
|
||||
// Currently unsupported
|
||||
return $this->db->display_error('db_unsupported_feature');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,495 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MySQL Database Adapter Class
|
||||
*
|
||||
* Note: _DB is an extender class that the app controller
|
||||
* creates dynamically based on whether the query builder
|
||||
* class is being used or not.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mysql_driver extends CI_DB {
|
||||
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $dbdriver = 'mysql';
|
||||
|
||||
/**
|
||||
* Compression flag
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $compress = FALSE;
|
||||
|
||||
/**
|
||||
* DELETE hack flag
|
||||
*
|
||||
* Whether to use the MySQL "delete hack" which allows the number
|
||||
* of affected rows to be shown. Uses a preg_replace when enabled,
|
||||
* adding a bit more processing to all queries.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $delete_hack = TRUE;
|
||||
|
||||
/**
|
||||
* Strict ON flag
|
||||
*
|
||||
* Whether we're running in strict SQL mode.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $stricton;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_escape_char = '`';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
if ( ! empty($this->port))
|
||||
{
|
||||
$this->hostname .= ':'.$this->port;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Non-persistent database connection
|
||||
*
|
||||
* @param bool $persistent
|
||||
* @return resource
|
||||
*/
|
||||
public function db_connect($persistent = FALSE)
|
||||
{
|
||||
$client_flags = ($this->compress === FALSE) ? 0 : MYSQL_CLIENT_COMPRESS;
|
||||
|
||||
if ($this->encrypt === TRUE)
|
||||
{
|
||||
$client_flags = $client_flags | MYSQL_CLIENT_SSL;
|
||||
}
|
||||
|
||||
// Error suppression is necessary mostly due to PHP 5.5+ issuing E_DEPRECATED messages
|
||||
$this->conn_id = ($persistent === TRUE)
|
||||
? mysql_pconnect($this->hostname, $this->username, $this->password, $client_flags)
|
||||
: mysql_connect($this->hostname, $this->username, $this->password, TRUE, $client_flags);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// Select the DB... assuming a database name is specified in the config file
|
||||
if ($this->database !== '' && ! $this->db_select())
|
||||
{
|
||||
log_message('error', 'Unable to select database: '.$this->database);
|
||||
|
||||
return ($this->db_debug === TRUE)
|
||||
? $this->display_error('db_unable_to_select', $this->database)
|
||||
: FALSE;
|
||||
}
|
||||
|
||||
if (isset($this->stricton) && is_resource($this->conn_id))
|
||||
{
|
||||
if ($this->stricton)
|
||||
{
|
||||
$this->simple_query('SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->simple_query(
|
||||
'SET SESSION sql_mode =
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||
@@sql_mode,
|
||||
"STRICT_ALL_TABLES,", ""),
|
||||
",STRICT_ALL_TABLES", ""),
|
||||
"STRICT_ALL_TABLES", ""),
|
||||
"STRICT_TRANS_TABLES,", ""),
|
||||
",STRICT_TRANS_TABLES", ""),
|
||||
"STRICT_TRANS_TABLES", "")'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->conn_id;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reconnect
|
||||
*
|
||||
* Keep / reestablish the db connection if no queries have been
|
||||
* sent for a length of time exceeding the server's idle timeout
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
if (mysql_ping($this->conn_id) === FALSE)
|
||||
{
|
||||
$this->conn_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Select the database
|
||||
*
|
||||
* @param string $database
|
||||
* @return bool
|
||||
*/
|
||||
public function db_select($database = '')
|
||||
{
|
||||
if ($database === '')
|
||||
{
|
||||
$database = $this->database;
|
||||
}
|
||||
|
||||
if (mysql_select_db($database, $this->conn_id))
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->data_cache = array();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set client character set
|
||||
*
|
||||
* @param string $charset
|
||||
* @return bool
|
||||
*/
|
||||
protected function _db_set_charset($charset)
|
||||
{
|
||||
return mysql_set_charset($charset, $this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database version number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
if (isset($this->data_cache['version']))
|
||||
{
|
||||
return $this->data_cache['version'];
|
||||
}
|
||||
|
||||
if ( ! $this->conn_id OR ($version = mysql_get_server_info($this->conn_id)) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return $this->data_cache['version'] = $version;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _execute($sql)
|
||||
{
|
||||
return mysql_query($this->_prep_query($sql), $this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Prep the query
|
||||
*
|
||||
* If needed, each database adapter can prep the query string
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return string
|
||||
*/
|
||||
protected function _prep_query($sql)
|
||||
{
|
||||
// mysql_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack
|
||||
// modifies the query so that it a proper number of affected rows is returned.
|
||||
if ($this->delete_hack === TRUE && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql))
|
||||
{
|
||||
return trim($sql).' WHERE 1=1';
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_begin()
|
||||
{
|
||||
$this->simple_query('SET AUTOCOMMIT=0');
|
||||
return $this->simple_query('START TRANSACTION'); // can also be BEGIN or BEGIN WORK
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_commit()
|
||||
{
|
||||
if ($this->simple_query('COMMIT'))
|
||||
{
|
||||
$this->simple_query('SET AUTOCOMMIT=1');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_rollback()
|
||||
{
|
||||
if ($this->simple_query('ROLLBACK'))
|
||||
{
|
||||
$this->simple_query('SET AUTOCOMMIT=1');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Platform-dependent string escape
|
||||
*
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
protected function _escape_str($str)
|
||||
{
|
||||
return mysql_real_escape_string($str, $this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Affected Rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function affected_rows()
|
||||
{
|
||||
return mysql_affected_rows($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function insert_id()
|
||||
{
|
||||
return mysql_insert_id($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* List table query
|
||||
*
|
||||
* Generates a platform-specific query string so that the table names can be fetched
|
||||
*
|
||||
* @param bool $prefix_limit
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_tables($prefix_limit = FALSE)
|
||||
{
|
||||
$sql = 'SHOW TABLES FROM '.$this->_escape_char.$this->database.$this->_escape_char;
|
||||
|
||||
if ($prefix_limit !== FALSE && $this->dbprefix !== '')
|
||||
{
|
||||
return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show column query
|
||||
*
|
||||
* Generates a platform-specific query string so that the column names can be fetched
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_columns($table = '')
|
||||
{
|
||||
return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns an object with field data
|
||||
*
|
||||
* @param string $table
|
||||
* @return array
|
||||
*/
|
||||
public function field_data($table)
|
||||
{
|
||||
if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
$query = $query->result_object();
|
||||
|
||||
$retval = array();
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $query[$i]->Field;
|
||||
|
||||
sscanf($query[$i]->Type, '%[a-z](%d)',
|
||||
$retval[$i]->type,
|
||||
$retval[$i]->max_length
|
||||
);
|
||||
|
||||
$retval[$i]->default = $query[$i]->Default;
|
||||
$retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI');
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* Returns an array containing code and message of the last
|
||||
* database error that has occurred.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
return array('code' => mysql_errno($this->conn_id), 'message' => mysql_error($this->conn_id));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* FROM tables
|
||||
*
|
||||
* Groups tables in FROM clauses if needed, so there is no confusion
|
||||
* about operator precedence.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _from_tables()
|
||||
{
|
||||
if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
|
||||
{
|
||||
return '('.implode(', ', $this->qb_from).')';
|
||||
}
|
||||
|
||||
return implode(', ', $this->qb_from);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Close DB Connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
// Error suppression to avoid annoying E_WARNINGs in cases
|
||||
// where the connection has already been closed for some reason.
|
||||
@mysql_close($this->conn_id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MySQL Forge Class
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mysql_forge extends CI_DB_forge {
|
||||
|
||||
/**
|
||||
* CREATE DATABASE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_database = 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s';
|
||||
|
||||
/**
|
||||
* CREATE TABLE keys flag
|
||||
*
|
||||
* Whether table keys are created from within the
|
||||
* CREATE TABLE statement.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_create_table_keys = TRUE;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_unsigned = array(
|
||||
'TINYINT',
|
||||
'SMALLINT',
|
||||
'MEDIUMINT',
|
||||
'INT',
|
||||
'INTEGER',
|
||||
'BIGINT',
|
||||
'REAL',
|
||||
'DOUBLE',
|
||||
'DOUBLE PRECISION',
|
||||
'FLOAT',
|
||||
'DECIMAL',
|
||||
'NUMERIC'
|
||||
);
|
||||
|
||||
/**
|
||||
* NULL value representation in CREATE/ALTER TABLE statements
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_null = 'NULL';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* CREATE TABLE attributes
|
||||
*
|
||||
* @param array $attributes Associative array of table attributes
|
||||
* @return string
|
||||
*/
|
||||
protected function _create_table_attr($attributes)
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
foreach (array_keys($attributes) as $key)
|
||||
{
|
||||
if (is_string($key))
|
||||
{
|
||||
$sql .= ' '.strtoupper($key).' = '.$attributes[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty($this->db->char_set) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET'))
|
||||
{
|
||||
$sql .= ' DEFAULT CHARACTER SET = '.$this->db->char_set;
|
||||
}
|
||||
|
||||
if ( ! empty($this->db->dbcollat) && ! strpos($sql, 'COLLATE'))
|
||||
{
|
||||
$sql .= ' COLLATE = '.$this->db->dbcollat;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alter_type ALTER type
|
||||
* @param string $table Table name
|
||||
* @param mixed $field Column definition
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function _alter_table($alter_type, $table, $field)
|
||||
{
|
||||
if ($alter_type === 'DROP')
|
||||
{
|
||||
return parent::_alter_table($alter_type, $table, $field);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
|
||||
for ($i = 0, $c = count($field); $i < $c; $i++)
|
||||
{
|
||||
if ($field[$i]['_literal'] !== FALSE)
|
||||
{
|
||||
$field[$i] = ($alter_type === 'ADD')
|
||||
? "\n\tADD ".$field[$i]['_literal']
|
||||
: "\n\tMODIFY ".$field[$i]['_literal'];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($alter_type === 'ADD')
|
||||
{
|
||||
$field[$i]['_literal'] = "\n\tADD ";
|
||||
}
|
||||
else
|
||||
{
|
||||
$field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE ";
|
||||
}
|
||||
|
||||
$field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return array($sql.implode(',', $field));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*
|
||||
* @param array $field
|
||||
* @return string
|
||||
*/
|
||||
protected function _process_column($field)
|
||||
{
|
||||
$extra_clause = isset($field['after'])
|
||||
? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
|
||||
|
||||
if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
|
||||
{
|
||||
$extra_clause = ' FIRST';
|
||||
}
|
||||
|
||||
return $this->db->escape_identifiers($field['name'])
|
||||
.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
|
||||
.' '.$field['type'].$field['length']
|
||||
.$field['unsigned']
|
||||
.$field['null']
|
||||
.$field['default']
|
||||
.$field['auto_increment']
|
||||
.$field['unique']
|
||||
.(empty($field['comment']) ? '' : ' COMMENT '.$field['comment'])
|
||||
.$extra_clause;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process indexes
|
||||
*
|
||||
* @param string $table (ignored)
|
||||
* @return string
|
||||
*/
|
||||
protected function _process_indexes($table)
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
for ($i = 0, $c = count($this->keys); $i < $c; $i++)
|
||||
{
|
||||
if (is_array($this->keys[$i]))
|
||||
{
|
||||
for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
|
||||
{
|
||||
if ( ! isset($this->fields[$this->keys[$i][$i2]]))
|
||||
{
|
||||
unset($this->keys[$i][$i2]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ( ! isset($this->fields[$this->keys[$i]]))
|
||||
{
|
||||
unset($this->keys[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
|
||||
|
||||
$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
|
||||
.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
|
||||
}
|
||||
|
||||
$this->keys = array();
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MySQL Result Class
|
||||
*
|
||||
* This class extends the parent result class: CI_DB_result
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mysql_result extends CI_DB_result {
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param object &$driver_object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(&$driver_object)
|
||||
{
|
||||
parent::__construct($driver_object);
|
||||
|
||||
// Required, due to mysql_data_seek() causing nightmares
|
||||
// with empty result sets
|
||||
$this->num_rows = mysql_num_rows($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of rows in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_rows()
|
||||
{
|
||||
return $this->num_rows;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
return mysql_num_fields($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
$field_names = array();
|
||||
mysql_field_seek($this->result_id, 0);
|
||||
while ($field = mysql_fetch_field($this->result_id))
|
||||
{
|
||||
$field_names[] = $field->name;
|
||||
}
|
||||
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
$retval = array();
|
||||
for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = mysql_field_name($this->result_id, $i);
|
||||
$retval[$i]->type = mysql_field_type($this->result_id, $i);
|
||||
$retval[$i]->max_length = mysql_field_len($this->result_id, $i);
|
||||
$retval[$i]->primary_key = (int) (strpos(mysql_field_flags($this->result_id, $i), 'primary_key') !== FALSE);
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
if (is_resource($this->result_id))
|
||||
{
|
||||
mysql_free_result($this->result_id);
|
||||
$this->result_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Data Seek
|
||||
*
|
||||
* Moves the internal pointer to the desired offset. We call
|
||||
* this internally before fetching results to make sure the
|
||||
* result set starts at zero.
|
||||
*
|
||||
* @param int $n
|
||||
* @return bool
|
||||
*/
|
||||
public function data_seek($n = 0)
|
||||
{
|
||||
return $this->num_rows
|
||||
? mysql_data_seek($this->result_id, $n)
|
||||
: FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
return mysql_fetch_assoc($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
return mysql_fetch_object($this->result_id, $class_name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.0.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MySQL Utility Class
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mysql_utility extends CI_DB_utility {
|
||||
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_list_databases = 'SHOW DATABASES';
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_optimize_table = 'OPTIMIZE TABLE %s';
|
||||
|
||||
/**
|
||||
* REPAIR TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_repair_table = 'REPAIR TABLE %s';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Export
|
||||
*
|
||||
* @param array $params Preferences
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _backup($params = array())
|
||||
{
|
||||
if (count($params) === 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Extract the prefs for simplicity
|
||||
extract($params);
|
||||
|
||||
// Build the output
|
||||
$output = '';
|
||||
|
||||
// Do we need to include a statement to disable foreign key checks?
|
||||
if ($foreign_key_checks === FALSE)
|
||||
{
|
||||
$output .= 'SET foreign_key_checks = 0;'.$newline;
|
||||
}
|
||||
|
||||
foreach ( (array) $tables as $table)
|
||||
{
|
||||
// Is the table in the "ignore" list?
|
||||
if (in_array($table, (array) $ignore, TRUE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the table schema
|
||||
$query = $this->db->query('SHOW CREATE TABLE '.$this->db->escape_identifiers($this->db->database.'.'.$table));
|
||||
|
||||
// No result means the table name was invalid
|
||||
if ($query === FALSE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Write out the table schema
|
||||
$output .= '#'.$newline.'# TABLE STRUCTURE FOR: '.$table.$newline.'#'.$newline.$newline;
|
||||
|
||||
if ($add_drop === TRUE)
|
||||
{
|
||||
$output .= 'DROP TABLE IF EXISTS '.$this->db->protect_identifiers($table).';'.$newline.$newline;
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$result = $query->result_array();
|
||||
foreach ($result[0] as $val)
|
||||
{
|
||||
if ($i++ % 2)
|
||||
{
|
||||
$output .= $val.';'.$newline.$newline;
|
||||
}
|
||||
}
|
||||
|
||||
// If inserts are not needed we're done...
|
||||
if ($add_insert === FALSE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grab all the data from the current table
|
||||
$query = $this->db->query('SELECT * FROM '.$this->db->protect_identifiers($table));
|
||||
|
||||
if ($query->num_rows() === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch the field names and determine if the field is an
|
||||
// integer type. We use this info to decide whether to
|
||||
// surround the data with quotes or not
|
||||
|
||||
$i = 0;
|
||||
$field_str = '';
|
||||
$is_int = array();
|
||||
while ($field = mysql_fetch_field($query->result_id))
|
||||
{
|
||||
// Most versions of MySQL store timestamp as a string
|
||||
$is_int[$i] = in_array(strtolower(mysql_field_type($query->result_id, $i)),
|
||||
array('tinyint', 'smallint', 'mediumint', 'int', 'bigint'), //, 'timestamp'),
|
||||
TRUE);
|
||||
|
||||
// Create a string of field names
|
||||
$field_str .= $this->db->escape_identifiers($field->name).', ';
|
||||
$i++;
|
||||
}
|
||||
|
||||
// Trim off the end comma
|
||||
$field_str = preg_replace('/, $/' , '', $field_str);
|
||||
|
||||
// Build the insert string
|
||||
foreach ($query->result_array() as $row)
|
||||
{
|
||||
$val_str = '';
|
||||
|
||||
$i = 0;
|
||||
foreach ($row as $v)
|
||||
{
|
||||
// Is the value NULL?
|
||||
if ($v === NULL)
|
||||
{
|
||||
$val_str .= 'NULL';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Escape the data if it's not an integer
|
||||
$val_str .= ($is_int[$i] === FALSE) ? $this->db->escape($v) : $v;
|
||||
}
|
||||
|
||||
// Append a comma
|
||||
$val_str .= ', ';
|
||||
$i++;
|
||||
}
|
||||
|
||||
// Remove the comma at the end of the string
|
||||
$val_str = preg_replace('/, $/' , '', $val_str);
|
||||
|
||||
// Build the INSERT string
|
||||
$output .= 'INSERT INTO '.$this->db->protect_identifiers($table).' ('.$field_str.') VALUES ('.$val_str.');'.$newline;
|
||||
}
|
||||
|
||||
$output .= $newline.$newline;
|
||||
}
|
||||
|
||||
// Do we need to include a statement to re-enable foreign key checks?
|
||||
if ($foreign_key_checks === FALSE)
|
||||
{
|
||||
$output .= 'SET foreign_key_checks = 1;'.$newline;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,554 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MySQLi Database Adapter Class
|
||||
*
|
||||
* Note: _DB is an extender class that the app controller
|
||||
* creates dynamically based on whether the query builder
|
||||
* class is being used or not.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mysqli_driver extends CI_DB {
|
||||
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $dbdriver = 'mysqli';
|
||||
|
||||
/**
|
||||
* Compression flag
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $compress = FALSE;
|
||||
|
||||
/**
|
||||
* DELETE hack flag
|
||||
*
|
||||
* Whether to use the MySQL "delete hack" which allows the number
|
||||
* of affected rows to be shown. Uses a preg_replace when enabled,
|
||||
* adding a bit more processing to all queries.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $delete_hack = TRUE;
|
||||
|
||||
/**
|
||||
* Strict ON flag
|
||||
*
|
||||
* Whether we're running in strict SQL mode.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $stricton;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_escape_char = '`';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* MySQLi object
|
||||
*
|
||||
* Has to be preserved without being assigned to $conn_id.
|
||||
*
|
||||
* @var MySQLi
|
||||
*/
|
||||
protected $_mysqli;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database connection
|
||||
*
|
||||
* @param bool $persistent
|
||||
* @return object
|
||||
*/
|
||||
public function db_connect($persistent = FALSE)
|
||||
{
|
||||
// PHP 8.1 changes default error handling mode from silent to exceptions - reverse that
|
||||
if (is_php('8.1'))
|
||||
{
|
||||
$mysqli_driver = new mysqli_driver();
|
||||
$mysqli_driver->report_mode = MYSQLI_REPORT_OFF;
|
||||
}
|
||||
|
||||
// Do we have a socket path?
|
||||
if ($this->hostname[0] === '/')
|
||||
{
|
||||
$hostname = NULL;
|
||||
$port = NULL;
|
||||
$socket = $this->hostname;
|
||||
}
|
||||
else
|
||||
{
|
||||
$hostname = ($persistent === TRUE)
|
||||
? 'p:'.$this->hostname : $this->hostname;
|
||||
$port = empty($this->port) ? NULL : $this->port;
|
||||
$socket = NULL;
|
||||
}
|
||||
|
||||
$client_flags = ($this->compress === TRUE) ? MYSQLI_CLIENT_COMPRESS : 0;
|
||||
$this->_mysqli = mysqli_init();
|
||||
|
||||
$this->_mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 10);
|
||||
|
||||
if (isset($this->stricton))
|
||||
{
|
||||
if ($this->stricton)
|
||||
{
|
||||
$this->_mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_mysqli->options(MYSQLI_INIT_COMMAND,
|
||||
'SET SESSION sql_mode =
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||
@@sql_mode,
|
||||
"STRICT_ALL_TABLES,", ""),
|
||||
",STRICT_ALL_TABLES", ""),
|
||||
"STRICT_ALL_TABLES", ""),
|
||||
"STRICT_TRANS_TABLES,", ""),
|
||||
",STRICT_TRANS_TABLES", ""),
|
||||
"STRICT_TRANS_TABLES", "")'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($this->encrypt))
|
||||
{
|
||||
$ssl = array();
|
||||
empty($this->encrypt['ssl_key']) OR $ssl['key'] = $this->encrypt['ssl_key'];
|
||||
empty($this->encrypt['ssl_cert']) OR $ssl['cert'] = $this->encrypt['ssl_cert'];
|
||||
empty($this->encrypt['ssl_ca']) OR $ssl['ca'] = $this->encrypt['ssl_ca'];
|
||||
empty($this->encrypt['ssl_capath']) OR $ssl['capath'] = $this->encrypt['ssl_capath'];
|
||||
empty($this->encrypt['ssl_cipher']) OR $ssl['cipher'] = $this->encrypt['ssl_cipher'];
|
||||
|
||||
if (isset($this->encrypt['ssl_verify']))
|
||||
{
|
||||
$client_flags |= MYSQLI_CLIENT_SSL;
|
||||
|
||||
if ($this->encrypt['ssl_verify'])
|
||||
{
|
||||
defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT') && $this->_mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, TRUE);
|
||||
}
|
||||
// Apparently (when it exists), setting MYSQLI_OPT_SSL_VERIFY_SERVER_CERT
|
||||
// to FALSE didn't do anything, so PHP 5.6.16 introduced yet another
|
||||
// constant ...
|
||||
//
|
||||
// https://secure.php.net/ChangeLog-5.php#5.6.16
|
||||
// https://bugs.php.net/bug.php?id=68344
|
||||
elseif (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT'))
|
||||
{
|
||||
$client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty($ssl))
|
||||
{
|
||||
$client_flags |= MYSQLI_CLIENT_SSL;
|
||||
$this->_mysqli->ssl_set(
|
||||
isset($ssl['key']) ? $ssl['key'] : NULL,
|
||||
isset($ssl['cert']) ? $ssl['cert'] : NULL,
|
||||
isset($ssl['ca']) ? $ssl['ca'] : NULL,
|
||||
isset($ssl['capath']) ? $ssl['capath'] : NULL,
|
||||
isset($ssl['cipher']) ? $ssl['cipher'] : NULL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, $socket, $client_flags))
|
||||
{
|
||||
// Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails
|
||||
if (
|
||||
($client_flags & MYSQLI_CLIENT_SSL)
|
||||
&& version_compare($this->_mysqli->client_info, '5.7.3', '<=')
|
||||
&& empty($this->_mysqli->query("SHOW STATUS LIKE 'ssl_cipher'")->fetch_object()->Value)
|
||||
)
|
||||
{
|
||||
$this->_mysqli->close();
|
||||
$message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!';
|
||||
log_message('error', $message);
|
||||
return ($this->db_debug) ? $this->display_error($message, '', TRUE) : FALSE;
|
||||
}
|
||||
|
||||
return $this->_mysqli;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reconnect
|
||||
*
|
||||
* Keep / reestablish the db connection if no queries have been
|
||||
* sent for a length of time exceeding the server's idle timeout
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
if ($this->conn_id !== FALSE && $this->conn_id->ping() === FALSE)
|
||||
{
|
||||
$this->conn_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Select the database
|
||||
*
|
||||
* @param string $database
|
||||
* @return bool
|
||||
*/
|
||||
public function db_select($database = '')
|
||||
{
|
||||
if ($database === '')
|
||||
{
|
||||
$database = $this->database;
|
||||
}
|
||||
|
||||
if ($this->conn_id->select_db($database))
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->data_cache = array();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set client character set
|
||||
*
|
||||
* @param string $charset
|
||||
* @return bool
|
||||
*/
|
||||
protected function _db_set_charset($charset)
|
||||
{
|
||||
return $this->conn_id->set_charset($charset);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database version number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
if (isset($this->data_cache['version']))
|
||||
{
|
||||
return $this->data_cache['version'];
|
||||
}
|
||||
|
||||
return $this->data_cache['version'] = $this->conn_id->server_info;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _execute($sql)
|
||||
{
|
||||
return $this->conn_id->query($this->_prep_query($sql));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Prep the query
|
||||
*
|
||||
* If needed, each database adapter can prep the query string
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return string
|
||||
*/
|
||||
protected function _prep_query($sql)
|
||||
{
|
||||
// mysqli_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack
|
||||
// modifies the query so that it a proper number of affected rows is returned.
|
||||
if ($this->delete_hack === TRUE && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql))
|
||||
{
|
||||
return trim($sql).' WHERE 1=1';
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_begin()
|
||||
{
|
||||
$this->conn_id->autocommit(FALSE);
|
||||
return is_php('5.5')
|
||||
? $this->conn_id->begin_transaction()
|
||||
: $this->simple_query('START TRANSACTION'); // can also be BEGIN or BEGIN WORK
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_commit()
|
||||
{
|
||||
if ($this->conn_id->commit())
|
||||
{
|
||||
$this->conn_id->autocommit(TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_rollback()
|
||||
{
|
||||
if ($this->conn_id->rollback())
|
||||
{
|
||||
$this->conn_id->autocommit(TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Platform-dependent string escape
|
||||
*
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
protected function _escape_str($str)
|
||||
{
|
||||
return $this->conn_id->real_escape_string($str);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Affected Rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function affected_rows()
|
||||
{
|
||||
return $this->conn_id->affected_rows;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function insert_id()
|
||||
{
|
||||
return $this->conn_id->insert_id;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* List table query
|
||||
*
|
||||
* Generates a platform-specific query string so that the table names can be fetched
|
||||
*
|
||||
* @param bool $prefix_limit
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_tables($prefix_limit = FALSE)
|
||||
{
|
||||
$sql = 'SHOW TABLES FROM '.$this->_escape_char.$this->database.$this->_escape_char;
|
||||
|
||||
if ($prefix_limit !== FALSE && $this->dbprefix !== '')
|
||||
{
|
||||
return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show column query
|
||||
*
|
||||
* Generates a platform-specific query string so that the column names can be fetched
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_columns($table = '')
|
||||
{
|
||||
return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns an object with field data
|
||||
*
|
||||
* @param string $table
|
||||
* @return array
|
||||
*/
|
||||
public function field_data($table)
|
||||
{
|
||||
if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
$query = $query->result_object();
|
||||
|
||||
$retval = array();
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $query[$i]->Field;
|
||||
|
||||
sscanf($query[$i]->Type, '%[a-z](%d)',
|
||||
$retval[$i]->type,
|
||||
$retval[$i]->max_length
|
||||
);
|
||||
|
||||
$retval[$i]->default = $query[$i]->Default;
|
||||
$retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI');
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* Returns an array containing code and message of the last
|
||||
* database error that has occurred.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
if ( ! empty($this->_mysqli->connect_errno))
|
||||
{
|
||||
return array(
|
||||
'code' => $this->_mysqli->connect_errno,
|
||||
'message' => $this->_mysqli->connect_error
|
||||
);
|
||||
}
|
||||
|
||||
return array('code' => $this->conn_id->errno, 'message' => $this->conn_id->error);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* FROM tables
|
||||
*
|
||||
* Groups tables in FROM clauses if needed, so there is no confusion
|
||||
* about operator precedence.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _from_tables()
|
||||
{
|
||||
if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
|
||||
{
|
||||
return '('.implode(', ', $this->qb_from).')';
|
||||
}
|
||||
|
||||
return implode(', ', $this->qb_from);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Close DB Connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
$this->conn_id->close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MySQLi Forge Class
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mysqli_forge extends CI_DB_forge {
|
||||
|
||||
/**
|
||||
* CREATE DATABASE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_database = 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s';
|
||||
|
||||
/**
|
||||
* CREATE TABLE keys flag
|
||||
*
|
||||
* Whether table keys are created from within the
|
||||
* CREATE TABLE statement.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_create_table_keys = TRUE;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_unsigned = array(
|
||||
'TINYINT',
|
||||
'SMALLINT',
|
||||
'MEDIUMINT',
|
||||
'INT',
|
||||
'INTEGER',
|
||||
'BIGINT',
|
||||
'REAL',
|
||||
'DOUBLE',
|
||||
'DOUBLE PRECISION',
|
||||
'FLOAT',
|
||||
'DECIMAL',
|
||||
'NUMERIC'
|
||||
);
|
||||
|
||||
/**
|
||||
* NULL value representation in CREATE/ALTER TABLE statements
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_null = 'NULL';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* CREATE TABLE attributes
|
||||
*
|
||||
* @param array $attributes Associative array of table attributes
|
||||
* @return string
|
||||
*/
|
||||
protected function _create_table_attr($attributes)
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
foreach (array_keys($attributes) as $key)
|
||||
{
|
||||
if (is_string($key))
|
||||
{
|
||||
$sql .= ' '.strtoupper($key).' = '.$attributes[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty($this->db->char_set) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET'))
|
||||
{
|
||||
$sql .= ' DEFAULT CHARACTER SET = '.$this->db->char_set;
|
||||
}
|
||||
|
||||
if ( ! empty($this->db->dbcollat) && ! strpos($sql, 'COLLATE'))
|
||||
{
|
||||
$sql .= ' COLLATE = '.$this->db->dbcollat;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alter_type ALTER type
|
||||
* @param string $table Table name
|
||||
* @param mixed $field Column definition
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function _alter_table($alter_type, $table, $field)
|
||||
{
|
||||
if ($alter_type === 'DROP')
|
||||
{
|
||||
return parent::_alter_table($alter_type, $table, $field);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
|
||||
for ($i = 0, $c = count($field); $i < $c; $i++)
|
||||
{
|
||||
if ($field[$i]['_literal'] !== FALSE)
|
||||
{
|
||||
$field[$i] = ($alter_type === 'ADD')
|
||||
? "\n\tADD ".$field[$i]['_literal']
|
||||
: "\n\tMODIFY ".$field[$i]['_literal'];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($alter_type === 'ADD')
|
||||
{
|
||||
$field[$i]['_literal'] = "\n\tADD ";
|
||||
}
|
||||
else
|
||||
{
|
||||
$field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE ";
|
||||
}
|
||||
|
||||
$field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return array($sql.implode(',', $field));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*
|
||||
* @param array $field
|
||||
* @return string
|
||||
*/
|
||||
protected function _process_column($field)
|
||||
{
|
||||
$extra_clause = isset($field['after'])
|
||||
? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
|
||||
|
||||
if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
|
||||
{
|
||||
$extra_clause = ' FIRST';
|
||||
}
|
||||
|
||||
return $this->db->escape_identifiers($field['name'])
|
||||
.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
|
||||
.' '.$field['type'].$field['length']
|
||||
.$field['unsigned']
|
||||
.$field['null']
|
||||
.$field['default']
|
||||
.$field['auto_increment']
|
||||
.$field['unique']
|
||||
.(empty($field['comment']) ? '' : ' COMMENT '.$field['comment'])
|
||||
.$extra_clause;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process indexes
|
||||
*
|
||||
* @param string $table (ignored)
|
||||
* @return string
|
||||
*/
|
||||
protected function _process_indexes($table)
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
for ($i = 0, $c = count($this->keys); $i < $c; $i++)
|
||||
{
|
||||
if (is_array($this->keys[$i]))
|
||||
{
|
||||
for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
|
||||
{
|
||||
if ( ! isset($this->fields[$this->keys[$i][$i2]]))
|
||||
{
|
||||
unset($this->keys[$i][$i2]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ( ! isset($this->fields[$this->keys[$i]]))
|
||||
{
|
||||
unset($this->keys[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
|
||||
|
||||
$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
|
||||
.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
|
||||
}
|
||||
|
||||
$this->keys = array();
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MySQLi Result Class
|
||||
*
|
||||
* This class extends the parent result class: CI_DB_result
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mysqli_result extends CI_DB_result {
|
||||
|
||||
/**
|
||||
* Number of rows in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_rows()
|
||||
{
|
||||
return is_int($this->num_rows)
|
||||
? $this->num_rows
|
||||
: $this->num_rows = $this->result_id->num_rows;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
return $this->result_id->field_count;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
$field_names = array();
|
||||
$this->result_id->field_seek(0);
|
||||
while ($field = $this->result_id->fetch_field())
|
||||
{
|
||||
$field_names[] = $field->name;
|
||||
}
|
||||
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
$retval = array();
|
||||
$field_data = $this->result_id->fetch_fields();
|
||||
for ($i = 0, $c = count($field_data); $i < $c; $i++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $field_data[$i]->name;
|
||||
$retval[$i]->type = static::_get_field_type($field_data[$i]->type);
|
||||
$retval[$i]->max_length = $field_data[$i]->max_length;
|
||||
$retval[$i]->primary_key = (int) ($field_data[$i]->flags & MYSQLI_PRI_KEY_FLAG);
|
||||
$retval[$i]->default = $field_data[$i]->def;
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get field type
|
||||
*
|
||||
* Extracts field type info from the bitflags returned by
|
||||
* mysqli_result::fetch_fields()
|
||||
*
|
||||
* @used-by CI_DB_mysqli_result::field_data()
|
||||
* @param int $type
|
||||
* @return string
|
||||
*/
|
||||
private static function _get_field_type($type)
|
||||
{
|
||||
static $map;
|
||||
isset($map) OR $map = array(
|
||||
MYSQLI_TYPE_DECIMAL => 'decimal',
|
||||
MYSQLI_TYPE_BIT => 'bit',
|
||||
MYSQLI_TYPE_TINY => 'tinyint',
|
||||
MYSQLI_TYPE_SHORT => 'smallint',
|
||||
MYSQLI_TYPE_INT24 => 'mediumint',
|
||||
MYSQLI_TYPE_LONG => 'int',
|
||||
MYSQLI_TYPE_LONGLONG => 'bigint',
|
||||
MYSQLI_TYPE_FLOAT => 'float',
|
||||
MYSQLI_TYPE_DOUBLE => 'double',
|
||||
MYSQLI_TYPE_TIMESTAMP => 'timestamp',
|
||||
MYSQLI_TYPE_DATE => 'date',
|
||||
MYSQLI_TYPE_TIME => 'time',
|
||||
MYSQLI_TYPE_DATETIME => 'datetime',
|
||||
MYSQLI_TYPE_YEAR => 'year',
|
||||
MYSQLI_TYPE_NEWDATE => 'date',
|
||||
MYSQLI_TYPE_INTERVAL => 'interval',
|
||||
MYSQLI_TYPE_ENUM => 'enum',
|
||||
MYSQLI_TYPE_SET => 'set',
|
||||
MYSQLI_TYPE_TINY_BLOB => 'tinyblob',
|
||||
MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob',
|
||||
MYSQLI_TYPE_BLOB => 'blob',
|
||||
MYSQLI_TYPE_LONG_BLOB => 'longblob',
|
||||
MYSQLI_TYPE_STRING => 'char',
|
||||
MYSQLI_TYPE_VAR_STRING => 'varchar',
|
||||
MYSQLI_TYPE_GEOMETRY => 'geometry'
|
||||
);
|
||||
|
||||
return isset($map[$type]) ? $map[$type] : $type;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
if (is_object($this->result_id))
|
||||
{
|
||||
$this->result_id->free();
|
||||
$this->result_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Data Seek
|
||||
*
|
||||
* Moves the internal pointer to the desired offset. We call
|
||||
* this internally before fetching results to make sure the
|
||||
* result set starts at zero.
|
||||
*
|
||||
* @param int $n
|
||||
* @return bool
|
||||
*/
|
||||
public function data_seek($n = 0)
|
||||
{
|
||||
return $this->result_id->data_seek($n);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
return $this->result_id->fetch_assoc();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
return $this->result_id->fetch_object($class_name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* MySQLi Utility Class
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_mysqli_utility extends CI_DB_utility {
|
||||
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_list_databases = 'SHOW DATABASES';
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_optimize_table = 'OPTIMIZE TABLE %s';
|
||||
|
||||
/**
|
||||
* REPAIR TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_repair_table = 'REPAIR TABLE %s';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Export
|
||||
*
|
||||
* @param array $params Preferences
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _backup($params = array())
|
||||
{
|
||||
if (count($params) === 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Extract the prefs for simplicity
|
||||
extract($params);
|
||||
|
||||
// Build the output
|
||||
$output = '';
|
||||
|
||||
// Do we need to include a statement to disable foreign key checks?
|
||||
if ($foreign_key_checks === FALSE)
|
||||
{
|
||||
$output .= 'SET foreign_key_checks = 0;'.$newline;
|
||||
}
|
||||
|
||||
foreach ( (array) $tables as $table)
|
||||
{
|
||||
// Is the table in the "ignore" list?
|
||||
if (in_array($table, (array) $ignore, TRUE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the table schema
|
||||
$query = $this->db->query('SHOW CREATE TABLE '.$this->db->escape_identifiers($this->db->database.'.'.$table));
|
||||
|
||||
// No result means the table name was invalid
|
||||
if ($query === FALSE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Write out the table schema
|
||||
$output .= '#'.$newline.'# TABLE STRUCTURE FOR: '.$table.$newline.'#'.$newline.$newline;
|
||||
|
||||
if ($add_drop === TRUE)
|
||||
{
|
||||
$output .= 'DROP TABLE IF EXISTS '.$this->db->protect_identifiers($table).';'.$newline.$newline;
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$result = $query->result_array();
|
||||
foreach ($result[0] as $val)
|
||||
{
|
||||
if ($i++ % 2)
|
||||
{
|
||||
$output .= $val.';'.$newline.$newline;
|
||||
}
|
||||
}
|
||||
|
||||
// If inserts are not needed we're done...
|
||||
if ($add_insert === FALSE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grab all the data from the current table
|
||||
$query = $this->db->query('SELECT * FROM '.$this->db->protect_identifiers($table));
|
||||
|
||||
if ($query->num_rows() === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch the field names and determine if the field is an
|
||||
// integer type. We use this info to decide whether to
|
||||
// surround the data with quotes or not
|
||||
|
||||
$i = 0;
|
||||
$field_str = '';
|
||||
$is_int = array();
|
||||
while ($field = $query->result_id->fetch_field())
|
||||
{
|
||||
// Most versions of MySQL store timestamp as a string
|
||||
$is_int[$i] = in_array($field->type, array(MYSQLI_TYPE_TINY, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_INT24, MYSQLI_TYPE_LONG), TRUE);
|
||||
|
||||
// Create a string of field names
|
||||
$field_str .= $this->db->escape_identifiers($field->name).', ';
|
||||
$i++;
|
||||
}
|
||||
|
||||
// Trim off the end comma
|
||||
$field_str = preg_replace('/, $/' , '', $field_str);
|
||||
|
||||
// Build the insert string
|
||||
foreach ($query->result_array() as $row)
|
||||
{
|
||||
$val_str = '';
|
||||
|
||||
$i = 0;
|
||||
foreach ($row as $v)
|
||||
{
|
||||
// Is the value NULL?
|
||||
if ($v === NULL)
|
||||
{
|
||||
$val_str .= 'NULL';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Escape the data if it's not an integer
|
||||
$val_str .= ($is_int[$i] === FALSE) ? $this->db->escape($v) : $v;
|
||||
}
|
||||
|
||||
// Append a comma
|
||||
$val_str .= ', ';
|
||||
$i++;
|
||||
}
|
||||
|
||||
// Remove the comma at the end of the string
|
||||
$val_str = preg_replace('/, $/' , '', $val_str);
|
||||
|
||||
// Build the INSERT string
|
||||
$output .= 'INSERT INTO '.$this->db->protect_identifiers($table).' ('.$field_str.') VALUES ('.$val_str.');'.$newline;
|
||||
}
|
||||
|
||||
$output .= $newline.$newline;
|
||||
}
|
||||
|
||||
// Do we need to include a statement to re-enable foreign key checks?
|
||||
if ($foreign_key_checks === FALSE)
|
||||
{
|
||||
$output .= 'SET foreign_key_checks = 1;'.$newline;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,712 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.4.1
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* oci8 Database Adapter Class
|
||||
*
|
||||
* Note: _DB is an extender class that the app controller
|
||||
* creates dynamically based on whether the query builder
|
||||
* class is being used or not.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
|
||||
/**
|
||||
* oci8 Database Adapter Class
|
||||
*
|
||||
* This is a modification of the DB_driver class to
|
||||
* permit access to oracle databases
|
||||
*
|
||||
* @author Kelly McArdle
|
||||
*/
|
||||
class CI_DB_oci8_driver extends CI_DB {
|
||||
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $dbdriver = 'oci8';
|
||||
|
||||
/**
|
||||
* Statement ID
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $stmt_id;
|
||||
|
||||
/**
|
||||
* Cursor ID
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $curs_id;
|
||||
|
||||
/**
|
||||
* Commit mode flag
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $commit_mode = OCI_COMMIT_ON_SUCCESS;
|
||||
|
||||
/**
|
||||
* Limit used flag
|
||||
*
|
||||
* If we use LIMIT, we'll add a field that will
|
||||
* throw off num_fields later.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $limit_used = FALSE;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reset $stmt_id flag
|
||||
*
|
||||
* Used by stored_procedure() to prevent _execute() from
|
||||
* re-setting the statement ID.
|
||||
*/
|
||||
protected $_reset_stmt_id = TRUE;
|
||||
|
||||
/**
|
||||
* List of reserved identifiers
|
||||
*
|
||||
* Identifiers that must NOT be escaped.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $_reserved_identifiers = array('*', 'rownum');
|
||||
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_random_keyword = array('ASC', 'ASC'); // not currently supported
|
||||
|
||||
/**
|
||||
* COUNT string
|
||||
*
|
||||
* @used-by CI_DB_driver::count_all()
|
||||
* @used-by CI_DB_query_builder::count_all_results()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_count_string = 'SELECT COUNT(1) AS ';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
$valid_dsns = array(
|
||||
'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS
|
||||
// Easy Connect string (Oracle 10g+)
|
||||
'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i',
|
||||
'in' => '/^[a-z0-9$_]+$/i' // Instance name (defined in tnsnames.ora)
|
||||
);
|
||||
|
||||
/* Space characters don't have any effect when actually
|
||||
* connecting, but can be a hassle while validating the DSN.
|
||||
*/
|
||||
$this->dsn = str_replace(array("\n", "\r", "\t", ' '), '', $this->dsn);
|
||||
|
||||
if ($this->dsn !== '')
|
||||
{
|
||||
foreach ($valid_dsns as $regexp)
|
||||
{
|
||||
if (preg_match($regexp, $this->dsn))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy support for TNS in the hostname configuration field
|
||||
$this->hostname = str_replace(array("\n", "\r", "\t", ' '), '', $this->hostname);
|
||||
if (preg_match($valid_dsns['tns'], $this->hostname))
|
||||
{
|
||||
$this->dsn = $this->hostname;
|
||||
return;
|
||||
}
|
||||
elseif ($this->hostname !== '' && strpos($this->hostname, '/') === FALSE && strpos($this->hostname, ':') === FALSE
|
||||
&& (( ! empty($this->port) && ctype_digit($this->port)) OR $this->database !== ''))
|
||||
{
|
||||
/* If the hostname field isn't empty, doesn't contain
|
||||
* ':' and/or '/' and if port and/or database aren't
|
||||
* empty, then the hostname field is most likely indeed
|
||||
* just a hostname. Therefore we'll try and build an
|
||||
* Easy Connect string from these 3 settings, assuming
|
||||
* that the database field is a service name.
|
||||
*/
|
||||
$this->dsn = $this->hostname
|
||||
.(( ! empty($this->port) && ctype_digit($this->port)) ? ':'.$this->port : '')
|
||||
.($this->database !== '' ? '/'.ltrim($this->database, '/') : '');
|
||||
|
||||
if (preg_match($valid_dsns['ec'], $this->dsn))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* At this point, we can only try and validate the hostname and
|
||||
* database fields separately as DSNs.
|
||||
*/
|
||||
if (preg_match($valid_dsns['ec'], $this->hostname) OR preg_match($valid_dsns['in'], $this->hostname))
|
||||
{
|
||||
$this->dsn = $this->hostname;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database = str_replace(array("\n", "\r", "\t", ' '), '', $this->database);
|
||||
foreach ($valid_dsns as $regexp)
|
||||
{
|
||||
if (preg_match($regexp, $this->database))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Well - OK, an empty string should work as well.
|
||||
* PHP will try to use environment variables to
|
||||
* determine which Oracle instance to connect to.
|
||||
*/
|
||||
$this->dsn = '';
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Non-persistent database connection
|
||||
*
|
||||
* @param bool $persistent
|
||||
* @return resource
|
||||
*/
|
||||
public function db_connect($persistent = FALSE)
|
||||
{
|
||||
$func = ($persistent === TRUE) ? 'oci_pconnect' : 'oci_connect';
|
||||
return empty($this->char_set)
|
||||
? $func($this->username, $this->password, $this->dsn)
|
||||
: $func($this->username, $this->password, $this->dsn, $this->char_set);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database version number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
if (isset($this->data_cache['version']))
|
||||
{
|
||||
return $this->data_cache['version'];
|
||||
}
|
||||
|
||||
if ( ! $this->conn_id OR ($version_string = oci_server_version($this->conn_id)) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
elseif (preg_match('#Release\s(\d+(?:\.\d+)+)#', $version_string, $match))
|
||||
{
|
||||
return $this->data_cache['version'] = $match[1];
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return resource
|
||||
*/
|
||||
protected function _execute($sql)
|
||||
{
|
||||
/* Oracle must parse the query before it is run. All of the actions with
|
||||
* the query are based on the statement id returned by oci_parse().
|
||||
*/
|
||||
if ($this->_reset_stmt_id === TRUE)
|
||||
{
|
||||
$this->stmt_id = oci_parse($this->conn_id, $sql);
|
||||
}
|
||||
|
||||
oci_set_prefetch($this->stmt_id, 1000);
|
||||
return oci_execute($this->stmt_id, $this->commit_mode);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get cursor. Returns a cursor from the database
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function get_cursor()
|
||||
{
|
||||
return $this->curs_id = oci_new_cursor($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Stored Procedure. Executes a stored procedure
|
||||
*
|
||||
* @param string package name in which the stored procedure is in
|
||||
* @param string stored procedure name to execute
|
||||
* @param array parameters
|
||||
* @return mixed
|
||||
*
|
||||
* params array keys
|
||||
*
|
||||
* KEY OPTIONAL NOTES
|
||||
* name no the name of the parameter should be in :<param_name> format
|
||||
* value no the value of the parameter. If this is an OUT or IN OUT parameter,
|
||||
* this should be a reference to a variable
|
||||
* type yes the type of the parameter
|
||||
* length yes the max size of the parameter
|
||||
*/
|
||||
public function stored_procedure($package, $procedure, array $params)
|
||||
{
|
||||
if ($package === '' OR $procedure === '')
|
||||
{
|
||||
log_message('error', 'Invalid query: '.$package.'.'.$procedure);
|
||||
return ($this->db_debug) ? $this->display_error('db_invalid_query') : FALSE;
|
||||
}
|
||||
|
||||
// Build the query string
|
||||
$sql = 'BEGIN '.$package.'.'.$procedure.'(';
|
||||
|
||||
$have_cursor = FALSE;
|
||||
foreach ($params as $param)
|
||||
{
|
||||
$sql .= $param['name'].',';
|
||||
|
||||
if (isset($param['type']) && $param['type'] === OCI_B_CURSOR)
|
||||
{
|
||||
$have_cursor = TRUE;
|
||||
}
|
||||
}
|
||||
$sql = trim($sql, ',').'); END;';
|
||||
|
||||
$this->_reset_stmt_id = FALSE;
|
||||
$this->stmt_id = oci_parse($this->conn_id, $sql);
|
||||
$this->_bind_params($params);
|
||||
$result = $this->query($sql, FALSE, $have_cursor);
|
||||
$this->_reset_stmt_id = TRUE;
|
||||
return $result;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Bind parameters
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
protected function _bind_params($params)
|
||||
{
|
||||
if ( ! is_array($params) OR ! is_resource($this->stmt_id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($params as $param)
|
||||
{
|
||||
foreach (array('name', 'value', 'type', 'length') as $val)
|
||||
{
|
||||
if ( ! isset($param[$val]))
|
||||
{
|
||||
$param[$val] = '';
|
||||
}
|
||||
}
|
||||
|
||||
oci_bind_by_name($this->stmt_id, $param['name'], $param['value'], $param['length'], $param['type']);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_begin()
|
||||
{
|
||||
$this->commit_mode = OCI_NO_AUTO_COMMIT;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_commit()
|
||||
{
|
||||
$this->commit_mode = OCI_COMMIT_ON_SUCCESS;
|
||||
|
||||
return oci_commit($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_rollback()
|
||||
{
|
||||
$this->commit_mode = OCI_COMMIT_ON_SUCCESS;
|
||||
return oci_rollback($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Affected Rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function affected_rows()
|
||||
{
|
||||
return oci_num_rows($this->stmt_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function insert_id()
|
||||
{
|
||||
// not supported in oracle
|
||||
return $this->display_error('db_unsupported_function');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show table query
|
||||
*
|
||||
* Generates a platform-specific query string so that the table names can be fetched
|
||||
*
|
||||
* @param bool $prefix_limit
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_tables($prefix_limit = FALSE)
|
||||
{
|
||||
$sql = 'SELECT "TABLE_NAME" FROM "ALL_TABLES"';
|
||||
|
||||
if ($prefix_limit !== FALSE && $this->dbprefix !== '')
|
||||
{
|
||||
return $sql.' WHERE "TABLE_NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
|
||||
.sprintf($this->_like_escape_str, $this->_like_escape_chr);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show column query
|
||||
*
|
||||
* Generates a platform-specific query string so that the column names can be fetched
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_columns($table = '')
|
||||
{
|
||||
if (strpos($table, '.') !== FALSE)
|
||||
{
|
||||
sscanf($table, '%[^.].%s', $owner, $table);
|
||||
}
|
||||
else
|
||||
{
|
||||
$owner = $this->username;
|
||||
}
|
||||
|
||||
return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS
|
||||
WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).'
|
||||
AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns an object with field data
|
||||
*
|
||||
* @param string $table
|
||||
* @return array
|
||||
*/
|
||||
public function field_data($table)
|
||||
{
|
||||
if (strpos($table, '.') !== FALSE)
|
||||
{
|
||||
sscanf($table, '%[^.].%s', $owner, $table);
|
||||
}
|
||||
else
|
||||
{
|
||||
$owner = $this->username;
|
||||
}
|
||||
|
||||
$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE
|
||||
FROM ALL_TAB_COLUMNS
|
||||
WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).'
|
||||
AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
|
||||
|
||||
if (($query = $this->query($sql)) === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
$query = $query->result_object();
|
||||
|
||||
$retval = array();
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $query[$i]->COLUMN_NAME;
|
||||
$retval[$i]->type = $query[$i]->DATA_TYPE;
|
||||
|
||||
$length = ($query[$i]->CHAR_LENGTH > 0)
|
||||
? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION;
|
||||
if ($length === NULL)
|
||||
{
|
||||
$length = $query[$i]->DATA_LENGTH;
|
||||
}
|
||||
$retval[$i]->max_length = $length;
|
||||
|
||||
$default = $query[$i]->DATA_DEFAULT;
|
||||
if ($default === NULL && $query[$i]->NULLABLE === 'N')
|
||||
{
|
||||
$default = '';
|
||||
}
|
||||
$retval[$i]->default = $default;
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* Returns an array containing code and message of the last
|
||||
* database error that has occurred.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
// oci_error() returns an array that already contains
|
||||
// 'code' and 'message' keys, but it can return false
|
||||
// if there was no error ....
|
||||
if (is_resource($this->curs_id))
|
||||
{
|
||||
$error = oci_error($this->curs_id);
|
||||
}
|
||||
elseif (is_resource($this->stmt_id))
|
||||
{
|
||||
$error = oci_error($this->stmt_id);
|
||||
}
|
||||
elseif (is_resource($this->conn_id))
|
||||
{
|
||||
$error = oci_error($this->conn_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
$error = oci_error();
|
||||
}
|
||||
|
||||
return is_array($error)
|
||||
? $error
|
||||
: array('code' => '', 'message' => '');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert batch statement
|
||||
*
|
||||
* Generates a platform-specific insert string from the supplied data
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @param array $keys INSERT keys
|
||||
* @param array $values INSERT values
|
||||
* @return string
|
||||
*/
|
||||
protected function _insert_batch($table, $keys, $values)
|
||||
{
|
||||
$keys = implode(', ', $keys);
|
||||
$sql = "INSERT ALL\n";
|
||||
|
||||
for ($i = 0, $c = count($values); $i < $c; $i++)
|
||||
{
|
||||
$sql .= ' INTO '.$table.' ('.$keys.') VALUES '.$values[$i]."\n";
|
||||
}
|
||||
|
||||
return $sql.'SELECT * FROM dual';
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Truncate statement
|
||||
*
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the TRUNCATE statement,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _truncate($table)
|
||||
{
|
||||
return 'TRUNCATE TABLE '.$table;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Delete statement
|
||||
*
|
||||
* Generates a platform-specific delete string from the supplied data
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _delete($table)
|
||||
{
|
||||
if ($this->qb_limit)
|
||||
{
|
||||
$this->where('rownum <= ',$this->qb_limit, FALSE);
|
||||
$this->qb_limit = FALSE;
|
||||
}
|
||||
|
||||
return parent::_delete($table);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* LIMIT
|
||||
*
|
||||
* Generates a platform-specific LIMIT clause
|
||||
*
|
||||
* @param string $sql SQL Query
|
||||
* @return string
|
||||
*/
|
||||
protected function _limit($sql)
|
||||
{
|
||||
if (version_compare($this->version(), '12.1', '>='))
|
||||
{
|
||||
// OFFSET-FETCH can be used only with the ORDER BY clause
|
||||
empty($this->qb_orderby) && $sql .= ' ORDER BY 1';
|
||||
|
||||
return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY';
|
||||
}
|
||||
|
||||
$this->limit_used = TRUE;
|
||||
return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($this->qb_offset + $this->qb_limit + 1).')'
|
||||
.($this->qb_offset ? ' WHERE rnum >= '.($this->qb_offset + 1) : '');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Close DB Connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
if (is_resource($this->curs_id))
|
||||
{
|
||||
oci_free_statement($this->curs_id);
|
||||
}
|
||||
|
||||
if (is_resource($this->stmt_id))
|
||||
{
|
||||
oci_free_statement($this->stmt_id);
|
||||
}
|
||||
|
||||
oci_close($this->conn_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* We need to reset our $limit_used hack flag, so it doesn't propagate
|
||||
* to subsequent queries.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _reset_select()
|
||||
{
|
||||
$this->limit_used = FALSE;
|
||||
parent::_reset_select();
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.4.1
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Oracle Forge Class
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_oci8_forge extends CI_DB_forge {
|
||||
|
||||
/**
|
||||
* CREATE DATABASE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_database = FALSE;
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_table_if = FALSE;
|
||||
|
||||
/**
|
||||
* DROP DATABASE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_drop_database = FALSE;
|
||||
|
||||
/**
|
||||
* DROP TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_drop_table_if = FALSE;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var bool|array
|
||||
*/
|
||||
protected $_unsigned = FALSE;
|
||||
|
||||
/**
|
||||
* NULL value representation in CREATE/ALTER TABLE statements
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_null = 'NULL';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alter_type ALTER type
|
||||
* @param string $table Table name
|
||||
* @param mixed $field Column definition
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function _alter_table($alter_type, $table, $field)
|
||||
{
|
||||
if ($alter_type === 'DROP')
|
||||
{
|
||||
return parent::_alter_table($alter_type, $table, $field);
|
||||
}
|
||||
elseif ($alter_type === 'CHANGE')
|
||||
{
|
||||
$alter_type = 'MODIFY';
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
|
||||
$sqls = array();
|
||||
for ($i = 0, $c = count($field); $i < $c; $i++)
|
||||
{
|
||||
if ($field[$i]['_literal'] !== FALSE)
|
||||
{
|
||||
$field[$i] = "\n\t".$field[$i]['_literal'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$field[$i]['_literal'] = "\n\t".$this->_process_column($field[$i]);
|
||||
|
||||
if ( ! empty($field[$i]['comment']))
|
||||
{
|
||||
$sqls[] = 'COMMENT ON COLUMN '
|
||||
.$this->db->escape_identifiers($table).'.'.$this->db->escape_identifiers($field[$i]['name'])
|
||||
.' IS '.$field[$i]['comment'];
|
||||
}
|
||||
|
||||
if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name']))
|
||||
{
|
||||
$sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
|
||||
.' TO '.$this->db->escape_identifiers($field[$i]['new_name']);
|
||||
}
|
||||
|
||||
$field[$i] = "\n\t".$field[$i]['_literal'];
|
||||
}
|
||||
}
|
||||
|
||||
$sql .= ' '.$alter_type.' ';
|
||||
$sql .= (count($field) === 1)
|
||||
? $field[0]
|
||||
: '('.implode(',', $field).')';
|
||||
|
||||
// RENAME COLUMN must be executed after MODIFY
|
||||
array_unshift($sqls, $sql);
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*
|
||||
* @param array &$attributes
|
||||
* @param array &$field
|
||||
* @return void
|
||||
*/
|
||||
protected function _attr_auto_increment(&$attributes, &$field)
|
||||
{
|
||||
if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'number') !== FALSE && version_compare($this->db->version(), '12.1', '>='))
|
||||
{
|
||||
$field['auto_increment'] = ' GENERATED ALWAYS AS IDENTITY';
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*
|
||||
* @param array $field
|
||||
* @return string
|
||||
*/
|
||||
protected function _process_column($field)
|
||||
{
|
||||
return $this->db->escape_identifiers($field['name'])
|
||||
.' '.$field['type'].$field['length']
|
||||
.$field['unsigned']
|
||||
.$field['default']
|
||||
.$field['auto_increment']
|
||||
.$field['null']
|
||||
.$field['unique'];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field attribute TYPE
|
||||
*
|
||||
* Performs a data type mapping between different databases.
|
||||
*
|
||||
* @param array &$attributes
|
||||
* @return void
|
||||
*/
|
||||
protected function _attr_type(&$attributes)
|
||||
{
|
||||
switch (strtoupper($attributes['TYPE']))
|
||||
{
|
||||
case 'TINYINT':
|
||||
$attributes['TYPE'] = 'NUMBER';
|
||||
return;
|
||||
case 'MEDIUMINT':
|
||||
$attributes['TYPE'] = 'NUMBER';
|
||||
return;
|
||||
case 'INT':
|
||||
$attributes['TYPE'] = 'NUMBER';
|
||||
return;
|
||||
case 'BIGINT':
|
||||
$attributes['TYPE'] = 'NUMBER';
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.4.1
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* oci8 Result Class
|
||||
*
|
||||
* This class extends the parent result class: CI_DB_result
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_oci8_result extends CI_DB_result {
|
||||
|
||||
/**
|
||||
* Statement ID
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $stmt_id;
|
||||
|
||||
/**
|
||||
* Cursor ID
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $curs_id;
|
||||
|
||||
/**
|
||||
* Limit used flag
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $limit_used;
|
||||
|
||||
/**
|
||||
* Commit mode flag
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $commit_mode;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param object &$driver_object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(&$driver_object)
|
||||
{
|
||||
parent::__construct($driver_object);
|
||||
|
||||
$this->stmt_id = $driver_object->stmt_id;
|
||||
$this->curs_id = $driver_object->curs_id;
|
||||
$this->limit_used = $driver_object->limit_used;
|
||||
$this->commit_mode =& $driver_object->commit_mode;
|
||||
$driver_object->stmt_id = FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
$count = oci_num_fields($this->stmt_id);
|
||||
|
||||
// if we used a limit we subtract it
|
||||
return ($this->limit_used) ? $count - 1 : $count;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
$field_names = array();
|
||||
for ($c = 1, $fieldCount = $this->num_fields(); $c <= $fieldCount; $c++)
|
||||
{
|
||||
$field_names[] = oci_field_name($this->stmt_id, $c);
|
||||
}
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
$retval = array();
|
||||
for ($c = 1, $fieldCount = $this->num_fields(); $c <= $fieldCount; $c++)
|
||||
{
|
||||
$F = new stdClass();
|
||||
$F->name = oci_field_name($this->stmt_id, $c);
|
||||
$F->type = oci_field_type($this->stmt_id, $c);
|
||||
$F->max_length = oci_field_size($this->stmt_id, $c);
|
||||
|
||||
$retval[] = $F;
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
if (is_resource($this->result_id))
|
||||
{
|
||||
oci_free_statement($this->result_id);
|
||||
$this->result_id = FALSE;
|
||||
}
|
||||
|
||||
if (is_resource($this->stmt_id))
|
||||
{
|
||||
oci_free_statement($this->stmt_id);
|
||||
}
|
||||
|
||||
if (is_resource($this->curs_id))
|
||||
{
|
||||
oci_cancel($this->curs_id);
|
||||
$this->curs_id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
$id = ($this->curs_id) ? $this->curs_id : $this->stmt_id;
|
||||
return oci_fetch_assoc($id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
$row = ($this->curs_id)
|
||||
? oci_fetch_object($this->curs_id)
|
||||
: oci_fetch_object($this->stmt_id);
|
||||
|
||||
if ($class_name === 'stdClass' OR ! $row)
|
||||
{
|
||||
return $row;
|
||||
}
|
||||
|
||||
$class_name = new $class_name();
|
||||
foreach ($row as $key => $value)
|
||||
{
|
||||
$class_name->$key = $value;
|
||||
}
|
||||
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.4.1
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Oracle Utility Class
|
||||
*
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_oci8_utility extends CI_DB_utility {
|
||||
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_list_databases = 'SELECT username FROM dba_users'; // Schemas are actual usernames
|
||||
|
||||
/**
|
||||
* Export
|
||||
*
|
||||
* @param array $params Preferences
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _backup($params = array())
|
||||
{
|
||||
// Currently unsupported
|
||||
return $this->db->display_error('db_unsupported_feature');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,426 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* ODBC Database Adapter Class
|
||||
*
|
||||
* Note: _DB is an extender class that the app controller
|
||||
* creates dynamically based on whether the query builder
|
||||
* class is being used or not.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_odbc_driver extends CI_DB_driver {
|
||||
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $dbdriver = 'odbc';
|
||||
|
||||
/**
|
||||
* Database schema
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $schema = 'public';
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* Must be empty for ODBC.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_escape_char = '';
|
||||
|
||||
/**
|
||||
* ESCAPE statement string
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_like_escape_str = " {escape '%s'} ";
|
||||
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_random_keyword = array('RND()', 'RND(%d)');
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ODBC result ID resource returned from odbc_prepare()
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $odbc_result;
|
||||
|
||||
/**
|
||||
* Values to use with odbc_execute() for prepared statements
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $binds = array();
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
// Legacy support for DSN in the hostname field
|
||||
if (empty($this->dsn))
|
||||
{
|
||||
$this->dsn = $this->hostname;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Non-persistent database connection
|
||||
*
|
||||
* @param bool $persistent
|
||||
* @return resource
|
||||
*/
|
||||
public function db_connect($persistent = FALSE)
|
||||
{
|
||||
return ($persistent === TRUE)
|
||||
? odbc_pconnect($this->dsn, $this->username, $this->password)
|
||||
: odbc_connect($this->dsn, $this->username, $this->password);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Compile Bindings
|
||||
*
|
||||
* @param string $sql SQL statement
|
||||
* @param array $binds An array of values to bind
|
||||
* @return string
|
||||
*/
|
||||
public function compile_binds($sql, $binds)
|
||||
{
|
||||
if (empty($binds) OR empty($this->bind_marker) OR strpos($sql, $this->bind_marker) === FALSE)
|
||||
{
|
||||
return $sql;
|
||||
}
|
||||
elseif ( ! is_array($binds))
|
||||
{
|
||||
$binds = array($binds);
|
||||
$bind_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure we're using numeric keys
|
||||
$binds = array_values($binds);
|
||||
$bind_count = count($binds);
|
||||
}
|
||||
|
||||
// We'll need the marker length later
|
||||
$ml = strlen($this->bind_marker);
|
||||
|
||||
// Make sure not to replace a chunk inside a string that happens to match the bind marker
|
||||
if ($c = preg_match_all("/'[^']*'|\"[^\"]*\"/i", $sql, $matches))
|
||||
{
|
||||
$c = preg_match_all('/'.preg_quote($this->bind_marker, '/').'/i',
|
||||
str_replace($matches[0],
|
||||
str_replace($this->bind_marker, str_repeat(' ', $ml), $matches[0]),
|
||||
$sql, $c),
|
||||
$matches, PREG_OFFSET_CAPTURE);
|
||||
|
||||
// Bind values' count must match the count of markers in the query
|
||||
if ($bind_count !== $c)
|
||||
{
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
elseif (($c = preg_match_all('/'.preg_quote($this->bind_marker, '/').'/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bind_count)
|
||||
{
|
||||
return $sql;
|
||||
}
|
||||
|
||||
if ($this->bind_marker !== '?')
|
||||
{
|
||||
do
|
||||
{
|
||||
$c--;
|
||||
$sql = substr_replace($sql, '?', $matches[0][$c][1], $ml);
|
||||
}
|
||||
while ($c !== 0);
|
||||
}
|
||||
|
||||
if (FALSE !== ($this->odbc_result = odbc_prepare($this->conn_id, $sql)))
|
||||
{
|
||||
$this->binds = array_values($binds);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param string $sql an SQL query
|
||||
* @return resource
|
||||
*/
|
||||
protected function _execute($sql)
|
||||
{
|
||||
if ( ! isset($this->odbc_result))
|
||||
{
|
||||
return odbc_exec($this->conn_id, $sql);
|
||||
}
|
||||
elseif ($this->odbc_result === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (TRUE === ($success = odbc_execute($this->odbc_result, $this->binds)))
|
||||
{
|
||||
// For queries that return result sets, return the result_id resource on success
|
||||
$this->is_write_type($sql) OR $success = $this->odbc_result;
|
||||
}
|
||||
|
||||
$this->odbc_result = NULL;
|
||||
$this->binds = array();
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_begin()
|
||||
{
|
||||
return odbc_autocommit($this->conn_id, FALSE);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_commit()
|
||||
{
|
||||
if (odbc_commit($this->conn_id))
|
||||
{
|
||||
odbc_autocommit($this->conn_id, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_rollback()
|
||||
{
|
||||
if (odbc_rollback($this->conn_id))
|
||||
{
|
||||
odbc_autocommit($this->conn_id, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines if a query is a "write" type.
|
||||
*
|
||||
* @param string An SQL query string
|
||||
* @return bool
|
||||
*/
|
||||
public function is_write_type($sql)
|
||||
{
|
||||
if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#is', $sql))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return parent::is_write_type($sql);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Platform-dependent string escape
|
||||
*
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
protected function _escape_str($str)
|
||||
{
|
||||
$this->display_error('db_unsupported_feature');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Affected Rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function affected_rows()
|
||||
{
|
||||
return odbc_num_rows($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function insert_id()
|
||||
{
|
||||
return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show table query
|
||||
*
|
||||
* Generates a platform-specific query string so that the table names can be fetched
|
||||
*
|
||||
* @param bool $prefix_limit
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_tables($prefix_limit = FALSE)
|
||||
{
|
||||
$sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = '".$this->schema."'";
|
||||
|
||||
if ($prefix_limit !== FALSE && $this->dbprefix !== '')
|
||||
{
|
||||
return $sql." AND table_name LIKE '".$this->escape_like_str($this->dbprefix)."%' "
|
||||
.sprintf($this->_like_escape_str, $this->_like_escape_chr);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show column query
|
||||
*
|
||||
* Generates a platform-specific query string so that the column names can be fetched
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _list_columns($table = '')
|
||||
{
|
||||
return 'SHOW COLUMNS FROM '.$table;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data query
|
||||
*
|
||||
* Generates a platform-specific query so that the column data can be retrieved
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _field_data($table)
|
||||
{
|
||||
return 'SELECT TOP 1 FROM '.$table;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* Returns an array containing code and message of the last
|
||||
* database error that has occurred.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
return array('code' => odbc_error($this->conn_id), 'message' => odbc_errormsg($this->conn_id));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Close DB Connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
odbc_close($this->conn_id);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* ODBC Forge Class
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/database/
|
||||
*/
|
||||
class CI_DB_odbc_forge extends CI_DB_forge {
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_table_if = FALSE;
|
||||
|
||||
/**
|
||||
* DROP TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_drop_table_if = FALSE;
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var bool|array
|
||||
*/
|
||||
protected $_unsigned = FALSE;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*
|
||||
* @param array &$attributes
|
||||
* @param array &$field
|
||||
* @return void
|
||||
*/
|
||||
protected function _attr_auto_increment(&$attributes, &$field)
|
||||
{
|
||||
// Not supported (in most databases at least)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* ODBC Result Class
|
||||
*
|
||||
* This class extends the parent result class: CI_DB_result
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_odbc_result extends CI_DB_result {
|
||||
|
||||
/**
|
||||
* Number of rows in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_rows()
|
||||
{
|
||||
if (is_int($this->num_rows))
|
||||
{
|
||||
return $this->num_rows;
|
||||
}
|
||||
elseif (($this->num_rows = odbc_num_rows($this->result_id)) !== -1)
|
||||
{
|
||||
return $this->num_rows;
|
||||
}
|
||||
|
||||
// Work-around for ODBC subdrivers that don't support num_rows()
|
||||
if (count($this->result_array) > 0)
|
||||
{
|
||||
return $this->num_rows = count($this->result_array);
|
||||
}
|
||||
elseif (count($this->result_object) > 0)
|
||||
{
|
||||
return $this->num_rows = count($this->result_object);
|
||||
}
|
||||
|
||||
return $this->num_rows = count($this->result_array());
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
return odbc_num_fields($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
$field_names = array();
|
||||
$num_fields = $this->num_fields();
|
||||
|
||||
if ($num_fields > 0)
|
||||
{
|
||||
for ($i = 1; $i <= $num_fields; $i++)
|
||||
{
|
||||
$field_names[] = odbc_field_name($this->result_id, $i);
|
||||
}
|
||||
}
|
||||
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
$retval = array();
|
||||
for ($i = 0, $odbc_index = 1, $c = $this->num_fields(); $i < $c; $i++, $odbc_index++)
|
||||
{
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = odbc_field_name($this->result_id, $odbc_index);
|
||||
$retval[$i]->type = odbc_field_type($this->result_id, $odbc_index);
|
||||
$retval[$i]->max_length = odbc_field_len($this->result_id, $odbc_index);
|
||||
$retval[$i]->primary_key = 0;
|
||||
$retval[$i]->default = '';
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
if (is_resource($this->result_id))
|
||||
{
|
||||
odbc_free_result($this->result_id);
|
||||
$this->result_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
return odbc_fetch_array($this->result_id);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
$row = odbc_fetch_object($this->result_id);
|
||||
|
||||
if ($class_name === 'stdClass' OR ! $row)
|
||||
{
|
||||
return $row;
|
||||
}
|
||||
|
||||
$class_name = new $class_name();
|
||||
foreach ($row as $key => $value)
|
||||
{
|
||||
$class_name->$key = $value;
|
||||
}
|
||||
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
if ( ! function_exists('odbc_fetch_array'))
|
||||
{
|
||||
/**
|
||||
* ODBC Fetch array
|
||||
*
|
||||
* Emulates the native odbc_fetch_array() function when
|
||||
* it is not available (odbc_fetch_array() requires unixODBC)
|
||||
*
|
||||
* @param resource &$result
|
||||
* @param int $rownumber
|
||||
* @return array
|
||||
*/
|
||||
function odbc_fetch_array(&$result, $rownumber = 1)
|
||||
{
|
||||
$rs = array();
|
||||
if ( ! odbc_fetch_into($result, $rs, $rownumber))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$rs_assoc = array();
|
||||
foreach ($rs as $k => $v)
|
||||
{
|
||||
$field_name = odbc_field_name($result, $k+1);
|
||||
$rs_assoc[$field_name] = $v;
|
||||
}
|
||||
|
||||
return $rs_assoc;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
if ( ! function_exists('odbc_fetch_object'))
|
||||
{
|
||||
/**
|
||||
* ODBC Fetch object
|
||||
*
|
||||
* Emulates the native odbc_fetch_object() function when
|
||||
* it is not available.
|
||||
*
|
||||
* @param resource &$result
|
||||
* @param int $rownumber
|
||||
* @return object
|
||||
*/
|
||||
function odbc_fetch_object(&$result, $rownumber = 1)
|
||||
{
|
||||
$rs = array();
|
||||
if ( ! odbc_fetch_into($result, $rs, $rownumber))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$rs_object = new stdClass();
|
||||
foreach ($rs as $k => $v)
|
||||
{
|
||||
$field_name = odbc_field_name($result, $k+1);
|
||||
$rs_object->$field_name = $v;
|
||||
}
|
||||
|
||||
return $rs_object;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 1.3.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* ODBC Utility Class
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/database/
|
||||
*/
|
||||
class CI_DB_odbc_utility extends CI_DB_utility {
|
||||
|
||||
/**
|
||||
* Export
|
||||
*
|
||||
* @param array $params Preferences
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _backup($params = array())
|
||||
{
|
||||
// Currently unsupported
|
||||
return $this->db->display_error('db_unsupported_feature');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,351 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 2.1.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* PDO Database Adapter Class
|
||||
*
|
||||
* Note: _DB is an extender class that the app controller
|
||||
* creates dynamically based on whether the query builder
|
||||
* class is being used or not.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_pdo_driver extends CI_DB {
|
||||
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $dbdriver = 'pdo';
|
||||
|
||||
/**
|
||||
* PDO Options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $options = array();
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* Validates the DSN string and/or detects the subdriver.
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
if (preg_match('/([^:]+):/', $this->dsn, $match) && count($match) === 2)
|
||||
{
|
||||
// If there is a minimum valid dsn string pattern found, we're done
|
||||
// This is for general PDO users, who tend to have a full DSN string.
|
||||
$this->subdriver = $match[1];
|
||||
return;
|
||||
}
|
||||
// Legacy support for DSN specified in the hostname field
|
||||
elseif (preg_match('/([^:]+):/', $this->hostname, $match) && count($match) === 2)
|
||||
{
|
||||
$this->dsn = $this->hostname;
|
||||
$this->hostname = NULL;
|
||||
$this->subdriver = $match[1];
|
||||
return;
|
||||
}
|
||||
elseif (in_array($this->subdriver, array('mssql', 'sybase'), TRUE))
|
||||
{
|
||||
$this->subdriver = 'dblib';
|
||||
}
|
||||
elseif ($this->subdriver === '4D')
|
||||
{
|
||||
$this->subdriver = '4d';
|
||||
}
|
||||
elseif ( ! in_array($this->subdriver, array('4d', 'cubrid', 'dblib', 'firebird', 'ibm', 'informix', 'mysql', 'oci', 'odbc', 'pgsql', 'sqlite', 'sqlsrv'), TRUE))
|
||||
{
|
||||
log_message('error', 'PDO: Invalid or non-existent subdriver');
|
||||
|
||||
if ($this->db_debug)
|
||||
{
|
||||
show_error('Invalid or non-existent PDO subdriver');
|
||||
}
|
||||
}
|
||||
|
||||
$this->dsn = NULL;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database connection
|
||||
*
|
||||
* @param bool $persistent
|
||||
* @return object
|
||||
*/
|
||||
public function db_connect($persistent = FALSE)
|
||||
{
|
||||
if ($persistent === TRUE)
|
||||
{
|
||||
$this->options[PDO::ATTR_PERSISTENT] = TRUE;
|
||||
}
|
||||
|
||||
// From PHP8.0, default PDO::ATTR_ERRMODE is changed
|
||||
// from PDO::ERRMODE_SILENT to PDO::ERRMODE_EXCEPTION
|
||||
// as https://wiki.php.net/rfc/pdo_default_errmode
|
||||
if ( ! isset($this->options[PDO::ATTR_ERRMODE]))
|
||||
{
|
||||
$this->options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_SILENT;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return new PDO($this->dsn, $this->username, $this->password, $this->options);
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
if ($this->db_debug && empty($this->failover))
|
||||
{
|
||||
$this->display_error($e->getMessage(), '', TRUE);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Database version number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
if (isset($this->data_cache['version']))
|
||||
{
|
||||
return $this->data_cache['version'];
|
||||
}
|
||||
|
||||
// Not all subdrivers support the getAttribute() method
|
||||
try
|
||||
{
|
||||
return $this->data_cache['version'] = $this->conn_id->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
return parent::version();
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param string $sql SQL query
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _execute($sql)
|
||||
{
|
||||
return $this->conn_id->query($sql);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_begin()
|
||||
{
|
||||
return $this->conn_id->beginTransaction();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_commit()
|
||||
{
|
||||
return $this->conn_id->commit();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _trans_rollback()
|
||||
{
|
||||
return $this->conn_id->rollBack();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Platform-dependent string escape
|
||||
*
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
protected function _escape_str($str)
|
||||
{
|
||||
// Escape the string
|
||||
$str = $this->conn_id->quote($str);
|
||||
|
||||
// If there are duplicated quotes, trim them away
|
||||
return ($str[0] === "'")
|
||||
? substr($str, 1, -1)
|
||||
: $str;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Affected Rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function affected_rows()
|
||||
{
|
||||
return is_object($this->result_id) ? $this->result_id->rowCount() : 0;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* @param string $name
|
||||
* @return int
|
||||
*/
|
||||
public function insert_id($name = NULL)
|
||||
{
|
||||
return $this->conn_id->lastInsertId($name);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data query
|
||||
*
|
||||
* Generates a platform-specific query so that the column data can be retrieved
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _field_data($table)
|
||||
{
|
||||
return 'SELECT TOP 1 * FROM '.$this->protect_identifiers($table);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* Returns an array containing code and message of the last
|
||||
* database error that has occurred.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
$error = array('code' => '00000', 'message' => '');
|
||||
$pdo_error = $this->conn_id->errorInfo();
|
||||
|
||||
if (empty($pdo_error[0]))
|
||||
{
|
||||
return $error;
|
||||
}
|
||||
|
||||
$error['code'] = isset($pdo_error[1]) ? $pdo_error[0].'/'.$pdo_error[1] : $pdo_error[0];
|
||||
if (isset($pdo_error[2]))
|
||||
{
|
||||
$error['message'] = $pdo_error[2];
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Truncate statement
|
||||
*
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the TRUNCATE statement,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function _truncate($table)
|
||||
{
|
||||
return 'TRUNCATE TABLE '.$table;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Close DB Connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
$this->result_id = FALSE;
|
||||
$this->conn_id = FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 2.1.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* PDO Forge Class
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/database/
|
||||
*/
|
||||
class CI_DB_pdo_forge extends CI_DB_forge {
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_create_table_if = FALSE;
|
||||
|
||||
/**
|
||||
* DROP TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_drop_table_if = FALSE;
|
||||
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 2.1.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* PDO Result Class
|
||||
*
|
||||
* This class extends the parent result class: CI_DB_result
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/userguide3/database/
|
||||
*/
|
||||
class CI_DB_pdo_result extends CI_DB_result {
|
||||
|
||||
/**
|
||||
* Number of rows in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_rows()
|
||||
{
|
||||
if (is_int($this->num_rows))
|
||||
{
|
||||
return $this->num_rows;
|
||||
}
|
||||
elseif (count($this->result_array) > 0)
|
||||
{
|
||||
return $this->num_rows = count($this->result_array);
|
||||
}
|
||||
elseif (count($this->result_object) > 0)
|
||||
{
|
||||
return $this->num_rows = count($this->result_object);
|
||||
}
|
||||
elseif (($num_rows = $this->result_id->rowCount()) > 0)
|
||||
{
|
||||
return $this->num_rows = $num_rows;
|
||||
}
|
||||
|
||||
return $this->num_rows = count($this->result_array());
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Number of fields in the result set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function num_fields()
|
||||
{
|
||||
return $this->result_id->columnCount();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch Field Names
|
||||
*
|
||||
* Generates an array of column names
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function list_fields()
|
||||
{
|
||||
$field_names = array();
|
||||
for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
|
||||
{
|
||||
// Might trigger an E_WARNING due to not all subdrivers
|
||||
// supporting getColumnMeta()
|
||||
$field_names[$i] = @$this->result_id->getColumnMeta($i);
|
||||
$field_names[$i] = $field_names[$i]['name'];
|
||||
}
|
||||
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field data
|
||||
*
|
||||
* Generates an array of objects containing field meta-data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_data()
|
||||
{
|
||||
try
|
||||
{
|
||||
$retval = array();
|
||||
|
||||
for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
|
||||
{
|
||||
$field = $this->result_id->getColumnMeta($i);
|
||||
|
||||
$retval[$i] = new stdClass();
|
||||
$retval[$i]->name = $field['name'];
|
||||
$retval[$i]->type = isset($field['native_type']) ? $field['native_type'] : null;
|
||||
$retval[$i]->max_length = ($field['len'] > 0) ? $field['len'] : NULL;
|
||||
$retval[$i]->primary_key = (int) ( ! empty($field['flags']) && in_array('primary_key', $field['flags'], TRUE));
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
if ($this->db->db_debug)
|
||||
{
|
||||
return $this->db->display_error('db_unsupported_feature');
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Free the result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free_result()
|
||||
{
|
||||
if (is_object($this->result_id))
|
||||
{
|
||||
$this->result_id = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - associative array
|
||||
*
|
||||
* Returns the result set as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _fetch_assoc()
|
||||
{
|
||||
return $this->result_id->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result - object
|
||||
*
|
||||
* Returns the result set as an object
|
||||
*
|
||||
* @param string $class_name
|
||||
* @return object
|
||||
*/
|
||||
protected function _fetch_object($class_name = 'stdClass')
|
||||
{
|
||||
return $this->result_id->fetchObject($class_name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 - 2022, CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author EllisLab Dev Team
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 2.1.0
|
||||
* @filesource
|
||||
*/
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* PDO Utility Class
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @subpackage Drivers
|
||||
* @category Database
|
||||
* @author EllisLab Dev Team
|
||||
* @link https://codeigniter.com/database/
|
||||
*/
|
||||
class CI_DB_pdo_utility extends CI_DB_utility {
|
||||
|
||||
/**
|
||||
* Export
|
||||
*
|
||||
* @param array $params Preferences
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _backup($params = array())
|
||||
{
|
||||
// Currently unsupported
|
||||
return $this->db->display_error('db_unsupported_feature');
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user