Update website

This commit is contained in:
Guilhem Lavaux 2024-11-19 08:02:04 +01:00
parent 4413528994
commit 1d90fbf296
6865 changed files with 1091082 additions and 0 deletions

View file

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Partitioning;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Dbal\DatabaseName;
use PhpMyAdmin\Dbal\TableName;
use PhpMyAdmin\Util;
use function sprintf;
final class Maintenance
{
/** @var DatabaseInterface */
private $dbi;
public function __construct(DatabaseInterface $dbi)
{
$this->dbi = $dbi;
}
public function analyze(DatabaseName $db, TableName $table, string $partition): array
{
$query = sprintf(
'ALTER TABLE %s ANALYZE PARTITION %s;',
Util::backquote($table->getName()),
Util::backquote($partition)
);
$this->dbi->selectDb($db);
$result = $this->dbi->fetchResult($query);
$rows = [];
foreach ($result as $row) {
$rows[$row['Table']][] = $row;
}
return [$rows, $query];
}
public function check(DatabaseName $db, TableName $table, string $partition): array
{
$query = sprintf(
'ALTER TABLE %s CHECK PARTITION %s;',
Util::backquote($table->getName()),
Util::backquote($partition)
);
$this->dbi->selectDb($db);
$result = $this->dbi->fetchResult($query);
$rows = [];
foreach ($result as $row) {
$rows[$row['Table']][] = $row;
}
return [$rows, $query];
}
public function drop(DatabaseName $db, TableName $table, string $partition): array
{
$query = sprintf(
'ALTER TABLE %s DROP PARTITION %s;',
Util::backquote($table->getName()),
Util::backquote($partition)
);
$this->dbi->selectDb($db);
$result = $this->dbi->tryQuery($query);
return [(bool) $result, $query];
}
public function optimize(DatabaseName $db, TableName $table, string $partition): array
{
$query = sprintf(
'ALTER TABLE %s OPTIMIZE PARTITION %s;',
Util::backquote($table->getName()),
Util::backquote($partition)
);
$this->dbi->selectDb($db);
$result = $this->dbi->fetchResult($query);
$rows = [];
foreach ($result as $row) {
$rows[$row['Table']][] = $row;
}
return [$rows, $query];
}
/**
* @return array<int, bool|string>
* @psalm-return array{bool, string}
*/
public function rebuild(DatabaseName $db, TableName $table, string $partition): array
{
$query = sprintf(
'ALTER TABLE %s REBUILD PARTITION %s;',
Util::backquote($table->getName()),
Util::backquote($partition)
);
$this->dbi->selectDb($db);
$result = $this->dbi->tryQuery($query);
return [(bool) $result, $query];
}
public function repair(DatabaseName $db, TableName $table, string $partition): array
{
$query = sprintf(
'ALTER TABLE %s REPAIR PARTITION %s;',
Util::backquote($table->getName()),
Util::backquote($partition)
);
$this->dbi->selectDb($db);
$result = $this->dbi->fetchResult($query);
$rows = [];
foreach ($result as $row) {
$rows[$row['Table']][] = $row;
}
return [$rows, $query];
}
/**
* @return array<int, bool|string>
* @psalm-return array{bool, string}
*/
public function truncate(DatabaseName $db, TableName $table, string $partition): array
{
$query = sprintf(
'ALTER TABLE %s TRUNCATE PARTITION %s;',
Util::backquote($table->getName()),
Util::backquote($partition)
);
$this->dbi->selectDb($db);
$result = $this->dbi->tryQuery($query);
return [(bool) $result, $query];
}
}

View file

