This commit is contained in:
Markus
2022-04-28 09:40:10 +02:00
commit 795794f992
9586 changed files with 1146991 additions and 0 deletions

View File

@@ -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);
}
}

View File

@@ -0,0 +1,605 @@
<?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.
*
* @throws DatabaseException
*
* @return mixed
*/
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 mixed
*/
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', $e->getMessage());
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().
*/
protected function _listTables(bool $prefixLimit = false): string
{
$sql = 'SHOW TABLES FROM ' . $this->escapeIdentifiers($this->database);
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
*
* @throws DatabaseException
*
* @return stdClass[]
*/
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
*
* @throws DatabaseException
* @throws LogicException
*
* @return stdClass[]
*/
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
*
* @throws DatabaseException
*
* @return stdClass[]
*/
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;
}
}

View File

@@ -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 mixed $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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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.');
}
}