Merge pull request #19487 from kamil-tekiela/Improvements-to-DatabaseInterface

Improvements to database interface
This commit is contained in:
Maurício Meneghini Fauth 2025-01-02 17:57:29 -03:00 committed by GitHub
commit 37018dbd5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 64 additions and 153 deletions

View File

@ -6684,7 +6684,7 @@ parameters:
-
message: '#^Only booleans are allowed in a negated boolean, PhpMyAdmin\\Dbal\\ResultInterface\|false given\.$#'
identifier: booleanNot.exprNotBoolean
count: 3
count: 2
path: src/Dbal/DatabaseInterface.php
-
@ -10170,7 +10170,7 @@ parameters:
Use dependency injection instead\.$#
'''
identifier: staticMethod.deprecated
count: 7
count: 8
path: src/Navigation/Nodes/Node.php
-

View File

@ -5993,6 +5993,7 @@
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
</DeprecatedMethod>
<MixedArgument>
<code><![CDATA[$db]]></code>

View File

@ -1711,25 +1711,6 @@ class DatabaseInterface
return $this->extension->getError($this->connections[$connectionType->value]);
}
/**
* returns the number of rows returned by last query
* used with tryQuery as it accepts false
*
* @param string $query query to run
*
* @psalm-return int|numeric-string
*/
public function queryAndGetNumRows(string $query): string|int
{
$result = $this->tryQuery($query);
if (! $result) {
return 0;
}
return $result->numRows();
}
/**
* returns last inserted auto_increment id for given $link
*/
@ -1970,14 +1951,13 @@ class DatabaseInterface
$this->isPercona = stripos($this->versionComment, 'percona') !== false;
}
/**
* Prepare an SQL statement for execution.
*
* @param string $query The query, as a string.
*/
public function prepare(string $query, ConnectionType $connectionType = ConnectionType::User): Statement|null
{
return $this->extension->prepare($this->connections[$connectionType->value], $query);
/** @param list<string> $params */
public function executeQuery(
string $query,
array $params,
ConnectionType $connectionType = ConnectionType::User,
): ResultInterface|null {
return $this->extension->executeQuery($this->connections[$connectionType->value], $query, $params);
}
public function getDatabaseList(): ListDatabase

View File

@ -91,11 +91,11 @@ interface DbiExtension
public function escapeString(Connection $connection, string $string): string;
/**
* Prepare an SQL statement for execution.
* Execute a prepared statement and return the result.
*
* @param string $query The query, as a string.
* @param list<string> $params
*/
public function prepare(Connection $connection, string $query): Statement|null;
public function executeQuery(Connection $connection, string $query, array $params): ResultInterface|null;
/**
* Returns the number of warnings from the last query.

View File

@ -284,20 +284,21 @@ class DbiMysqli implements DbiExtension
}
/**
* Prepare an SQL statement for execution.
* Execute a prepared statement and return the result.
*
* @param string $query The query, as a string.
* @param list<string> $params
*/
public function prepare(Connection $connection, string $query): Statement|null
public function executeQuery(Connection $connection, string $query, array $params): MysqliResult|null
{
/** @var mysqli $mysqli */
$mysqli = $connection->connection;
$statement = $mysqli->prepare($query);
if ($statement === false) {
$result = $mysqli->execute_query($query, $params);
if ($result === false) {
return null;
}
return new MysqliStatement($statement);
return new MysqliResult($result);
}
/**

View File

@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Dbal;
use mysqli_stmt;
use function count;
final class MysqliStatement implements Statement
{
public function __construct(private mysqli_stmt $statement)
{
}
/**
* Executes a prepared statement.
*
* @param list<string> $params
*/
public function execute(array $params): bool
{
$paramCount = $this->statement->param_count;
if (count($params) !== $paramCount) {
return false;
}
return $this->statement->execute($params);
}
/**
* Gets a result set from a prepared statement.
*/
public function getResult(): ResultInterface
{
return new MysqliResult($this->statement->get_result());
}
}

View File

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Dbal;
interface Statement
{
/**
* Executes a prepared statement.
*
* @param list<string> $params
*/
public function execute(array $params): bool;
/**
* Gets a result set from a prepared statement.
*/
public function getResult(): ResultInterface;
}

View File

@ -379,13 +379,13 @@ class Node
$query = 'SHOW DATABASES ';
$query .= $this->getWhereClause('Database', $searchClause);
return (int) $dbi->queryAndGetNumRows($query);
return $this->queryAndGetNumRows($query);
}
$retval = 0;
foreach ($this->getDatabasesToSearch($userPrivileges, $searchClause) as $db) {
$query = 'SHOW DATABASES LIKE ' . $dbi->quoteString($db);
$retval += (int) $dbi->queryAndGetNumRows($query);
$retval += $this->queryAndGetNumRows($query);
}
return $retval;
@ -831,4 +831,20 @@ class Node
return $retval;
}
/**
* returns the number of rows returned by last query
* used with tryQuery as it accepts false
*/
protected function queryAndGetNumRows(string $query): int
{
$dbi = DatabaseInterface::getInstance();
$result = $dbi->tryQuery($query);
if ($result === false) {
return 0;
}
return (int) $result->numRows();
}
}

View File

@ -94,7 +94,7 @@ class NodeTable extends NodeDatabaseChild
$db = Util::backquote($db);
$table = Util::backquote($table);
$query = 'SHOW COLUMNS FROM ' . $table . ' FROM ' . $db;
$retval = (int) $dbi->queryAndGetNumRows($query);
$retval = $this->queryAndGetNumRows($query);
}
break;
@ -102,7 +102,7 @@ class NodeTable extends NodeDatabaseChild
$db = Util::backquote($db);
$table = Util::backquote($table);
$query = 'SHOW INDEXES FROM ' . $table . ' FROM ' . $db;
$retval = (int) $dbi->queryAndGetNumRows($query);
$retval = $this->queryAndGetNumRows($query);
break;
case 'triggers':
if (! $this->config->selectedServer['DisableIS']) {
@ -116,7 +116,7 @@ class NodeTable extends NodeDatabaseChild
} else {
$db = Util::backquote($db);
$query = 'SHOW TRIGGERS FROM ' . $db . ' WHERE `Table` = ' . $dbi->quoteString($table);
$retval = (int) $dbi->queryAndGetNumRows($query);
$retval = $this->queryAndGetNumRows($query);
}
break;

View File

@ -1196,12 +1196,12 @@ class Privileges
NOT (`Table_priv` = \'\' AND Column_priv = \'\')
ORDER BY `User` ASC, `Host` ASC, `Db` ASC, `Table_priv` ASC;
';
$statement = $this->dbi->prepare($query);
if ($statement === null || ! $statement->execute([$db->getName(), $table->getName()])) {
$result = $this->dbi->executeQuery($query, [$db->getName(), $table->getName()]);
if ($result === null) {
return [];
}
return $statement->getResult()->fetchAllAssoc();
return $result->fetchAllAssoc();
}
/** @return array<int, array<string|null>> */
@ -3171,12 +3171,11 @@ class Privileges
private function getUserPrivileges(string $user, string $host, bool $hasAccountLocking): array|null
{
$query = 'SELECT * FROM `mysql`.`user` WHERE `User` = ? AND `Host` = ?;';
$statement = $this->dbi->prepare($query);
if ($statement === null || ! $statement->execute([$user, $host])) {
$result = $this->dbi->executeQuery($query, [$user, $host]);
if ($result === null) {
return null;
}
$result = $statement->getResult();
/** @var array<string, string|null>|null $userPrivileges */
$userPrivileges = $result->fetchAssoc();
if ($userPrivileges === []) {
@ -3190,12 +3189,11 @@ class Privileges
$userPrivileges['account_locked'] = 'N';
$query = 'SELECT * FROM `mysql`.`global_priv` WHERE `User` = ? AND `Host` = ?;';
$statement = $this->dbi->prepare($query);
if ($statement === null || ! $statement->execute([$user, $host])) {
$result = $this->dbi->executeQuery($query, [$user, $host]);
if ($result === null) {
return $userPrivileges;
}
$result = $statement->getResult();
/** @var array<string, string|null>|null $globalPrivileges */
$globalPrivileges = $result->fetchAssoc();
if ($globalPrivileges === []) {

View File

@ -13,7 +13,6 @@ use PhpMyAdmin\Dbal\ConnectionType;
use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\Dbal\DbiExtension;
use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\Dbal\Statement;
use PhpMyAdmin\I18n\LanguageManager;
use PhpMyAdmin\Index;
use PhpMyAdmin\Query\Utilities;
@ -758,17 +757,17 @@ class DatabaseInterfaceTest extends AbstractTestCase
$dummyDbi->assertAllQueriesConsumed();
}
public function testPrepare(): void
public function testExecuteQuery(): void
{
$query = 'SELECT * FROM `mysql`.`user` WHERE `User` = ? AND `Host` = ?;';
$stmtStub = self::createStub(Statement::class);
$resultStub = self::createStub(ResultInterface::class);
$dummyDbi = $this->createMock(DbiExtension::class);
$dummyDbi->expects(self::once())->method('prepare')
->with(self::isType('object'), self::equalTo($query))
->willReturn($stmtStub);
$dummyDbi->expects(self::once())->method('executeQuery')
->with(self::isType('object'), self::equalTo($query), self::equalTo(['root', 'localhost']))
->willReturn($resultStub);
$dbi = $this->createDatabaseInterface($dummyDbi);
$stmt = $dbi->prepare($query, ConnectionType::ControlUser);
self::assertSame($stmtStub, $stmt);
$stmt = $dbi->executeQuery($query, ['root', 'localhost'], ConnectionType::ControlUser);
self::assertSame($resultStub, $stmt);
}
/**

View File

@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Dbal;
use mysqli_stmt;
use PhpMyAdmin\Dbal\MysqliResult;
use PhpMyAdmin\Dbal\MysqliStatement;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(MysqliStatement::class)]
#[CoversClass(MysqliResult::class)]
class MysqliStatementTest extends TestCase
{
public function testGetResult(): void
{
$mysqliStmt = self::createMock(mysqli_stmt::class);
$mysqliStmt->expects(self::once())->method('get_result')->willReturn(false);
$statement = new MysqliStatement($mysqliStmt);
$result = $statement->getResult();
self::assertInstanceOf(MysqliResult::class, $result);
}
}

View File

@ -12,7 +12,6 @@ use PhpMyAdmin\Current;
use PhpMyAdmin\Dbal\ConnectionType;
use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\Dbal\Statement;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Http\Factory\ServerRequestFactory;
use PhpMyAdmin\Message;
@ -1896,18 +1895,15 @@ class PrivilegesTest extends AbstractTestCase
public function testGetUserPrivileges(): void
{
$mysqliResultStub = $this->createMock(ResultInterface::class);
$mysqliStmtStub = $this->createMock(Statement::class);
$mysqliStmtStub->expects(self::exactly(2))->method('execute')->willReturn(true);
$mysqliStmtStub->expects(self::exactly(2))->method('getResult')->willReturn($mysqliResultStub);
$dbi = $this->createMock(DatabaseInterface::class);
$dbi->expects(self::once())->method('isMariaDB')->willReturn(true);
$userQuery = 'SELECT * FROM `mysql`.`user` WHERE `User` = ? AND `Host` = ?;';
$globalPrivQuery = 'SELECT * FROM `mysql`.`global_priv` WHERE `User` = ? AND `Host` = ?;';
$dbi->expects(self::exactly(2))->method('prepare')->willReturnMap([
[$userQuery, ConnectionType::User, $mysqliStmtStub],
[$globalPrivQuery, ConnectionType::User, $mysqliStmtStub],
$dbi->expects(self::exactly(2))->method('executeQuery')->willReturnMap([
[$userQuery, ['test.user', 'test.host'], ConnectionType::User, $mysqliResultStub],
[$globalPrivQuery,['test.user', 'test.host'], ConnectionType::User, $mysqliResultStub],
]);
$mysqliResultStub->expects(self::exactly(2))

View File

@ -15,7 +15,6 @@ use PhpMyAdmin\Config\Settings\Server;
use PhpMyAdmin\Dbal\Connection;
use PhpMyAdmin\Dbal\DbiExtension;
use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\Dbal\Statement;
use PhpMyAdmin\FieldMetadata;
use PhpMyAdmin\Identifiers\DatabaseName;
use PhpMyAdmin\Tests\FieldHelper;
@ -314,7 +313,12 @@ class DbiDummy implements DbiExtension
$this->dummyQueries = [];
}
public function prepare(Connection $connection, string $query): Statement|null
/**
* Execute a prepared statement and return the result.
*
* @param list<string> $params
*/
public function executeQuery(Connection $connection, string $query, array $params): ResultInterface|null
{
return null;
}