diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 317b590563..1f291dc314 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7765,85 +7765,7 @@ parameters: path: src/Navigation/Nodes/Node.php - - message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#' - identifier: empty.notAllowed - count: 4 - path: src/Navigation/Nodes/Node.php - - - - message: '#^Method PhpMyAdmin\\Navigation\\Nodes\\Node\:\:getDatabasesToSearch\(\) should return array\ but returns list\\|string\.$#' - identifier: return.type - count: 1 - path: src/Navigation/Nodes/Node.php - - - - message: '#^Parameter \#1 \$array of function sort expects TArray of array\, array\\|string given\.$#' - identifier: argument.type - count: 1 - path: src/Navigation/Nodes/Node.php - - - - message: '#^Parameter \#1 \$db of method PhpMyAdmin\\Navigation\\Nodes\\Node\:\:isHideDb\(\) expects string, string\|null given\.$#' - identifier: argument.type - count: 4 - path: src/Navigation/Nodes/Node.php - - - - message: '#^Parameter \#1 \$haystack of function strstr expects string, string\|null given\.$#' - identifier: argument.type - count: 4 - path: src/Navigation/Nodes/Node.php - - - - message: '#^Parameter \#1 \$str of method PhpMyAdmin\\Dbal\\DatabaseInterface\:\:quoteString\(\) expects string, mixed given\.$#' - identifier: argument.type - count: 5 - path: src/Navigation/Nodes/Node.php - - - - message: '#^Binary operation "\." between mixed and ''\)'' results in an error\.$#' - identifier: binaryOp.invalid - count: 1 - path: src/Navigation/Nodes/NodeColumn.php - - - - message: '#^Binary operation "\.\=" between mixed and '' \('' results in an error\.$#' - identifier: assignOp.invalid - count: 1 - path: src/Navigation/Nodes/NodeColumn.php - - - - message: '#^Binary operation "\.\=" between mixed and '', '' results in an error\.$#' - identifier: assignOp.invalid - count: 1 - path: src/Navigation/Nodes/NodeColumn.php - - - - message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#' - identifier: empty.notAllowed - count: 1 - path: src/Navigation/Nodes/NodeColumn.php - - - - message: '#^Parameter \#1 \$item of method PhpMyAdmin\\Navigation\\Nodes\\NodeColumn\:\:getDisplayName\(\) expects array\, array\ given\.$#' - identifier: argument.type - count: 1 - path: src/Navigation/Nodes/NodeColumn.php - - - - message: '#^Parameter \#1 \$key of method PhpMyAdmin\\Navigation\\Nodes\\NodeColumn\:\:getColumnIcon\(\) expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Navigation/Nodes/NodeColumn.php - - - - message: '#^Parameter \#2 \$name of method PhpMyAdmin\\Navigation\\Nodes\\Node\:\:__construct\(\) expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Navigation/Nodes/NodeColumn.php - - - - message: '#^Parameter \#2 \$value of method PhpMyAdmin\\Navigation\\Nodes\\NodeColumn\:\:getTruncateValue\(\) expects string, mixed given\.$#' + message: '#^Parameter \#2 \$name of method PhpMyAdmin\\Navigation\\Nodes\\Node\:\:__construct\(\) expects string, string\|null given\.$#' identifier: argument.type count: 1 path: src/Navigation/Nodes/NodeColumn.php @@ -7893,6 +7815,12 @@ parameters: count: 1 path: src/Navigation/Nodes/NodeTable.php + - + message: '#^Parameter \#2 \$item of class PhpMyAdmin\\Navigation\\Nodes\\NodeColumn constructor expects array\{name\: string\|null, key\: string, type\: string\|null, default\: string\|null, nullable\: string\}, array\ given\.$#' + identifier: argument.type + count: 1 + path: src/Navigation/Nodes/NodeTable.php + - message: '#^Method PhpMyAdmin\\Navigation\\Nodes\\ObjectFetcher\:\:getEventsFromDb\(\) should return array\ but returns list\\.$#' identifier: return.type @@ -16335,7 +16263,7 @@ parameters: Use dependency injection instead\.$# ''' identifier: staticMethod.deprecated - count: 2 + count: 7 path: tests/unit/Navigation/Nodes/NodeTableTest.php - @@ -16344,13 +16272,7 @@ parameters: Use dependency injection instead\.$# ''' identifier: staticMethod.deprecated - count: 7 - path: tests/unit/Navigation/Nodes/NodeTest.php - - - - message: '#^Property PhpMyAdmin\\Config\:\:\$selectedServer \(array\{host\: string, port\: string, socket\: string, ssl\: bool, ssl_key\: string\|null, ssl_cert\: string\|null, ssl_ca\: string\|null, ssl_ca_path\: string\|null, \.\.\.\}\) does not accept array\{host\: string, port\: string, socket\: string, ssl\: bool, ssl_key\: string\|null, ssl_cert\: string\|null, ssl_ca\: string\|null, ssl_ca_path\: string\|null, \.\.\.\}\.$#' - identifier: assign.propertyType - count: 3 + count: 8 path: tests/unit/Navigation/Nodes/NodeTest.php - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 5ecfb6a289..6ae427635e 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -5276,67 +5276,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - config->selectedServer['hide_db'])]]> - config->selectedServer['hide_db'])]]> - config->selectedServer['only_db'])]]> - config->selectedServer['only_db'])]]> - - - + - - - - - - - - - - + + - + @@ -5358,6 +5307,9 @@ + + + getRealParent()->realName]]> getRealParent()->realName]]> @@ -10503,6 +10455,11 @@ + + + + + @@ -10514,11 +10471,8 @@ + - - selectedServer]]> - selectedServer]]> - diff --git a/src/Navigation/NavigationTree.php b/src/Navigation/NavigationTree.php index fc719c09e0..7df57af971 100644 --- a/src/Navigation/NavigationTree.php +++ b/src/Navigation/NavigationTree.php @@ -225,8 +225,8 @@ class NavigationTree return (int) $this->dbi->fetchValue( sprintf( $query, - $this->config->settings['FirstLevelNavigationItems'], - $this->config->settings['FirstLevelNavigationItems'], + $this->config->config->FirstLevelNavigationItems, + $this->config->config->FirstLevelNavigationItems, $this->dbi->quoteString($this->config->settings['NavigationTreeDbSeparator']), $this->dbi->quoteString(Current::$database), ), @@ -277,7 +277,7 @@ class NavigationTree } } - $navItems = $this->config->settings['FirstLevelNavigationItems']; + $navItems = $this->config->config->FirstLevelNavigationItems; return (int) floor(count($prefixMap) / $navItems) * $navItems; } @@ -1129,7 +1129,7 @@ class NavigationTree ['server' => Current::$server], Url::getFromRoute('/navigation'), 'frame_navigation', - $this->config->settings['FirstLevelNavigationItems'], + $this->config->config->FirstLevelNavigationItems, 'pos', ['dbselector'], ); @@ -1278,7 +1278,7 @@ class NavigationTree ['server' => Current::$server], Url::getFromRoute('/navigation'), 'frame_navigation', - $this->config->settings['FirstLevelNavigationItems'], + $this->config->config->FirstLevelNavigationItems, 'pos', ['dbselector'], ); diff --git a/src/Navigation/Nodes/Node.php b/src/Navigation/Nodes/Node.php index 7e28f0599f..0efd64ab85 100644 --- a/src/Navigation/Nodes/Node.php +++ b/src/Navigation/Nodes/Node.php @@ -318,7 +318,7 @@ class Node int $pos, string $searchClause = '', ): array { - if (isset($this->config->selectedServer['DisableIS']) && ! $this->config->selectedServer['DisableIS']) { + if (! $this->config->selectedServer['DisableIS']) { return $this->getDataFromInfoSchema($pos, $searchClause); } @@ -345,7 +345,7 @@ class Node ! $this->config->settings['NavigationTreeEnableGrouping'] || ! $this->config->settings['ShowDatabasesNavigationAsTree'] ) { - if (isset($this->config->selectedServer['DisableIS']) && ! $this->config->selectedServer['DisableIS']) { + if (! $this->config->selectedServer['DisableIS']) { $query = 'SELECT COUNT(*) '; $query .= 'FROM INFORMATION_SCHEMA.SCHEMATA '; $query .= $this->getWhereClause('SCHEMA_NAME', $searchClause); @@ -392,14 +392,15 @@ class Node continue; } - while ($arr = $handle->fetchRow()) { - if ($this->isHideDb($arr[0])) { + /** @var string $value */ + foreach ($handle->fetchAllColumn() as $value) { + if ($this->isHideDb($value)) { continue; } - $prefix = strstr($arr[0], $dbSeparator, true); + $prefix = strstr($value, $dbSeparator, true); if ($prefix === false) { - $prefix = $arr[0]; + $prefix = $value; } $prefixMap[$prefix] = 1; @@ -414,10 +415,11 @@ class Node $query .= $this->getWhereClause('Database', $searchClause); $handle = $dbi->tryQuery($query); if ($handle !== false) { - while ($arr = $handle->fetchRow()) { - $prefix = strstr($arr[0], $dbSeparator, true); + /** @var string $value */ + foreach ($handle->fetchAllColumn() as $value) { + $prefix = strstr($value, $dbSeparator, true); if ($prefix === false) { - $prefix = $arr[0]; + $prefix = $value; } $prefixMap[$prefix] = 1; @@ -434,7 +436,7 @@ class Node */ private function isHideDb(string $db): bool { - return ! empty($this->config->selectedServer['hide_db']) + return $this->config->selectedServer['hide_db'] !== '' && preg_match('/' . $this->config->selectedServer['hide_db'] . '/', $db) === 1; } @@ -446,15 +448,15 @@ class Node * * @param string $searchClause search clause * - * @return mixed[] array of databases + * @return string[] array of databases */ private function getDatabasesToSearch(UserPrivileges $userPrivileges, string $searchClause): array { $databases = []; if ($searchClause !== '') { $databases = ['%' . DatabaseInterface::getInstance()->escapeMysqlWildcards($searchClause) . '%']; - } elseif (! empty($this->config->selectedServer['only_db'])) { - $databases = $this->config->selectedServer['only_db']; + } elseif ($this->config->selectedServer['only_db'] !== '') { + $databases = (array) $this->config->selectedServer['only_db']; } elseif ($userPrivileges->databasesToTest !== false && $userPrivileges->databasesToTest !== []) { $databases = $userPrivileges->databasesToTest; } @@ -480,12 +482,12 @@ class Node . ' LIKE ' . $dbi->quoteString('%' . $dbi->escapeMysqlWildcards($searchClause) . '%') . ' '; } - if (! empty($this->config->selectedServer['hide_db'])) { + if ($this->config->selectedServer['hide_db'] !== '') { $whereClause .= 'AND ' . Util::backquote($columnName) . ' NOT REGEXP ' . $dbi->quoteString($this->config->selectedServer['hide_db']) . ' '; } - if (! empty($this->config->selectedServer['only_db'])) { + if ($this->config->selectedServer['only_db'] !== '') { if (is_string($this->config->selectedServer['only_db'])) { $this->config->selectedServer['only_db'] = [$this->config->selectedServer['only_db']]; } @@ -591,7 +593,7 @@ class Node */ private function getDataFromInfoSchema(int $pos, string $searchClause): array { - $maxItems = $this->config->settings['FirstLevelNavigationItems']; + $maxItems = $this->config->config->FirstLevelNavigationItems; $dbi = DatabaseInterface::getInstance(); if ( ! $this->config->settings['NavigationTreeEnableGrouping'] @@ -632,7 +634,7 @@ class Node */ private function getDataFromShowDatabases(int $pos, string $searchClause): array { - $maxItems = $this->config->settings['FirstLevelNavigationItems']; + $maxItems = $this->config->config->FirstLevelNavigationItems; $dbi = DatabaseInterface::getInstance(); if ( ! $this->config->settings['NavigationTreeEnableGrouping'] @@ -646,22 +648,7 @@ class Node return []; } - $count = 0; - if (! $handle->seek($pos)) { - return []; - } - - $retval = []; - while ($arr = $handle->fetchRow()) { - if ($count >= $maxItems) { - break; - } - - $retval[] = $arr[0]; - $count++; - } - - return $retval; + return array_slice($handle->fetchAllColumn(), $pos, $maxItems); } $dbSeparator = $this->config->settings['NavigationTreeDbSeparator']; @@ -673,10 +660,10 @@ class Node if ($handle !== false) { $prefixMap = []; $total = $pos + $maxItems; - while ($arr = $handle->fetchRow()) { - $prefix = strstr($arr[0], $dbSeparator, true); + while ($value = $handle->fetchValue()) { + $prefix = strstr($value, $dbSeparator, true); if ($prefix === false) { - $prefix = $arr[0]; + $prefix = $value; } $prefixMap[$prefix] = 1; @@ -710,11 +697,11 @@ class Node * @param int $pos The offset of the list within the results. * @param string $searchClause A string used to filter the results of the query. * - * @return list + * @return list */ private function getDataFromShowDatabasesLike(UserPrivileges $userPrivileges, int $pos, string $searchClause): array { - $maxItems = $this->config->settings['FirstLevelNavigationItems']; + $maxItems = $this->config->config->FirstLevelNavigationItems; $dbi = DatabaseInterface::getInstance(); if ( ! $this->config->settings['NavigationTreeEnableGrouping'] @@ -728,17 +715,18 @@ class Node continue; } - while ($arr = $handle->fetchRow()) { - if ($this->isHideDb($arr[0])) { + /** @var string $value */ + foreach ($handle->fetchAllColumn() as $value) { + if ($this->isHideDb($value)) { continue; } - if (in_array($arr[0], $retval, true)) { + if (in_array($value, $retval, true)) { continue; } if ($pos <= 0 && $count < $maxItems) { - $retval[] = $arr[0]; + $retval[] = $value; $count++; } @@ -761,14 +749,15 @@ class Node continue; } - while ($arr = $handle->fetchRow()) { - if ($this->isHideDb($arr[0])) { + /** @var string $value */ + foreach ($handle->fetchAllColumn() as $value) { + if ($this->isHideDb($value)) { continue; } - $prefix = strstr($arr[0], $dbSeparator, true); + $prefix = strstr($value, $dbSeparator, true); if ($prefix === false) { - $prefix = $arr[0]; + $prefix = $value; } $prefixMap[$prefix] = 1; @@ -786,19 +775,20 @@ class Node continue; } - while ($arr = $handle->fetchRow()) { - if ($this->isHideDb($arr[0])) { + /** @var string $value */ + foreach ($handle->fetchAllColumn() as $value) { + if ($this->isHideDb($value)) { continue; } - if (in_array($arr[0], $retval, true)) { + if (in_array($value, $retval, true)) { continue; } foreach ($prefixes as $prefix) { - $startsWith = str_starts_with($arr[0] . $dbSeparator, $prefix . $dbSeparator); + $startsWith = str_starts_with($value . $dbSeparator, $prefix . $dbSeparator); if ($startsWith) { - $retval[] = $arr[0]; + $retval[] = $value; break; } } diff --git a/src/Navigation/Nodes/NodeColumn.php b/src/Navigation/Nodes/NodeColumn.php index 521e8cb7d6..72310f16b4 100644 --- a/src/Navigation/Nodes/NodeColumn.php +++ b/src/Navigation/Nodes/NodeColumn.php @@ -20,7 +20,7 @@ class NodeColumn extends Node { public string $displayName; - /** @param mixed[] $item array to identify the column node */ + /** @param array{name:string|null, key:string, type:string|null, default:string|null, nullable:string} $item */ public function __construct(Config $config, array $item) { $this->displayName = $this->getDisplayName($item); @@ -60,7 +60,7 @@ class NodeColumn extends Node /** * Get displayable name for navigation tree (key_type, data_type, default) * - * @param array $item Item is array containing required info + * @param array{name:string|null, key:string, type:string|null, default:string|null, nullable:string} $item * * @return string Display name for navigation tree */ @@ -69,7 +69,7 @@ class NodeColumn extends Node $retval = $item['name']; $isFirst = true; foreach ($item as $key => $value) { - if (empty($value) || $key === 'name') { + if ($value === null || $value === '' || $key === 'name') { continue; } diff --git a/src/Navigation/Nodes/NodeTable.php b/src/Navigation/Nodes/NodeTable.php index 5dae3198ac..33a8604740 100644 --- a/src/Navigation/Nodes/NodeTable.php +++ b/src/Navigation/Nodes/NodeTable.php @@ -13,7 +13,7 @@ use PhpMyAdmin\UserPrivileges; use PhpMyAdmin\Util; use function __; -use function in_array; +use function array_slice; /** * Represents a columns node in the navigation tree @@ -127,7 +127,7 @@ class NodeTable extends NodeDatabaseChild $query .= 'FROM `INFORMATION_SCHEMA`.`COLUMNS` '; $query .= 'WHERE `TABLE_NAME`=' . $dbi->quoteString($table) . ' '; $query .= 'AND `TABLE_SCHEMA`=' . $dbi->quoteString($db) . ' '; - $query .= 'ORDER BY `COLUMN_NAME` ASC '; + $query .= 'ORDER BY `ORDINAL_POSITION` ASC '; $query .= 'LIMIT ' . $pos . ', ' . $maxItems; $columnNodes = []; @@ -144,26 +144,18 @@ class NodeTable extends NodeDatabaseChild return []; } - $retval = []; - $count = 0; - if ($handle->seek($pos)) { - foreach ($handle as $arr) { - if ($count >= $maxItems) { - return $retval; - } - - $retval[] = new NodeColumn($this->config, [ - 'name' => $arr['Field'], - 'key' => $arr['Key'], - 'type' => Util::extractColumnSpec($arr['Type'])['type'], - 'default' => $arr['Default'], - 'nullable' => $arr['Null'] === 'NO' ? '' : 'nullable', - ]); - $count++; - } + $columnNodes = []; + foreach (array_slice($handle->fetchAllAssoc(), $pos, $maxItems) as $arr) { + $columnNodes[] = new NodeColumn($this->config, [ + 'name' => $arr['Field'], + 'key' => $arr['Key'] ?? '', + 'type' => Util::extractColumnSpec($arr['Type'])['type'], + 'default' => $arr['Default'], + 'nullable' => $arr['Null'] === 'NO' ? '' : 'nullable', + ]); } - return $retval; + return $columnNodes; } /** @return NodeIndex[] */ @@ -178,24 +170,9 @@ class NodeTable extends NodeDatabaseChild return []; } - $indexNames = []; - $count = 0; - /** @var string $keyName */ - foreach ($handle as ['Key_name' => $keyName]) { - if (in_array($keyName, $indexNames, true)) { - continue; - } - - if ($pos <= 0 && $count < $maxItems) { - $indexNames[] = $keyName; - $count++; - } - - $pos--; - } - $indexNodes = []; - foreach ($indexNames as $indexName) { + /** @var string $indexName */ + foreach (array_slice($handle->fetchAllAssoc(), $pos, $maxItems) as ['Key_name' => $indexName]) { $indexNodes[] = new NodeIndex($this->config, $indexName); } @@ -232,21 +209,13 @@ class NodeTable extends NodeDatabaseChild return []; } - $retval = []; - $count = 0; - if ($handle->seek($pos)) { - /** @var string $triggerName */ - foreach ($handle as ['Trigger' => $triggerName]) { - if ($count >= $maxItems) { - return $retval; - } - - $retval[] = new NodeTrigger($this->config, $triggerName); - $count++; - } + $triggerNodes = []; + /** @var string $triggerName */ + foreach (array_slice($handle->fetchAllAssoc(), $pos, $maxItems) as ['Trigger' => $triggerName]) { + $triggerNodes[] = new NodeTrigger($this->config, $triggerName); } - return $retval; + return $triggerNodes; } /** diff --git a/tests/unit/Navigation/Nodes/NodeTableTest.php b/tests/unit/Navigation/Nodes/NodeTableTest.php index 357bbc5763..b3de17e962 100644 --- a/tests/unit/Navigation/Nodes/NodeTableTest.php +++ b/tests/unit/Navigation/Nodes/NodeTableTest.php @@ -5,8 +5,11 @@ declare(strict_types=1); namespace PhpMyAdmin\Tests\Navigation\Nodes; use PhpMyAdmin\Config; +use PhpMyAdmin\Dbal\DatabaseInterface; +use PhpMyAdmin\Navigation\Nodes\NodeDatabase; use PhpMyAdmin\Navigation\Nodes\NodeTable; use PhpMyAdmin\Tests\AbstractTestCase; +use PhpMyAdmin\Tests\Stubs\DummyResult; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; @@ -64,4 +67,166 @@ class NodeTableTest extends AbstractTestCase ['/sql', 'b_browse', 'Browse'], ]; } + + public function testGetColumnsWithoutDisableIS(): void + { + $config = Config::getInstance(); + + $dbi = self::createMock(DatabaseInterface::class); + $dbi->expects(self::once()) + ->method('fetchResultSimple') + ->willReturnOnConsecutiveCalls( + [ + ['name' => 'foo1', 'type' => 'varchar(255)', 'key' => '', 'default' => '', 'nullable' => ''], + ['name' => 'foo2', 'type' => 'varchar(64)', 'key' => '', 'default' => '', 'nullable' => ''], + ['name' => 'foo3', 'type' => 'int(11)', 'key' => '', 'default' => '0', 'nullable' => ''], + ['name' => 'foo4', 'type' => 'text', 'key' => '', 'default' => '', 'nullable' => ''], + ], + ); + + $nodeContainer = new NodeDatabase($config, 'DB'); + $nodeTable = new NodeTable($config, 'node'); + $nodeContainer->addChild($nodeTable); + + $result = $nodeTable->getColumns($dbi, 1); + self::assertCount(4, $result); + + self::assertSame('foo1 (varchar(255))', $result[0]->displayName); + self::assertSame('foo2 (varchar(64))', $result[1]->displayName); + self::assertSame('foo3 (int(11), 0)', $result[2]->displayName); + self::assertSame('foo4 (text)', $result[3]->displayName); + } + + public function testGetColumnsWithDisableIS(): void + { + $config = Config::getInstance(); + $config->selectedServer['DisableIS'] = true; + $config->set('MaxNavigationItems', 2); + + $dbi = self::createMock(DatabaseInterface::class); + $resultStub = self::createMock(DummyResult::class); + $dbi->expects(self::once()) + ->method('tryQuery') + ->with('SHOW COLUMNS FROM `node` FROM `DB`') + ->willReturn($resultStub); + $resultStub->expects(self::once()) + ->method('fetchAllAssoc') + ->willReturnOnConsecutiveCalls( + [ + ['Field' => 'foo1', 'Type' => 'varchar(255)', 'Key' => '', 'Default' => '', 'Null' => 'NO'], + ['Field' => 'foo2', 'Type' => 'varchar(64)', 'Key' => '', 'Default' => '', 'Null' => 'NO'], + ['Field' => 'foo3', 'Type' => 'int(11)', 'Key' => '', 'Default' => '0', 'Null' => 'NO'], + ['Field' => 'foo4', 'Type' => 'text', 'Key' => '', 'Default' => '', 'Null' => 'NO'], + ], + ); + + $nodeContainer = new NodeDatabase($config, 'DB'); + $nodeTable = new NodeTable($config, 'node'); + $nodeContainer->addChild($nodeTable); + + $result = $nodeTable->getColumns($dbi, 1); + self::assertCount(2, $result); + + self::assertSame('foo2 (varchar)', $result[0]->displayName); + self::assertSame('foo3 (int, 0)', $result[1]->displayName); + } + + public function testGetTriggersWithoutDisableIS(): void + { + $config = Config::getInstance(); + + $dbi = self::createMock(DatabaseInterface::class); + $dbi->expects(self::once()) + ->method('fetchSingleColumn') + ->willReturnOnConsecutiveCalls( + [ + 'foo1', + 'foo2', + 'foo3', + 'foo4', + ], + ); + + $nodeContainer = new NodeDatabase($config, 'DB'); + $nodeTable = new NodeTable($config, 'node'); + $nodeContainer->addChild($nodeTable); + + $result = $nodeTable->getTriggers($dbi, 1); + self::assertCount(4, $result); + + self::assertSame('foo1', $result[0]->name); + self::assertSame('foo2', $result[1]->name); + self::assertSame('foo3', $result[2]->name); + self::assertSame('foo4', $result[3]->name); + } + + public function testGetTriggersWithDisableIS(): void + { + $config = Config::getInstance(); + $config->selectedServer['DisableIS'] = true; + $config->set('MaxNavigationItems', 2); + + $dbi = self::createMock(DatabaseInterface::class); + $resultStub = self::createMock(DummyResult::class); + $dbi->expects(self::once())->method('quoteString') + ->willReturnCallback(static fn (string $string): string => "'" . $string . "'"); + $dbi->expects(self::once()) + ->method('tryQuery') + ->with('SHOW TRIGGERS FROM `DB` WHERE `Table` = \'node\'') + ->willReturn($resultStub); + $resultStub->expects(self::once()) + ->method('fetchAllAssoc') + ->willReturnOnConsecutiveCalls( + [ + ['Trigger' => 'foo1'], + ['Trigger' => 'foo2'], + ['Trigger' => 'foo3'], + ['Trigger' => 'foo4'], + ], + ); + + $nodeContainer = new NodeDatabase($config, 'DB'); + $nodeTable = new NodeTable($config, 'node'); + $nodeContainer->addChild($nodeTable); + + $result = $nodeTable->getTriggers($dbi, 1); + self::assertCount(2, $result); + + self::assertSame('foo2', $result[0]->name); + self::assertSame('foo3', $result[1]->name); + } + + public function testGetIndexes(): void + { + $config = Config::getInstance(); + $config->selectedServer['DisableIS'] = true; + $config->set('MaxNavigationItems', 2); + + $dbi = self::createMock(DatabaseInterface::class); + $resultStub = self::createMock(DummyResult::class); + $dbi->expects(self::once()) + ->method('tryQuery') + ->with('SHOW INDEXES FROM `node` FROM `DB`') + ->willReturn($resultStub); + $resultStub->expects(self::once()) + ->method('fetchAllAssoc') + ->willReturnOnConsecutiveCalls( + [ + ['Key_name' => 'foo1'], + ['Key_name' => 'foo2'], + ['Key_name' => 'foo3'], + ['Key_name' => 'foo4'], + ], + ); + + $nodeContainer = new NodeDatabase($config, 'DB'); + $nodeTable = new NodeTable($config, 'node'); + $nodeContainer->addChild($nodeTable); + + $result = $nodeTable->getIndexes($dbi, 1); + self::assertCount(2, $result); + + self::assertSame('foo2', $result[0]->name); + self::assertSame('foo3', $result[1]->name); + } } diff --git a/tests/unit/Navigation/Nodes/NodeTest.php b/tests/unit/Navigation/Nodes/NodeTest.php index 86c2f2183c..ae179b54bb 100644 --- a/tests/unit/Navigation/Nodes/NodeTest.php +++ b/tests/unit/Navigation/Nodes/NodeTest.php @@ -295,7 +295,7 @@ final class NodeTest extends AbstractTestCase "WHERE TRUE AND `SCHEMA_NAME` NOT REGEXP 'regexpHideDb' ", $method->invoke($node, 'SCHEMA_NAME'), ); - unset($config->selectedServer['hide_db']); + $config->selectedServer['hide_db'] = ''; // When only_db directive is present and it's a single db $config->selectedServer['only_db'] = 'stringOnlyDb'; @@ -303,7 +303,7 @@ final class NodeTest extends AbstractTestCase "WHERE TRUE AND ( `SCHEMA_NAME` LIKE 'stringOnlyDb' ) ", $method->invoke($node, 'SCHEMA_NAME'), ); - unset($config->selectedServer['only_db']); + $config->selectedServer['only_db'] = ''; // When only_db directive is present and it's an array of dbs $config->selectedServer['only_db'] = ['onlyDbOne', 'onlyDbTwo']; @@ -311,7 +311,7 @@ final class NodeTest extends AbstractTestCase 'WHERE TRUE AND ( `SCHEMA_NAME` LIKE \'onlyDbOne\' OR `SCHEMA_NAME` LIKE \'onlyDbTwo\' ) ', $method->invoke($node, 'SCHEMA_NAME'), ); - unset($config->selectedServer['only_db']); + $config->selectedServer['only_db'] = ''; } /** @@ -322,7 +322,7 @@ final class NodeTest extends AbstractTestCase $config = Config::getInstance(); $config->selectedServer['DisableIS'] = false; $config->settings['NavigationTreeEnableGrouping'] = true; - $config->settings['FirstLevelNavigationItems'] = 20; + $config->set('FirstLevelNavigationItems', 20); $config->settings['NavigationTreeDbSeparator'] = '_'; $expectedSql = 'SELECT `SCHEMA_NAME` '; @@ -361,7 +361,7 @@ final class NodeTest extends AbstractTestCase $config = Config::getInstance(); $config->selectedServer['DisableIS'] = false; $config->settings['NavigationTreeEnableGrouping'] = false; - $config->settings['FirstLevelNavigationItems'] = 20; + $config->set('FirstLevelNavigationItems', 20); $expectedSql = 'SELECT `SCHEMA_NAME` '; $expectedSql .= 'FROM `INFORMATION_SCHEMA`.`SCHEMATA` '; @@ -386,7 +386,7 @@ final class NodeTest extends AbstractTestCase $config = Config::getInstance(); $config->selectedServer['DisableIS'] = true; $config->settings['NavigationTreeEnableGrouping'] = true; - $config->settings['FirstLevelNavigationItems'] = 10; + $config->set('FirstLevelNavigationItems', 2); $config->settings['NavigationTreeDbSeparator'] = '_'; $node = new Node($config, 'node'); @@ -398,9 +398,9 @@ final class NodeTest extends AbstractTestCase ->method('tryQuery') ->with("SHOW DATABASES WHERE TRUE AND `Database` LIKE '%db%' ") ->willReturn($resultStub); - $resultStub->expects(self::exactly(3)) - ->method('fetchRow') - ->willReturn(['0' => 'db'], ['0' => 'aa_db'], []); + $resultStub->expects(self::exactly(2)) + ->method('fetchValue') + ->willReturn('db', 'aa_db', 'foo', ''); $dbi->expects(self::once()) ->method('fetchSingleColumn') @@ -418,6 +418,40 @@ final class NodeTest extends AbstractTestCase $node->getData(new UserPrivileges(), 0, 'db'); } + /** + * Tests when DisableIS is true and navigation tree grouping disabled. + */ + public function testGetDataWithDisabledISAndGroupingDisabled(): void + { + $config = Config::getInstance(); + $config->selectedServer['DisableIS'] = true; + $config->settings['NavigationTreeEnableGrouping'] = false; + $config->set('FirstLevelNavigationItems', 2); + + $node = new Node($config, 'node'); + + $resultStub = self::createMock(DummyResult::class); + + $dbi = self::createMock(DatabaseInterface::class); + $dbi->expects(self::once()) + ->method('tryQuery') + ->with("SHOW DATABASES WHERE TRUE AND `Database` LIKE '%db%' ") + ->willReturn($resultStub); + $resultStub->expects(self::once()) + ->method('fetchAllColumn') + ->willReturn(['db', 'aa_db', 'foo']); + + $dbi->expects(self::once())->method('escapeMysqlWildcards') + ->willReturnArgument(0); + $dbi->expects(self::once())->method('quoteString') + ->willReturnCallback(static fn (string $string): string => "'" . $string . "'"); + + DatabaseInterface::$instance = $dbi; + $result = $node->getData(new UserPrivileges(), 0, 'db'); + + self::assertSame(['db', 'aa_db'], $result); + } + /** * Tests when DisableIS is false and navigation tree grouping enabled. */