@ -0,0 +1,271 @@
<?php
/**
* Library for extracting information about the partitions
*/
declare(strict_types=1);
namespace PhpMyAdmin\Partitioning;
use function array_values;
/**
* base Partition Class
*/
class Partition extends SubPartition
{
/** @var string partition description */
protected $description;
/** @var SubPartition[] sub partitions */
protected $subPartitions = [];
/**
* Loads data from the fetched row from information_schema.PARTITIONS
*
* @param array $row fetched row
*/
protected function loadData(array $row): void
{
$this->name = $row['PARTITION_NAME'];
$this->ordinal = $row['PARTITION_ORDINAL_POSITION'];
$this->method = $row['PARTITION_METHOD'];
$this->expression = $row['PARTITION_EXPRESSION'];
$this->description = $row['PARTITION_DESCRIPTION'];
// no sub partitions, load all data to this object
if (! empty($row['SUBPARTITION_NAME'])) {
return;
}
$this->loadCommonData($row);
}
/**
* Returns the partition description
*
* @return string partition description
*/
public function getDescription()
{
return $this->description;
}
/**
* Add a sub partition
*
* @param SubPartition $partition Sub partition
*/
public function addSubPartition(SubPartition $partition): void
{
$this->subPartitions[] = $partition;
}
/**
* Whether there are sub partitions
*/
public function hasSubPartitions(): bool
{
return ! empty($this->subPartitions);
}
/**
* Returns the number of data rows
*
* @return int number of rows
*/
public function getRows()
{
if (empty($this->subPartitions)) {
return $this->rows;
}
$rows = 0;
foreach ($this->subPartitions as $subPartition) {
$rows += $subPartition->rows;
}
return $rows;
}
/**
* Returns the total data length
*
* @return int data length
*/
public function getDataLength()
{
if (empty($this->subPartitions)) {
return $this->dataLength;
}
$dataLength = 0;
foreach ($this->subPartitions as $subPartition) {
$dataLength += $subPartition->dataLength;
}
return $dataLength;
}
/**
* Returns the total index length
*
* @return int index length
*/
public function getIndexLength()
{
if (empty($this->subPartitions)) {
return $this->indexLength;
}
$indexLength = 0;
foreach ($this->subPartitions as $subPartition) {
$indexLength += $subPartition->indexLength;
}
return $indexLength;
}
/**
* Returns the list of sub partitions
*
* @return SubPartition[]
*/
public function getSubPartitions()
{
return $this->subPartitions;
}
/**
* Returns array of partitions for a specific db/table
*
* @param string $db database name
* @param string $table table name
*
* @return Partition[]
*/
public static function getPartitions($db, $table)
{
global $dbi;
if (self::havePartitioning()) {
$result = $dbi->fetchResult(
'SELECT * FROM `information_schema`.`PARTITIONS`'
. " WHERE `TABLE_SCHEMA` = '" . $dbi->escapeString($db)
. "' AND `TABLE_NAME` = '" . $dbi->escapeString($table) . "'"
);
if ($result) {
$partitionMap = [];
/** @var array $row */
foreach ($result as $row) {
if (isset($partitionMap[$row['PARTITION_NAME']])) {
$partition = $partitionMap[$row['PARTITION_NAME']];
} else {
$partition = new Partition($row);
$partitionMap[$row['PARTITION_NAME']] = $partition;
}
if (empty($row['SUBPARTITION_NAME'])) {
continue;
}
$parentPartition = $partition;
$partition = new SubPartition($row);
$parentPartition->addSubPartition($partition);
}
return array_values($partitionMap);
}
return [];
}
return [];
}
/**
* returns array of partition names for a specific db/table
*
* @param string $db database name
* @param string $table table name
*
* @return array of partition names
*/
public static function getPartitionNames($db, $table)
{
global $dbi;
if (self::havePartitioning()) {
return $dbi->fetchResult(
'SELECT DISTINCT `PARTITION_NAME` FROM `information_schema`.`PARTITIONS`'
. " WHERE `TABLE_SCHEMA` = '" . $dbi->escapeString($db)
. "' AND `TABLE_NAME` = '" . $dbi->escapeString($table) . "'"
);
}
return [];
}
/**
* returns the partition method used by the table.
*
* @param string $db database name
* @param string $table table name
*
* @return string|null partition method
*/
public static function getPartitionMethod($db, $table)
{
global $dbi;
if (self::havePartitioning()) {
$partition_method = $dbi->fetchResult(
'SELECT `PARTITION_METHOD` FROM `information_schema`.`PARTITIONS`'
. " WHERE `TABLE_SCHEMA` = '" . $dbi->escapeString($db) . "'"
. " AND `TABLE_NAME` = '" . $dbi->escapeString($table) . "'"
. ' LIMIT 1'
);
if (! empty($partition_method)) {
return $partition_method[0];
}
}
return null;
}
/**
* checks if MySQL server supports partitioning
*
* @static
* @staticvar bool $have_partitioning
* @staticvar bool $already_checked
*/
public static function havePartitioning(): bool
{
global $dbi;
static $have_partitioning = false;
static $already_checked = false;
if (! $already_checked) {
if ($dbi->getVersion() < 50600) {
if ($dbi->fetchValue('SELECT @@have_partitioning;')) {
$have_partitioning = true;
}
} elseif ($dbi->getVersion() >= 80000) {
$have_partitioning = true;
} else {
// see https://dev.mysql.com/doc/refman/5.6/en/partitioning.html
$plugins = $dbi->fetchResult('SHOW PLUGINS');
foreach ($plugins as $value) {
if ($value['Name'] === 'partition') {
$have_partitioning = true;
break;
}
}
}
$already_checked = true;
}
return $have_partitioning;
}
}

