gl-website-deployer/admin/phpMyAdmin/libraries/classes/Controllers/Table/Structure/PartitioningController.php
2024-11-23 20:45:29 +01:00

283 lines
10 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table\Structure;
use PhpMyAdmin\Config\PageSettings;
use PhpMyAdmin\Controllers\Table\AbstractController;
use PhpMyAdmin\Controllers\Table\StructureController;
use PhpMyAdmin\CreateAddField;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Message;
use PhpMyAdmin\Partitioning\TablePartitionDefinition;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
use PhpMyAdmin\StorageEngine;
use PhpMyAdmin\Table;
use PhpMyAdmin\Template;
use PhpMyAdmin\Util;
use function __;
use function count;
use function strpos;
use function strrpos;
use function substr;
use function trim;
final class PartitioningController extends AbstractController
{
/** @var DatabaseInterface */
private $dbi;
/** @var CreateAddField */
private $createAddField;
/** @var StructureController */
private $structureController;
public function __construct(
ResponseRenderer $response,
Template $template,
string $db,
string $table,
DatabaseInterface $dbi,
CreateAddField $createAddField,
StructureController $structureController
) {
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
$this->createAddField = $createAddField;
$this->structureController = $structureController;
}
public function __invoke(): void
{
if (isset($_POST['save_partitioning'])) {
$this->dbi->selectDb($this->db);
$this->updatePartitioning();
($this->structureController)();
return;
}
$pageSettings = new PageSettings('TableStructure');
$this->response->addHTML($pageSettings->getErrorHTML());
$this->response->addHTML($pageSettings->getHTML());
$this->addScriptFiles(['table/structure.js', 'indexes.js']);
$partitionDetails = null;
if (! isset($_POST['partition_by'])) {
$partitionDetails = $this->extractPartitionDetails();
}
$storageEngines = StorageEngine::getArray();
$partitionDetails = TablePartitionDefinition::getDetails($partitionDetails);
$this->render('table/structure/partition_definition_form', [
'db' => $this->db,
'table' => $this->table,
'partition_details' => $partitionDetails,
'storage_engines' => $storageEngines,
]);
}
/**
* Extracts partition details from CREATE TABLE statement
*
* @return array<string, array<int, array<string, mixed>>|bool|int|string>|null array of partition details
*/
private function extractPartitionDetails(): ?array
{
$createTable = (new Table($this->table, $this->db))->showCreate();
if (! $createTable) {
return null;
}
$parser = new Parser($createTable);
/**
* @var CreateStatement $stmt
*/
$stmt = $parser->statements[0];
$partitionDetails = [];
$partitionDetails['partition_by'] = '';
$partitionDetails['partition_expr'] = '';
$partitionDetails['partition_count'] = 0;
if (! empty($stmt->partitionBy)) {
$openPos = strpos($stmt->partitionBy, '(');
$closePos = strrpos($stmt->partitionBy, ')');
if ($openPos !== false && $closePos !== false) {
$partitionDetails['partition_by'] = trim(substr($stmt->partitionBy, 0, $openPos));
$partitionDetails['partition_expr'] = trim(substr(
$stmt->partitionBy,
$openPos + 1,
$closePos - ($openPos + 1)
));
$count = $stmt->partitionsNum ?? count($stmt->partitions);
$partitionDetails['partition_count'] = $count;
}
}
$partitionDetails['subpartition_by'] = '';
$partitionDetails['subpartition_expr'] = '';
$partitionDetails['subpartition_count'] = 0;
if (! empty($stmt->subpartitionBy)) {
$openPos = strpos($stmt->subpartitionBy, '(');
$closePos = strrpos($stmt->subpartitionBy, ')');
if ($openPos !== false && $closePos !== false) {
$partitionDetails['subpartition_by'] = trim(substr($stmt->subpartitionBy, 0, $openPos));
$partitionDetails['subpartition_expr'] = trim(substr(
$stmt->subpartitionBy,
$openPos + 1,
$closePos - ($openPos + 1)
));
$count = $stmt->subpartitionsNum ?? count($stmt->partitions[0]->subpartitions);
$partitionDetails['subpartition_count'] = $count;
}
}
// Only LIST and RANGE type parameters allow subpartitioning
$partitionDetails['can_have_subpartitions'] = $partitionDetails['partition_count'] > 1
&& ($partitionDetails['partition_by'] === 'RANGE'
|| $partitionDetails['partition_by'] === 'RANGE COLUMNS'
|| $partitionDetails['partition_by'] === 'LIST'
|| $partitionDetails['partition_by'] === 'LIST COLUMNS');
// Values are specified only for LIST and RANGE type partitions
$partitionDetails['value_enabled'] = isset($partitionDetails['partition_by'])
&& ($partitionDetails['partition_by'] === 'RANGE'
|| $partitionDetails['partition_by'] === 'RANGE COLUMNS'
|| $partitionDetails['partition_by'] === 'LIST'
|| $partitionDetails['partition_by'] === 'LIST COLUMNS');
$partitionDetails['partitions'] = [];
for ($i = 0, $iMax = $partitionDetails['partition_count']; $i < $iMax; $i++) {
if (! isset($stmt->partitions[$i])) {
$partitionDetails['partitions'][$i] = [
'name' => 'p' . $i,
'value_type' => '',
'value' => '',
'engine' => '',
'comment' => '',
'data_directory' => '',
'index_directory' => '',
'max_rows' => '',
'min_rows' => '',
'tablespace' => '',
'node_group' => '',
];
} else {
$p = $stmt->partitions[$i];
$type = $p->type;
$expr = trim((string) $p->expr, '()');
if ($expr === 'MAXVALUE') {
$type .= ' MAXVALUE';
$expr = '';
}
$partitionDetails['partitions'][$i] = [
'name' => $p->name,
'value_type' => $type,
'value' => $expr,
'engine' => $p->options->has('ENGINE', true),
'comment' => trim((string) $p->options->has('COMMENT', true), "'"),
'data_directory' => trim((string) $p->options->has('DATA DIRECTORY', true), "'"),
'index_directory' => trim((string) $p->options->has('INDEX_DIRECTORY', true), "'"),
'max_rows' => $p->options->has('MAX_ROWS', true),
'min_rows' => $p->options->has('MIN_ROWS', true),
'tablespace' => $p->options->has('TABLESPACE', true),
'node_group' => $p->options->has('NODEGROUP', true),
];
}
$partition =& $partitionDetails['partitions'][$i];
$partition['prefix'] = 'partitions[' . $i . ']';
if ($partitionDetails['subpartition_count'] <= 1) {
continue;
}
$partition['subpartition_count'] = $partitionDetails['subpartition_count'];
$partition['subpartitions'] = [];
for ($j = 0, $jMax = $partitionDetails['subpartition_count']; $j < $jMax; $j++) {
if (! isset($stmt->partitions[$i]->subpartitions[$j])) {
$partition['subpartitions'][$j] = [
'name' => $partition['name'] . '_s' . $j,
'engine' => '',
'comment' => '',
'data_directory' => '',
'index_directory' => '',
'max_rows' => '',
'min_rows' => '',
'tablespace' => '',
'node_group' => '',
];
} else {
$sp = $stmt->partitions[$i]->subpartitions[$j];
$partition['subpartitions'][$j] = [
'name' => $sp->name,
'engine' => $sp->options->has('ENGINE', true),
'comment' => trim((string) $sp->options->has('COMMENT', true), "'"),
'data_directory' => trim((string) $sp->options->has('DATA DIRECTORY', true), "'"),
'index_directory' => trim((string) $sp->options->has('INDEX_DIRECTORY', true), "'"),
'max_rows' => $sp->options->has('MAX_ROWS', true),
'min_rows' => $sp->options->has('MIN_ROWS', true),
'tablespace' => $sp->options->has('TABLESPACE', true),
'node_group' => $sp->options->has('NODEGROUP', true),
];
}
$subpartition =& $partition['subpartitions'][$j];
$subpartition['prefix'] = 'partitions[' . $i . ']'
. '[subpartitions][' . $j . ']';
}
}
return $partitionDetails;
}
private function updatePartitioning(): void
{
$sql_query = 'ALTER TABLE ' . Util::backquote($this->table) . ' '
. $this->createAddField->getPartitionsDefinition();
// Execute alter query
$result = $this->dbi->tryQuery($sql_query);
if ($result === false) {
$this->response->setRequestStatus(false);
$this->response->addJSON(
'message',
Message::rawError(
__('Query error') . ':<br>' . $this->dbi->getError()
)
);
return;
}
$message = Message::success(
__('Table %1$s has been altered successfully.')
);
$message->addParam($this->table);
$this->response->addHTML(
Generator::getMessage($message, $sql_query, 'success')
);
}
}