Translated using Weblate (Slovenian)
[phpmyadmin.git] / src / CreateAddField.php
blobc3bef39fa5e1f3c5d366d9ae59aa6adf5964202d
1 <?php
3 declare(strict_types=1);
5 namespace PhpMyAdmin;
7 use PhpMyAdmin\Html\Generator;
8 use PhpMyAdmin\Identifiers\DatabaseName;
9 use PhpMyAdmin\Table\Table;
11 use function count;
12 use function implode;
13 use function in_array;
14 use function json_decode;
15 use function preg_replace;
16 use function trim;
18 /**
19 * Set of functions for /table/create and /table/add-field
21 class CreateAddField
23 public function __construct(private DatabaseInterface $dbi)
27 /**
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];
45 /**
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
51 * for table creation
53 * @return mixed[] An array of initial sql statements
54 * according to the request
56 private function buildColumnCreationStatement(
57 int $fieldCount,
58 bool $isCreateTable = true,
59 ): array {
60 $definitions = [];
61 $previousField = -1;
62 for ($i = 0; $i < $fieldCount; ++$i) {
63 if ($_POST['field_name'][$i] === '') {
64 continue;
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);
83 $previousField = $i;
84 $definitions[] = $definition;
87 return $definitions;
90 /**
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
95 * for table creation
97 * @return string suffix
99 private function setColumnCreationStatementSuffix(
100 int $previousField,
101 bool $isCreateTable = true,
102 ): string {
103 // no suffix is needed if request is a table creation
104 if ($isCreateTable) {
105 return ' ';
108 if ((string) $_POST['field_where'] === 'last') {
109 return ' ';
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') {
115 return ' FIRST';
118 if (! empty($_POST['after_field'])) {
119 return ' AFTER '
120 . Util::backquote($_POST['after_field']);
123 return ' ';
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
136 * for table creation
138 * @return string sql statement for indexes
140 private function buildIndexStatement(
141 array $index,
142 string $indexChoice,
143 bool $isCreateTable = true,
144 ): string {
145 if ($index === []) {
146 return '';
149 $sqlQuery = $this->getStatementPrefix($isCreateTable) . $indexChoice;
151 if (! empty($index['Key_name']) && $index['Key_name'] !== 'PRIMARY') {
152 $sqlQuery .= ' ' . Util::backquote($index['Key_name']);
155 $indexFields = [];
156 foreach ($index['columns'] as $key => $column) {
157 $indexFields[$key] = Util::backquote($_POST['field_name'][$column['col_index']]);
158 if (! $column['size']) {
159 continue;
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
173 if (
174 $index['Index_choice'] !== 'SPATIAL'
175 && $index['Index_choice'] !== 'FULLTEXT'
176 && in_array($index['Index_type'], Index::getIndexTypes(), true)
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']);
189 return $sqlQuery;
193 * Statement prefix for the buildColumnCreationStatement()
195 * @param bool $isCreateTable true if requirement is to get the statement
196 * for table creation
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
207 * requested
209 * @param bool $isCreateTable true if requirement is to get the statement
210 * for table creation
212 * @return string sql statement
214 private function getColumnCreationStatements(bool $isCreateTable = true): string
216 $sqlStatement = '';
218 $fieldCount,
219 $fieldPrimary,
220 $fieldIndex,
221 $fieldUnique,
222 $fieldFullText,
223 $fieldSpatial,
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
266 $sqlQuery = '';
267 if (
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'];
278 if (
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'])) {
290 $partitions = [];
291 foreach ($_POST['partitions'] as $partition) {
292 $partitions[] = $this->getPartitionDefinition($partition);
295 $sqlQuery .= ' (' . implode(', ', $partitions) . ')';
298 return $sqlQuery;
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(
310 array $partition,
311 bool $isSubPartition = false,
312 ): string {
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'])) {
357 $subpartitions = [];
358 foreach ($partition['subpartitions'] as $subpartition) {
359 $subpartitions[] = $this->getPartitionDefinition($subpartition, true);
362 $sqlQuery .= ' (' . implode(', ', $subpartitions) . ')';
365 return $sqlQuery;
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
384 if (
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']);
396 if (
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();
409 $sqlQuery .= ';';
411 return $sqlQuery;
415 * Function to get the column creation statement
417 * @param string $table current table
419 public function getColumnCreationQuery(
420 string $table,
421 ): string {
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(
441 DatabaseName $db,
442 string $sqlQuery,
443 string $errorUrl,
444 ): bool {
445 // To allow replication, we first select the db to use and then run queries
446 // on this db.
447 if (! $this->dbi->selectDb($db)) {
448 Generator::mysqlDie(
449 $this->dbi->getError(),
450 'USE ' . Util::backquote($db->getName()),
451 false,
452 $errorUrl,
456 return (bool) $this->dbi->tryQuery($sqlQuery);