View file

@ -0,0 +1,154 @@
<?php
/**
* Library for extracting information about the sub-partitions
*/
declare(strict_types=1);
namespace PhpMyAdmin\Partitioning;
/**
* Represents a sub partition of a table
*/
class SubPartition
{
/** @var string the database */
protected $db;
/** @var string the table */
protected $table;
/** @var string partition name */
protected $name;
/** @var int ordinal */
protected $ordinal;
/** @var string partition method */
protected $method;
/** @var string partition expression */
protected $expression;
/** @var int no of table rows in the partition */
protected $rows;
/** @var int data length */
protected $dataLength;
/** @var int index length */
protected $indexLength;
/** @var string partition comment */
protected $comment;
/**
* Constructs a partition
*
* @param array $row fetched row from information_schema.PARTITIONS
*/
public function __construct(array $row)
{
$this->db = $row['TABLE_SCHEMA'];
$this->table = $row['TABLE_NAME'];
$this->loadData($row);
}
/**
* Loads data from the fetched row from information_schema.PARTITIONS
*
* @param array $row fetched row
*/
protected function loadData(array $row): void
{
$this->name = $row['SUBPARTITION_NAME'];
$this->ordinal = $row['SUBPARTITION_ORDINAL_POSITION'];
$this->method = $row['SUBPARTITION_METHOD'];
$this->expression = $row['SUBPARTITION_EXPRESSION'];
$this->loadCommonData($row);
}
/**
* Loads some data that is common to both partitions and sub partitions
*
* @param array $row fetched row
*/
protected function loadCommonData(array $row): void
{
$this->rows = $row['TABLE_ROWS'];
$this->dataLength = $row['DATA_LENGTH'];
$this->indexLength = $row['INDEX_LENGTH'];
$this->comment = $row['PARTITION_COMMENT'];
}
/**
* Return the partition name
*
* @return string partition name
*/
public function getName()
{
return $this->name;
}
/**
* Return the ordinal of the partition
*
* @return int the ordinal
*/
public function getOrdinal()
{
return $this->ordinal;
}
/**
* Returns the partition method
*
* @return string partition method
*/
public function getMethod()
{
return $this->method;
}
/**
* Returns the partition expression
*
* @return string partition expression
*/
public function getExpression()
{
return $this->expression;
}
/**
* Returns the number of data rows
*
* @return int number of rows
*/
public function getRows()
{
return $this->rows;
}
/**
* Returns the data length
*
* @return int data length
*/
public function getDataLength()
{
return $this->dataLength;
}
/**
* Returns the index length
*
* @return int index length
*/
public function getIndexLength()
{
return $this->indexLength;
}
/**
* Returns the partition comment
*
* @return string partition comment
*/
public function getComment()
{
return $this->comment;
}
}

View file

