402 lines
13 KiB
PHP
402 lines
13 KiB
PHP
|
<?php
|
||
|
// MariaDBConnection.php
|
||
|
// Created: 2021-04-30
|
||
|
// Updated: 2022-02-27
|
||
|
|
||
|
namespace Index\Data\MariaDB;
|
||
|
|
||
|
use mysqli;
|
||
|
use mysqli_sql_exception;
|
||
|
use InvalidArgumentException;
|
||
|
use RuntimeException;
|
||
|
use Index\AString;
|
||
|
use Index\Version;
|
||
|
use Index\Data\BeginTransactionFailedException;
|
||
|
use Index\Data\DataException;
|
||
|
use Index\Data\IDbConnection;
|
||
|
use Index\Data\IDbTransactions;
|
||
|
use Index\Data\CommitFailedException;
|
||
|
use Index\Data\ConnectionFailedException;
|
||
|
use Index\Data\ReleaseSavePointFailedException;
|
||
|
use Index\Data\RollbackFailedException;
|
||
|
use Index\Data\SavePointFailedException;
|
||
|
use Index\Data\QueryExecuteException;
|
||
|
|
||
|
/**
|
||
|
* Represents a connection with a MariaDB or MySQL database server.
|
||
|
*/
|
||
|
class MariaDBConnection implements IDbConnection, IDbTransactions {
|
||
|
/**
|
||
|
* Refresh grant tables.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public const REFRESH_GRANT = MYSQLI_REFRESH_GRANT;
|
||
|
|
||
|
/**
|
||
|
* Flushes logs, like the FLUSH LOGS; statement.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public const REFRESH_LOG = MYSQLI_REFRESH_LOG;
|
||
|
|
||
|
/**
|
||
|
* Refresh tables, like the FLUSH TABLES; statement.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public const REFRESH_TABLES = MYSQLI_REFRESH_TABLES;
|
||
|
|
||
|
/**
|
||
|
* Refreshes the hosts cache, like the FLUSH HOSTS; statement.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public const REFRESH_HOSTS = MYSQLI_REFRESH_HOSTS;
|
||
|
|
||
|
/**
|
||
|
* Refresh the status variables, like the FLUSH STATUS; statement.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public const REFRESH_STATUS = MYSQLI_REFRESH_STATUS;
|
||
|
|
||
|
/**
|
||
|
* Flushes the thread cache.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public const REFRESH_THREADS = MYSQLI_REFRESH_THREADS;
|
||
|
|
||
|
/**
|
||
|
* Resets information about the master server and restarts on slave replication servers,
|
||
|
* like the RESET SLAVE; statement.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public const REFRESH_SLAVE = MYSQLI_REFRESH_SLAVE;
|
||
|
|
||
|
/**
|
||
|
* Removes binary log files listed in the binary log index and truncates the index file
|
||
|
* on master replication servers, like the RESET MASTER; statement.
|
||
|
*/
|
||
|
public const REFRESH_MASTER = MYSQLI_REFRESH_MASTER;
|
||
|
|
||
|
private mysqli $connection;
|
||
|
|
||
|
/**
|
||
|
* Creates a new instance of MariaDBConnection.
|
||
|
*
|
||
|
* @param MariaDBConnectionInfo $connectionInfo Information about the connection.
|
||
|
* @return MariaDBConnection A new instance of MariaDBConnection.
|
||
|
*/
|
||
|
public function __construct(MariaDBConnectionInfo $connectionInfo) {
|
||
|
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||
|
|
||
|
// I'm not sure if calling "new mysqli" without arguments is equivalent to this
|
||
|
// the documentation would suggest it's not and that it just pulls from the config
|
||
|
// nothing suggests otherwise too.
|
||
|
// The output of mysqli_init is just an object anyway so we can safely use it instead
|
||
|
// but continue to use it as an object.
|
||
|
$this->connection = mysqli_init();
|
||
|
$this->connection->options(MYSQLI_OPT_LOCAL_INFILE, 0);
|
||
|
|
||
|
if($connectionInfo->hasCharacterSet())
|
||
|
$this->connection->options(MYSQLI_SET_CHARSET_NAME, $connectionInfo->getCharacterSet());
|
||
|
|
||
|
if($connectionInfo->hasInitCommand())
|
||
|
$this->connection->options(MYSQLI_INIT_COMMAND, $connectionInfo->getInitCommand());
|
||
|
|
||
|
$flags = $connectionInfo->shouldUseCompression() ? MYSQLI_CLIENT_COMPRESS : 0;
|
||
|
|
||
|
if($connectionInfo->isSecure()) {
|
||
|
$flags |= MYSQLI_CLIENT_SSL;
|
||
|
|
||
|
if($connectionInfo->shouldVerifyCertificate())
|
||
|
$this->connection->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, 1);
|
||
|
else
|
||
|
$flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
|
||
|
|
||
|
$this->connection->ssl_set(
|
||
|
$connectionInfo->getKeyPath(),
|
||
|
$connectionInfo->getCertificatePath(),
|
||
|
$connectionInfo->getCertificateAuthorityPath(),
|
||
|
$connectionInfo->getTrustedCertificatesPath(),
|
||
|
$connectionInfo->getCipherAlgorithms()
|
||
|
);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
if($connectionInfo->isUnixSocket())
|
||
|
$this->connection->real_connect(
|
||
|
'',
|
||
|
$connectionInfo->getUserName(),
|
||
|
$connectionInfo->getPassword(),
|
||
|
$connectionInfo->getDatabaseName(),
|
||
|
-1,
|
||
|
$connectionInfo->getSocketPath(),
|
||
|
$flags
|
||
|
);
|
||
|
else
|
||
|
$this->connection->real_connect(
|
||
|
$connectionInfo->getHost(),
|
||
|
$connectionInfo->getUserName(),
|
||
|
$connectionInfo->getPassword(),
|
||
|
$connectionInfo->getDatabaseName(),
|
||
|
$connectionInfo->getPort(),
|
||
|
'',
|
||
|
$flags
|
||
|
);
|
||
|
} catch(mysqli_sql_exception $ex) {
|
||
|
throw new ConnectionFailedException($ex->getMessage(), $ex->getCode(), $ex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the number of rows affected by the last operation.
|
||
|
*
|
||
|
* @return int|string Number of rows affected by the last operation.
|
||
|
*/
|
||
|
public function getAffectedRows(): int|string {
|
||
|
return $this->connection->affected_rows;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the name of the currently active character set.
|
||
|
*
|
||
|
* @return string Name of the character set.
|
||
|
*/
|
||
|
public function getCharacterSet(): string {
|
||
|
return $this->connection->character_set_name();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Switch to a different character set.
|
||
|
*
|
||
|
* @param string $charSet Name of the new character set.
|
||
|
* @throws InvalidArgumentException Switching to new character set failed.
|
||
|
*/
|
||
|
public function setCharacterSet(string $charSet): void {
|
||
|
if(!$this->connection->set_charset($charSet))
|
||
|
throw new InvalidArgumentException('$charSet is not a supported character set.');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns info about the currently character set and collation.
|
||
|
*
|
||
|
* @return MariaDBCharacterSetInfo Information about the character set.
|
||
|
*/
|
||
|
public function getCharacterSetInfo(): MariaDBCharacterSetInfo {
|
||
|
return new MariaDBCharacterSetInfo($this->connection->get_charset());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Switches the connection to a different user and default database.
|
||
|
*
|
||
|
* @param string $userName New user name.
|
||
|
* @param string $password New password.
|
||
|
* @param string|null $database New default database.
|
||
|
* @throws DataException If the user switch action failed.
|
||
|
*/
|
||
|
public function switchUser(string $userName, string $password, string|null $database = null): void {
|
||
|
if(!$this->connection->change_user($userName, $password, $database === null ? null : $database))
|
||
|
throw new DataException($this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Switches the default database for this connection.
|
||
|
*
|
||
|
* @param string $database New default database.
|
||
|
* @throws DataException If the database switch failed.
|
||
|
*/
|
||
|
public function switchDatabase(string $database): void {
|
||
|
if(!$this->connection->select_db($database))
|
||
|
throw new DataException($this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the version of the server.
|
||
|
*
|
||
|
* @return Version Version of the server.
|
||
|
*/
|
||
|
public function getServerVersion(): Version {
|
||
|
return MariaDBBackend::intToVersion($this->connection->server_version);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the last error code.
|
||
|
*
|
||
|
* @return int Last error code.
|
||
|
*/
|
||
|
public function getLastErrorCode(): int {
|
||
|
return $this->connection->errno;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the last error string.
|
||
|
*
|
||
|
* @return string Last error string.
|
||
|
*/
|
||
|
public function getLastErrorString(): string {
|
||
|
return $this->connection->error;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the current SQL State of the connection.
|
||
|
*
|
||
|
* @return string Current SQL State.
|
||
|
*/
|
||
|
public function getSQLState(): string {
|
||
|
return $this->connection->sqlstate;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets a list of errors from the last command.
|
||
|
*
|
||
|
* @return array List of last errors.
|
||
|
*/
|
||
|
public function getLastErrors(): array {
|
||
|
return MariaDBWarning::fromLastErrors($this->connection->error_list);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the number of columns for the last query.
|
||
|
*
|
||
|
* @return int
|
||
|
*/
|
||
|
public function getLastFieldCount(): int {
|
||
|
return $this->connection->field_count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets list of warnings.
|
||
|
*
|
||
|
* The result of SHOW WARNINGS;
|
||
|
*
|
||
|
* @return array List of warnings.
|
||
|
*/
|
||
|
public function getWarnings(): array {
|
||
|
return MariaDBWarning::fromGetWarnings($this->connection->get_warnings());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the number of warnings for the last query.
|
||
|
*
|
||
|
* @return int Amount of warnings.
|
||
|
*/
|
||
|
public function getWarningCount(): int {
|
||
|
return $this->connection->warning_count;
|
||
|
}
|
||
|
|
||
|
public function getLastInsertId(): int|string {
|
||
|
return $this->connection->insert_id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sends a keep alive request to the server, or tries to reconnect if it has gone down.
|
||
|
*
|
||
|
* @return bool true if the keep alive was successful, false if the server is Gone.
|
||
|
*/
|
||
|
public function ping(): bool {
|
||
|
return $this->connection->ping();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Runs various refresh operations, see the REFRESH_ constants for the flags.
|
||
|
*
|
||
|
* @param int $flags A bitset of REFRESH_ flags.
|
||
|
*/
|
||
|
public function refresh(int $flags): void {
|
||
|
$this->connection->refresh($flags);
|
||
|
}
|
||
|
|
||
|
public function setAutoCommit(bool $state): void {
|
||
|
$this->connection->autocommit($state);
|
||
|
}
|
||
|
|
||
|
public function beginTransaction(): void {
|
||
|
if(!$this->connection->begin_transaction())
|
||
|
throw new BeginTransactionFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
}
|
||
|
|
||
|
public function commit(): void {
|
||
|
if(!$this->connection->commit())
|
||
|
throw new CommitFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
}
|
||
|
|
||
|
public function rollback(?string $name = null): void {
|
||
|
if(!$this->connection->rollback(0, $name))
|
||
|
throw new RollbackFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
}
|
||
|
|
||
|
public function savePoint(string $name): void {
|
||
|
if(!$this->connection->savepoint($name))
|
||
|
throw new SavePointFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
}
|
||
|
|
||
|
public function releaseSavePoint(string $name): void {
|
||
|
if(!$this->connection->release_savepoint($name))
|
||
|
throw new ReleaseSavePointFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the thread ID for the current connection.
|
||
|
*
|
||
|
* @return int Thread ID.
|
||
|
*/
|
||
|
public function getThreadId(): int {
|
||
|
return $this->connection->thread_id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Asks the server to kill a thread.
|
||
|
*
|
||
|
* @param int $threadId ID of the thread that should be killed.
|
||
|
* @return bool true if the thread was killed, false if not.
|
||
|
*/
|
||
|
public function killThread(int $threadId): bool {
|
||
|
return $this->connection->kill($threadId);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return MariaDBStatement A database statement.
|
||
|
*/
|
||
|
public function prepare(string $query): MariaDBStatement {
|
||
|
$statement = $this->connection->prepare($query);
|
||
|
if($statement === false)
|
||
|
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
return new MariaDBStatement($statement);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return MariaDBResult A database result.
|
||
|
*/
|
||
|
public function query(string $query): MariaDBResult {
|
||
|
$result = $this->connection->query($query, MYSQLI_STORE_RESULT);
|
||
|
if($result === false)
|
||
|
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
// Yes, this always uses Native, for some reason the stupid limitation in libmysql only applies to preparing
|
||
|
return new MariaDBResultNative($result);
|
||
|
}
|
||
|
|
||
|
public function execute(string $query): int|string {
|
||
|
if(!$this->connection->real_query($query))
|
||
|
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||
|
return $this->connection->affected_rows;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Closes the connection and associated resources.
|
||
|
*/
|
||
|
public function close(): void {
|
||
|
$this->connection->close();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Closes the connection and associated resources.
|
||
|
*/
|
||
|
public function __destruct() {
|
||
|
$this->close();
|
||
|
}
|
||
|
}
|