1
0
mirror of https://github.com/phpmyadmin/phpmyadmin.git synced 2026-10-29 21:24:44 +03:00

Merge #16945 - Add suport for Mroonga engine

Pull-request: #16945

Signed-off-by: William Desportes <williamdes@wdes.fr>
This commit is contained in:
William Desportes
2021-07-13 20:54:01 +02:00
6 changed files with 673 additions and 2 deletions

View File

@@ -22,6 +22,7 @@ use PhpMyAdmin\ReplicationInfo;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\Sanitize;
use PhpMyAdmin\Sql;
use PhpMyAdmin\StorageEngine;
use PhpMyAdmin\Table;
use PhpMyAdmin\Template;
use PhpMyAdmin\Tracker;
@@ -1054,6 +1055,17 @@ class StructureController extends AbstractController
case 'SYSTEM VIEW':
// possibly a view, do nothing
break;
case 'Mroonga':
// The idea is to show the size only if Mroonga is available,
// in other case the old unknown message will appear
if (StorageEngine::hasMroongaEngine()) {
[$currentTable, $formattedSize, $unit, $sumSize] = $this->getValuesForMroongaTable(
$currentTable,
$sumSize
);
break;
}
// no break, go to default case
default:
// Unknown table type.
if ($this->isShowStats) {
@@ -1194,6 +1206,40 @@ class StructureController extends AbstractController
];
}
/**
* Get values for Mroonga table
*
* @param array $currentTable current table
* @param int $sumSize sum size
*
* @return array
*/
protected function getValuesForMroongaTable(
array $currentTable,
int $sumSize
): array {
$formattedSize = '';
$unit = '';
if ($this->isShowStats) {
/** @var int $tblsize */
$tblsize = $currentTable['Data_length'] + $currentTable['Index_length'];
$sumSize += $tblsize;
[$formattedSize, $unit] = Util::formatByteDown(
$tblsize,
3,
($tblsize > 0 ? 1 : 0)
);
}
return [
$currentTable,
$formattedSize,
$unit,
$sumSize,
];
}
public function showCreate(): void
{
$selected = $_POST['selected_tbl'] ?? [];

View File

@@ -400,6 +400,28 @@ class DatabaseInterface implements DbalInterface
$link
);
// here, we check for Mroonga engine and compute the good data_length and index_length
// in the StructureController only we need to sum the two values as the other engines
foreach ($tables as $one_database_name => $one_database_tables) {
foreach ($one_database_tables as $one_table_name => $one_table_data) {
if ($one_table_data['Engine'] !== 'Mroonga') {
continue;
}
if (! StorageEngine::hasMroongaEngine()) {
continue;
}
[
$tables[$one_database_name][$one_table_name]['Data_length'],
$tables[$one_database_name][$one_table_name]['Index_length'],
] = StorageEngine::getMroongaLengths(
$one_database_name,
$one_table_name
);
}
}
if ($sort_by === 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
// here, the array's first key is by schema name
foreach ($tables as $one_database_name => $one_database_tables) {
@@ -490,6 +512,26 @@ class DatabaseInterface implements DbalInterface
$each_tables = $this->fetchResult($sql, 'Name', null, $link);
// here, we check for Mroonga engine and compute the good data_length and index_length
// in the StructureController only we need to sum the two values as the other engines
foreach ($each_tables as $table_name => $table_data) {
if ($table_data['Engine'] !== 'Mroonga') {
continue;
}
if (! StorageEngine::hasMroongaEngine()) {
continue;
}
[
$each_tables[$table_name]['Data_length'],
$each_tables[$table_name]['Index_length'],
] = StorageEngine::getMroongaLengths(
$each_database,
$table_name
);
}
// Sort naturally if the config allows it and we're sorting
// the Name column.
if ($sort_by === 'Name' && $GLOBALS['cfg']['NaturalOrder']) {

View File

@@ -21,11 +21,16 @@ use PhpMyAdmin\Utils\SessionCache;
use function __;
use function array_key_exists;
use function array_keys;
use function explode;
use function htmlspecialchars;
use function in_array;
use function json_decode;
use function mb_stripos;
use function mb_strtolower;
use function sprintf;
use function strlen;
use function strncmp;
/**
* Library for extracting information about the available storage engines
@@ -128,6 +133,95 @@ class StorageEngine
return $storage_engines;
}
/**
* Returns if Mroonga is available to be used
*
* This is public to be used in the StructureComtroller, the first release
* of this function was looking Mroonga in the engines list but this second
* method checks too that mroonga is installed successfully
*
* @return bool true when the mroonga_command is found
*/
public static function hasMroongaEngine(): bool
{
global $dbi;
$cacheKey = 'storage-engine.mroonga.has.mroonga_command';
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey, false);
}
$supportsMroonga = $dbi->tryQuery('SELECT mroonga_command(\'object_list\');') !== false;
Cache::set($cacheKey, $supportsMroonga);
return $supportsMroonga;
}
/**
* Get the lengths of a table of database
*
* @param string $dbName DB name
* @param string $tableName Table name
*
* @return int[]
*/
public static function getMroongaLengths(string $dbName, string $tableName): array
{
global $dbi;
$cacheKey = 'storage-engine.mroonga.object_list.' . $dbName;
$dbi->selectDb($dbName);// Needed for mroonga_command calls
if (! Cache::has($cacheKey)) {
$result = $dbi->fetchSingleRow('SELECT mroonga_command(\'object_list\');', 'NUM');
$objectList = (array) json_decode($result[0] ?? '', true);
foreach ($objectList as $mroongaName => $mroongaData) {
/**
* We only need the objects of table or column types, more info:
* - https://groonga.org/docs/reference/commands/object_list.html#object-type
* - https://groonga.org/docs/reference/commands/object_inspect.html#table-type-id
* - https://groonga.org/docs/reference/commands/object_inspect.html#column-type-raw-id
*/
if (in_array($mroongaData['type']['id'], [48, 49, 50, 51, 64, 65, 72])) {
continue;
}
unset($objectList[$mroongaName]);
}
// At this point, we can remove all the data because only need the mroongaName values
Cache::set($cacheKey, array_keys($objectList));
}
/** @var string[] */
$objectList = Cache::get($cacheKey, []);
$dataLength = 0;
$indexLength = 0;
foreach ($objectList as $mroongaName) {
if (strncmp($tableName, $mroongaName, strlen($tableName)) !== 0) {
continue;
}
$result = $dbi->fetchSingleRow('SELECT mroonga_command(\'object_inspect ' . $mroongaName . '\');', 'NUM');
$decodedData = json_decode($result[0] ?? '', true);
if ($decodedData === null) {
// Invalid for some strange reason, maybe query failed
continue;
}
$indexPrefix = $tableName . '#' . $tableName;
if (strncmp($indexPrefix, $mroongaName, strlen($indexPrefix)) === 0) {
$indexLength += $decodedData['disk_usage'];
continue;
}
$dataLength += $decodedData['disk_usage'];
}
return [$dataLength, $indexLength];
}
/**
* @return array<int|string, array<string, mixed>>
*/

View File

@@ -559,4 +559,97 @@ class StructureControllerTest extends AbstractTestCase
$this->assertStringContainsString('id="overhead"', $result);
$this->assertStringContainsString('9.8', $result);
}
/**
* Tests for getValuesForMroongaTable()
*/
public function testGetValuesForMroongaTable(): void
{
global $containerBuilder;
parent::loadContainerBuilder();
parent::loadDbiIntoContainerBuilder();
$GLOBALS['db'] = 'testdb';
$GLOBALS['table'] = 'mytable';
$containerBuilder->setParameter('db', $GLOBALS['db']);
$containerBuilder->setParameter('table', $GLOBALS['table']);
/** @var StructureController $structureController */
$structureController = $containerBuilder->get(StructureController::class);
$this->assertSame(
[
[],
'',
'',
0,
],
$this->callFunction(
$structureController,
StructureController::class,
'getValuesForMroongaTable',
[
[],
0,
]
)
);
// Enable stats
$GLOBALS['cfg']['ShowStats'] = true;
$this->callFunction(
$structureController,
StructureController::class,
'getDatabaseInfo',
['']
);
$this->assertSame(
[
[
'Data_length' => 45,
'Index_length' => 60,
],
'105',
'B',
105,
],
$this->callFunction(
$structureController,
StructureController::class,
'getValuesForMroongaTable',
[
[
'Data_length' => 45,
'Index_length' => 60,
],
0,
]
)
);
$this->assertSame(
[
[
'Data_length' => 45,
'Index_length' => 60,
],
'105',
'B',
180, //105 + 75
],
$this->callFunction(
$structureController,
StructureController::class,
'getValuesForMroongaTable',
[
[
'Data_length' => 45,
'Index_length' => 60,
],
75,
]
)
);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests;
use PhpMyAdmin\Cache;
use PhpMyAdmin\Engines\Bdb;
use PhpMyAdmin\Engines\Berkeleydb;
use PhpMyAdmin\Engines\Binlog;
@@ -19,6 +20,8 @@ use PhpMyAdmin\Engines\PerformanceSchema;
use PhpMyAdmin\StorageEngine;
use PHPUnit\Framework\MockObject\MockObject;
use function json_encode;
/**
* @covers \PhpMyAdmin\StorageEngine
*/
@@ -319,4 +322,397 @@ class StorageEngineTest extends AbstractTestCase
$this->object->resolveTypeSize(12)
);
}
/**
* @covers hasMroongaEngine
*/
public function testHasMroongaEngine(): void
{
$this->dummyDbi->addResult('SELECT mroonga_command(\'object_list\');', [
[
(string) json_encode([]), // Fake result
],
]);
$this->assertTrue(StorageEngine::hasMroongaEngine());
$this->assertTrue(StorageEngine::hasMroongaEngine()); // Does not call any query
Cache::remove('storage-engine.mroonga.has.mroonga_command'); // Cache clear
$this->dummyDbi->addResult('SELECT mroonga_command(\'object_list\');', false);
$this->assertFalse(StorageEngine::hasMroongaEngine());
$this->assertAllQueriesConsumed();
}
/**
* @covers getMroongaLengths
*/
public function testGetMroongaLengths(): void
{
$this->dummyDbi->addResult('SELECT mroonga_command(\'object_list\');', [
[
// Partial
(string) json_encode([
'WGS84GeoPoint' => [
'id' => 18,
'name' => 'WGS84GeoPoint',
'opened' => true,
'n_elements' => 4,
'type' => [
'id' => 32,
'name' => 'type',
],
'flags' => [
'value' => 24,
'names' => 'KEY_GEO_POINT',
],
'path' => null,
'size' => 8,
],
'mroonga_operations' => [
'type' => [
'id' => 51,
'name' => 'table:no_key',
],
],
'mroonga_operations.type' => [
'type' => [
'id' => 65,
'name' => 'column:var_size',
],
],
'mroonga_operations.table' => [
'type' => [
'id' => 65,
'name' => 'column:var_size',
],
],
'mroonga_operations.record' => [
'type' => [
'id' => 64,
'name' => 'column:fix_size',
],
],
'idx_correo' => [
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
],
'idx_correo.id' => [
'type' => [
'id' => 64,
'name' => 'column:fix_size',
],
],
'idx_correo.search' => [
'type' => [
'id' => 65,
'name' => 'column:var_size',
],
],
'idx_correo#idx_correo_search' => [
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
],
'idx_correo#idx_correo_search.index' => [
'type' => [
'id' => 72,
'name' => 'column:index',
],
],
]),
],
]);
$this->dummyDbi->addResult('SELECT mroonga_command(\'object_inspect idx_correo\');', [
[
// Partial
(string) json_encode([
'id' => 265,
'name' => 'idx_correo',
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
'key' => [
'type' => [
'id' => 8,
'name' => 'Int32',
'type' => [
'id' => 32,
'name' => 'type',
],
'size' => 4,
],
'total_size' => 0,
'max_total_size' => 4294967294,
],
'value' => ['type' => null],
'n_records' => 0,
'disk_usage' => 4243456,
]),
],
]);
$this->dummyDbi->addResult('SELECT mroonga_command(\'object_inspect idx_correo.id\');', [
[
// Full object
(string) json_encode([
'id' => 266,
'name' => 'id',
'table' => [
'id' => 265,
'name' => 'idx_correo',
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
'key' => [
'type' => [
'id' => 8,
'name' => 'Int32',
'type' => [
'id' => 32,
'name' => 'type',
],
'size' => 4,
],
'total_size' => 0,
'max_total_size' => 4294967294,
],
'value' => ['type' => null],
'n_records' => 0,
'disk_usage' => 4243456,
],
'full_name' => 'idx_correo.id',
'type' => [
'name' => 'scalar',
'raw' => [
'id' => 64,
'name' => 'column:fix_size',
],
],
'value' => [
'type' => [
'id' => 8,
'name' => 'Int32',
'type' => [
'id' => 32,
'name' => 'type',
],
'size' => 4,
],
'compress' => null,
],
'disk_usage' => 4096,
]),
],
]);
$this->dummyDbi->addResult('SELECT mroonga_command(\'object_inspect idx_correo.search\');', [
[
// Full object
(string) json_encode([
'id' => 267,
'name' => 'search',
'table' => [
'id' => 265,
'name' => 'idx_correo',
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
'key' => [
'type' => [
'id' => 8,
'name' => 'Int32',
'type' => [
'id' => 32,
'name' => 'type',
],
'size' => 4,
],
'total_size' => 0,
'max_total_size' => 4294967294,
],
'value' => ['type' => null],
'n_records' => 0,
'disk_usage' => 4243456,
],
'full_name' => 'idx_correo.search',
'type' => [
'name' => 'scalar',
'raw' => [
'id' => 65,
'name' => 'column:var_size',
],
],
'value' => [
'type' => [
'id' => 16,
'name' => 'LongText',
'type' => [
'id' => 32,
'name' => 'type',
],
'size' => 2147483648,
],
'compress' => null,
],
'disk_usage' => 274432,
]),
],
]);
$this->dummyDbi->addResult('SELECT mroonga_command(\'object_inspect idx_correo#idx_correo_search\');', [
[
// Partial
(string) json_encode([
'id' => 268,
'name' => 'idx_correo#idx_correo_search',
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
'key' => [
'type' => [
'id' => 14,
'name' => 'ShortText',
'type' => [
'id' => 32,
'name' => 'type',
],
'size' => 4096,
],
'total_size' => 0,
'max_total_size' => 4294967294,
],
'value' => ['type' => null],
'n_records' => 0,
'disk_usage' => 12878,
]),
],
]);
$this->dummyDbi->addResult('SELECT mroonga_command(\'object_inspect idx_correo#idx_correo_search.index\');', [
[
// Full object
(string) json_encode([
'id' => 269,
'name' => 'index',
'table' => [
'id' => 268,
'name' => 'idx_correo#idx_correo_search',
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
'key' => [
'type' => [
'id' => 14,
'name' => 'ShortText',
'type' => [
'id' => 32,
'name' => 'type',
],
'size' => 4096,
],
'total_size' => 0,
'max_total_size' => 4294967294,
],
'value' => ['type' => null],
'n_records' => 0,
'disk_usage' => 4243456,
],
'full_name' => 'idx_correo#idx_correo_search.index',
'type' => [
'name' => 'index',
'raw' => [
'id' => 72,
'name' => 'column:index',
],
],
'value' => [
'type' => [
'id' => 265,
'name' => 'idx_correo',
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
'size' => 4,
],
'section' => false,
'weight' => false,
'position' => true,
'size' => 'normal',
'statistics' => [
'max_section_id' => 0,
'n_garbage_segments' => 0,
'max_array_segment_id' => 0,
'n_array_segments' => 0,
'max_buffer_segment_id' => 0,
'n_buffer_segments' => 0,
'max_in_use_physical_segment_id' => 0,
'n_unmanaged_segments' => 0,
'total_chunk_size' => 0,
'max_in_use_chunk_id' => 0,
'n_garbage_chunks' => [
0 => 0,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
7 => 0,
8 => 0,
9 => 0,
10 => 0,
11 => 0,
12 => 0,
13 => 0,
14 => 0,
],
],
],
'sources' => [
0 => [
'id' => 267,
'name' => 'search',
'table' => [
'id' => 265,
'name' => 'idx_correo',
'type' => [
'id' => 49,
'name' => 'table:pat_key',
],
'key' => [
'type' => [
'id' => 8,
'name' => 'Int32',
'type' => [
'id' => 32,
'name' => 'type',
],
'size' => 4,
],
'total_size' => 0,
'max_total_size' => 4294967294,
],
'value' => ['type' => null],
'n_records' => 0,
'disk_usage' => 4243456,
],
'full_name' => 'idx_correo.search',
],
],
'disk_usage' => 565248,
]),
],
]);
$lengths = StorageEngine::getMroongaLengths('my_db', 'idx_correo');
$this->assertSame([4521984, 578126], $lengths);
$this->assertAllQueriesConsumed();
}
}

View File

@@ -2345,8 +2345,8 @@ class DbiDummy implements DbiExtension
],
[
'query' => 'SHOW TABLE STATUS FROM `PMA_db` WHERE `Name` LIKE \'PMA\_table%\'',
'columns' => ['Name'],
'result' => [['PMA_table']],
'columns' => ['Name', 'Engine'],
'result' => [['PMA_table', 'InnoDB']],
],
[
'query' => 'SELECT `id` FROM `table_1` WHERE `id` > 10 AND (`id` <> 20)',