@ -0,0 +1,198 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Partitioning;
use function array_intersect_key;
use function array_merge;
use function array_splice;
use function is_numeric;
use function min;
final class TablePartitionDefinition
{
/**
* @param array|null $details Details that may be pre-filled
*
* @return array
*/
public static function getDetails(?array $details = null): array
{
if (! isset($details)) {
$details = self::generateDetails();
}
return $details;
}
/**
* @return array
*/
private static function generateDetails(): array
{
$partitionDetails = self::extractDetailsFromRequest();
// Only LIST and RANGE type parameters allow subpartitioning
$partitionDetails['can_have_subpartitions'] = $partitionDetails['partition_count'] > 1
&& isset($partitionDetails['partition_by'])
&& ($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');
return self::extractPartitions($partitionDetails);
}
/**
* Extract some partitioning and subpartitioning parameters from the request
*
* @return array
*/
private static function extractDetailsFromRequest(): array
{
$partitionParams = [
'partition_by' => null,
'partition_expr' => null,
'subpartition_by' => null,
'subpartition_expr' => null,
];
//Initialize details with values to "null" if not in request
$details = array_merge(
$partitionParams,
//Keep $_POST values, but only for keys that are in $partitionParams
array_intersect_key($_POST, $partitionParams)
);
$details['partition_count'] = self::extractPartitionCount('partition_count') ?: 0;
$details['subpartition_count'] = self::extractPartitionCount('subpartition_count') ?: 0;
return $details;
}
/**
* @param string $paramLabel Label searched in request
*/
private static function extractPartitionCount(string $paramLabel): int
{
if (isset($_POST[$paramLabel]) && is_numeric($_POST[$paramLabel])) {
// MySQL's limit is 8192, so do not allow more
// @see https://dev.mysql.com/doc/refman/en/partitioning-limitations.html
$count = min((int) $_POST[$paramLabel], 8192);
} else {
$count = 0;
}
return $count;
}
/**
* @param array $partitionDetails Details of partitions
*
* @return array
*/
private static function extractPartitions(array $partitionDetails): array
{
$partitionCount = $partitionDetails['partition_count'];
$subpartitionCount = $partitionDetails['subpartition_count'];
// No partitions
if ($partitionCount <= 1) {
return $partitionDetails;
}
// Has partitions
$partitions = $_POST['partitions'] ?? [];
// Remove details of the additional partitions
// when number of partitions have been reduced
array_splice($partitions, $partitionCount);
for ($i = 0; $i < $partitionCount; $i++) {
if (! isset($partitions[$i])) { // Newly added partition
$partitions[$i] = [
'name' => 'p' . $i,
'value_type' => '',
'value' => '',
'engine' => '',
'comment' => '',
'data_directory' => '',
'index_directory' => '',
'max_rows' => '',
'min_rows' => '',
'tablespace' => '',
'node_group' => '',
];
}
$partition =& $partitions[$i];
$partition['prefix'] = 'partitions[' . $i . ']';
// Changing from HASH/KEY to RANGE/LIST
if (! isset($partition['value_type'])) {
$partition['value_type'] = '';
$partition['value'] = '';
}
if (! isset($partition['engine'])) { // When removing subpartitioning
$partition['engine'] = '';
$partition['comment'] = '';
$partition['data_directory'] = '';
$partition['index_directory'] = '';
$partition['max_rows'] = '';
$partition['min_rows'] = '';
$partition['tablespace'] = '';
$partition['node_group'] = '';
}
// No subpartitions
if ($subpartitionCount < 2 || $partitionDetails['can_have_subpartitions'] !== true) {
unset($partition['subpartitions'], $partition['subpartition_count']);
continue;
}
// Has subpartitions
$partition['subpartition_count'] = $subpartitionCount;
if (! isset($partition['subpartitions'])) {
$partition['subpartitions'] = [];
}
$subpartitions =& $partition['subpartitions'];
// Remove details of the additional subpartitions
// when number of subpartitions have been reduced
array_splice($subpartitions, $subpartitionCount);
for ($j = 0; $j < $subpartitionCount; $j++) {
if (! isset($subpartitions[$j])) { // Newly added subpartition
$subpartitions[$j] = [
'name' => $partition['name'] . '_s' . $j,
'engine' => '',
'comment' => '',
'data_directory' => '',
'index_directory' => '',
'max_rows' => '',
'min_rows' => '',
'tablespace' => '',
'node_group' => '',
];
}
$subpartitions[$j]['prefix'] = 'partitions[' . $i . ']'
. '[subpartitions][' . $j . ']';
}
}
$partitionDetails['partitions'] = $partitions;
return $partitionDetails;
}
}