3 declare(strict_types
=1);
7 use PhpMyAdmin\Html\Generator
;
8 use PhpMyAdmin\Identifiers\DatabaseName
;
9 use PhpMyAdmin\Table\Table
;
13 use function in_array
;
14 use function json_decode
;
15 use function preg_replace
;
19 * Set of functions for /table/create and /table/add-field
23 public function __construct(private DatabaseInterface
$dbi)
28 * Transforms the radio button field_key into 4 arrays
30 * @return mixed[] An array of arrays which represents column keys for each index type
31 * @psalm-return array{int, array, array, array, array, array}
33 private function getIndexedColumns(): array
35 $fieldCount = count($_POST['field_name']);
36 $fieldPrimary = json_decode($_POST['primary_indexes'], true);
37 $fieldIndex = json_decode($_POST['indexes'], true);
38 $fieldUnique = json_decode($_POST['unique_indexes'], true);
39 $fieldFullText = json_decode($_POST['fulltext_indexes'], true);
40 $fieldSpatial = json_decode($_POST['spatial_indexes'], true);
42 return [$fieldCount, $fieldPrimary, $fieldIndex, $fieldUnique, $fieldFullText, $fieldSpatial];
46 * Initiate the column creation statement according to the table creation or
47 * add columns to a existing table
49 * @param int $fieldCount number of columns
50 * @param bool $isCreateTable true if requirement is to get the statement
53 * @return mixed[] An array of initial sql statements
54 * according to the request
56 private function buildColumnCreationStatement(
58 bool $isCreateTable = true,
62 for ($i = 0; $i < $fieldCount; ++
$i) {
63 if ($_POST['field_name'][$i] === '') {
67 $definition = $this->getStatementPrefix($isCreateTable) . Table
::generateFieldSpec(
68 trim($_POST['field_name'][$i]),
69 $_POST['field_type'][$i],
70 $_POST['field_length'][$i],
71 $_POST['field_attribute'][$i],
72 $_POST['field_collation'][$i] ??
'',
73 $_POST['field_null'][$i] ??
'NO',
74 $_POST['field_default_type'][$i],
75 $_POST['field_default_value'][$i],
76 $_POST['field_extra'][$i] ??
'',
77 $_POST['field_comments'][$i] ??
'',
78 $_POST['field_virtuality'][$i] ??
'',
79 $_POST['field_expression'][$i] ??
'',
82 $definition .= $this->setColumnCreationStatementSuffix($previousField, $isCreateTable);
84 $definitions[] = $definition;
91 * Set column creation suffix according to requested position of the new column
93 * @param int $previousField previous field for ALTER statement
94 * @param bool $isCreateTable true if requirement is to get the statement
97 * @return string suffix
99 private function setColumnCreationStatementSuffix(
101 bool $isCreateTable = true,
103 // no suffix is needed if request is a table creation
104 if ($isCreateTable) {
108 if ((string) $_POST['field_where'] === 'last') {
112 // Only the first field can be added somewhere other than at the end
113 if ($previousField === -1) {
114 if ((string) $_POST['field_where'] === 'first') {
118 if (! empty($_POST['after_field'])) {
120 . Util
::backquote($_POST['after_field']);
126 return ' AFTER ' . Util
::backquote($_POST['field_name'][$previousField]);
130 * Create relevant index statements
132 * @param mixed[] $index an array of index columns
133 * @param string $indexChoice index choice that which represents
134 * the index type of $indexed_fields
135 * @param bool $isCreateTable true if requirement is to get the statement
138 * @return string sql statement for indexes
140 private function buildIndexStatement(
143 bool $isCreateTable = true,
149 $sqlQuery = $this->getStatementPrefix($isCreateTable) . $indexChoice;
151 if (! empty($index['Key_name']) && $index['Key_name'] !== 'PRIMARY') {
152 $sqlQuery .= ' ' . Util
::backquote($index['Key_name']);
156 foreach ($index['columns'] as $key => $column) {
157 $indexFields[$key] = Util
::backquote($_POST['field_name'][$column['col_index']]);
158 if (! $column['size']) {
162 $indexFields[$key] .= '(' . $column['size'] . ')';
165 $sqlQuery .= ' (' . implode(', ', $indexFields) . ')';
167 if ($index['Key_block_size']) {
168 $sqlQuery .= ' KEY_BLOCK_SIZE = '
169 . $this->dbi
->escapeString($index['Key_block_size']);
172 // specifying index type is allowed only for primary, unique and index only
174 $index['Index_choice'] !== 'SPATIAL'
175 && $index['Index_choice'] !== 'FULLTEXT'
176 && in_array($index['Index_type'], Index
::getIndexTypes())
178 $sqlQuery .= ' USING ' . $index['Index_type'];
181 if ($index['Index_choice'] === 'FULLTEXT' && $index['Parser']) {
182 $sqlQuery .= ' WITH PARSER ' . $this->dbi
->escapeString($index['Parser']);
185 if ($index['Index_comment']) {
186 $sqlQuery .= ' COMMENT ' . $this->dbi
->quoteString($index['Index_comment']);
193 * Statement prefix for the buildColumnCreationStatement()
195 * @param bool $isCreateTable true if requirement is to get the statement
198 * @return string prefix
200 private function getStatementPrefix(bool $isCreateTable = true): string
202 return $isCreateTable ?
'' : 'ADD ';
206 * Returns sql statement according to the column and index specifications as
209 * @param bool $isCreateTable true if requirement is to get the statement
212 * @return string sql statement
214 private function getColumnCreationStatements(bool $isCreateTable = true): string
224 ] = $this->getIndexedColumns();
225 $definitions = $this->buildColumnCreationStatement($fieldCount, $isCreateTable);
227 // Builds the PRIMARY KEY statements
228 if (isset($fieldPrimary[0])) {
229 $definitions[] = $this->buildIndexStatement($fieldPrimary[0], 'PRIMARY KEY', $isCreateTable);
232 // Builds the INDEX statements
233 foreach ($fieldIndex as $index) {
234 $definitions[] = $this->buildIndexStatement($index, 'INDEX', $isCreateTable);
237 // Builds the UNIQUE statements
238 foreach ($fieldUnique as $index) {
239 $definitions[] = $this->buildIndexStatement($index, 'UNIQUE', $isCreateTable);
242 // Builds the FULLTEXT statements
243 foreach ($fieldFullText as $index) {
244 $definitions[] = $this->buildIndexStatement($index, 'FULLTEXT', $isCreateTable);
247 // Builds the SPATIAL statements
248 foreach ($fieldSpatial as $index) {
249 $definitions[] = $this->buildIndexStatement($index, 'SPATIAL', $isCreateTable);
252 if ($definitions !== []) {
253 $sqlStatement = implode(', ', $definitions);
256 return preg_replace('@, $@', '', $sqlStatement) ??
'';
260 * Returns the partitioning clause
262 * @return string partitioning clause
264 public function getPartitionsDefinition(): string
268 ! empty($_POST['partition_by'])
269 && ! empty($_POST['partition_expr'])
270 && ! empty($_POST['partition_count'])
271 && $_POST['partition_count'] > 1
273 $sqlQuery .= ' PARTITION BY ' . $_POST['partition_by']
274 . ' (' . $_POST['partition_expr'] . ')'
275 . ' PARTITIONS ' . $_POST['partition_count'];
279 ! empty($_POST['subpartition_by'])
280 && ! empty($_POST['subpartition_expr'])
281 && ! empty($_POST['subpartition_count'])
282 && $_POST['subpartition_count'] > 1
284 $sqlQuery .= ' SUBPARTITION BY ' . $_POST['subpartition_by']
285 . ' (' . $_POST['subpartition_expr'] . ')'
286 . ' SUBPARTITIONS ' . $_POST['subpartition_count'];
289 if (! empty($_POST['partitions'])) {
291 foreach ($_POST['partitions'] as $partition) {
292 $partitions[] = $this->getPartitionDefinition($partition);
295 $sqlQuery .= ' (' . implode(', ', $partitions) . ')';
302 * Returns the definition of a partition/subpartition
304 * @param mixed[] $partition array of partition/subpartition details
305 * @param bool $isSubPartition whether a subpartition
307 * @return string partition/subpartition definition
309 private function getPartitionDefinition(
311 bool $isSubPartition = false,
313 $sqlQuery = ' ' . ($isSubPartition ?
'SUB' : '') . 'PARTITION ';
314 $sqlQuery .= $partition['name'];
316 if (! empty($partition['value_type'])) {
317 $sqlQuery .= ' VALUES ' . $partition['value_type'];
319 if ($partition['value_type'] !== 'LESS THAN MAXVALUE') {
320 $sqlQuery .= ' (' . $partition['value'] . ')';
324 if (! empty($partition['engine'])) {
325 $sqlQuery .= ' ENGINE = ' . $partition['engine'];
328 if (! empty($partition['comment'])) {
329 $sqlQuery .= " COMMENT = '" . $partition['comment'] . "'";
332 if (! empty($partition['data_directory'])) {
333 $sqlQuery .= " DATA DIRECTORY = '" . $partition['data_directory'] . "'";
336 if (! empty($partition['index_directory'])) {
337 $sqlQuery .= " INDEX_DIRECTORY = '" . $partition['index_directory'] . "'";
340 if (! empty($partition['max_rows'])) {
341 $sqlQuery .= ' MAX_ROWS = ' . $partition['max_rows'];
344 if (! empty($partition['min_rows'])) {
345 $sqlQuery .= ' MIN_ROWS = ' . $partition['min_rows'];
348 if (! empty($partition['tablespace'])) {
349 $sqlQuery .= ' TABLESPACE = ' . $partition['tablespace'];
352 if (! empty($partition['node_group'])) {
353 $sqlQuery .= ' NODEGROUP = ' . $partition['node_group'];
356 if (! empty($partition['subpartitions'])) {
358 foreach ($partition['subpartitions'] as $subpartition) {
359 $subpartitions[] = $this->getPartitionDefinition($subpartition, true);
362 $sqlQuery .= ' (' . implode(', ', $subpartitions) . ')';
369 * Function to get table creation sql query
371 * @param string $db database name
372 * @param string $table table name
374 public function getTableCreationQuery(string $db, string $table): string
376 // get column addition statements
377 $sqlStatement = $this->getColumnCreationStatements();
379 // Builds the 'create table' statement
380 $sqlQuery = 'CREATE TABLE ' . Util
::backquote($db) . '.'
381 . Util
::backquote(trim($table)) . ' (' . $sqlStatement . ')';
383 // Adds table type, character set, comments and partition definition
385 ! empty($_POST['tbl_storage_engine'])
386 && ($_POST['tbl_storage_engine'] !== 'Default')
387 && StorageEngine
::isValid($_POST['tbl_storage_engine'])
389 $sqlQuery .= ' ENGINE = ' . $_POST['tbl_storage_engine'];
392 if (! empty($_POST['tbl_collation'])) {
393 $sqlQuery .= Util
::getCharsetQueryPart($_POST['tbl_collation']);
397 ! empty($_POST['connection'])
398 && ! empty($_POST['tbl_storage_engine'])
399 && $_POST['tbl_storage_engine'] === 'FEDERATED'
401 $sqlQuery .= ' CONNECTION = ' . $this->dbi
->quoteString($_POST['connection']);
404 if (! empty($_POST['comment'])) {
405 $sqlQuery .= ' COMMENT = ' . $this->dbi
->quoteString($_POST['comment']);
408 $sqlQuery .= $this->getPartitionsDefinition();
415 * Function to get the column creation statement
417 * @param string $table current table
419 public function getColumnCreationQuery(
422 // get column addition statements
423 $sqlStatement = $this->getColumnCreationStatements(false);
425 $sqlQuery = 'ALTER TABLE ' . Util
::backquote($table) . ' ' . $sqlStatement;
426 if (isset($_POST['online_transaction'])) {
427 $sqlQuery .= ', ALGORITHM=INPLACE, LOCK=NONE';
430 return $sqlQuery . ';';
434 * Function to execute the column creation statement
436 * @param DatabaseName $db current database
437 * @param string $sqlQuery the query to run
438 * @param string $errorUrl error page url
440 public function tryColumnCreationQuery(
445 // To allow replication, we first select the db to use and then run queries
447 if (! $this->dbi
->selectDb($db)) {
449 $this->dbi
->getError(),
450 'USE ' . Util
::backquote($db->getName()),
456 return (bool) $this->dbi
->tryQuery($sqlQuery);