Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / CreateAddField.php
blobd6e4dabcc9013a771a60638ed5f2f3f60f69b894
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 string[] An array of initial sql statements according to the request
55 private function buildColumnCreationStatement(
56 int $fieldCount,
57 bool $isCreateTable = true,
58 ): array {
59 $definitions = [];
60 $previousField = -1;
61 for ($i = 0; $i < $fieldCount; ++$i) {
62 if ($_POST['field_name'][$i] === '') {
63 continue;
66 $definition = $this->getStatementPrefix($isCreateTable) . Table::generateFieldSpec(
67 trim($_POST['field_name'][$i]),
68 $_POST['field_type'][$i],
69 $_POST['field_length'][$i],
70 $_POST['field_attribute'][$i],
71 $_POST['field_collation'][$i] ?? '',
72 $_POST['field_null'][$i] ?? 'NO',
73 $_POST['field_default_type'][$i],
74 $_POST['field_default_value'][$i],
75 $_POST['field_extra'][$i] ?? '',
76 $_POST['field_comments'][$i] ?? '',
77 $_POST['field_virtuality'][$i] ?? '',
78 $_POST['field_expression'][$i] ?? '',
81 $definition .= $this->setColumnCreationStatementSuffix($previousField, $isCreateTable);
82 $previousField = $i;
83 $definitions[] = $definition;
86 return $definitions;
89 /**
90 * Set column creation suffix according to requested position of the new column
92 * @param int $previousField previous field for ALTER statement
93 * @param bool $isCreateTable true if requirement is to get the statement
94 * for table creation
96 * @return string suffix
98 private function setColumnCreationStatementSuffix(
99 int $previousField,
100 bool $isCreateTable = true,
101 ): string {
102 // no suffix is needed if request is a table creation
103 if ($isCreateTable) {
104 return ' ';
107 if ((string) $_POST['field_where'] === 'last') {
108 return ' ';
111 // Only the first field can be added somewhere other than at the end
112 if ($previousField === -1) {
113 if ((string) $_POST['field_where'] === 'first') {
114 return ' FIRST';
117 if (! empty($_POST['after_field'])) {
118 return ' AFTER '
119 . Util::backquote($_POST['after_field']);
122 return ' ';
125 return ' AFTER ' . Util::backquote($_POST['field_name'][$previousField]);
129 * Create relevant index statements
131 * @param mixed[] $index an array of index columns
132 * @param string $indexChoice index choice that which represents
133 * the index type of $indexed_fields
134 * @param bool $isCreateTable true if requirement is to get the statement
135 * for table creation
137 * @return string sql statement for indexes
139 private function buildIndexStatement(
140 array $index,
141 string $indexChoice,
142 bool $isCreateTable = true,
143 ): string {
144 if ($index === []) {
145 return '';
148 $sqlQuery = $this->getStatementPrefix($isCreateTable) . $indexChoice;
150 if (! empty($index['Key_name']) && $index['Key_name'] !== 'PRIMARY') {
151 $sqlQuery .= ' ' . Util::backquote($index['Key_name']);
154 $indexFields = [];
155 foreach ($index['columns'] as $key => $column) {
156 $indexFields[$key] = Util::backquote($_POST['field_name'][$column['col_index']]);
157 if (! $column['size']) {
158 continue;
161 $indexFields[$key] .= '(' . $column['size'] . ')';
164 $sqlQuery .= ' (' . implode(', ', $indexFields) . ')';
166 if ($index['Key_block_size']) {
167 $sqlQuery .= ' KEY_BLOCK_SIZE = ' . (int) $index['Key_block_size'];
170 // specifying index type is allowed only for primary, unique and index only
171 if (
172 $index['Index_choice'] !== 'SPATIAL'
173 && $index['Index_choice'] !== 'FULLTEXT'
174 && in_array($index['Index_type'], Index::getIndexTypes(), true)
176 $sqlQuery .= ' USING ' . $index['Index_type'];
179 if ($index['Index_choice'] === 'FULLTEXT' && $index['Parser']) {
180 $sqlQuery .= ' WITH PARSER ' . $index['Parser'];
183 if ($index['Index_comment']) {
184 $sqlQuery .= ' COMMENT ' . $this->dbi->quoteString($index['Index_comment']);
187 return $sqlQuery;
191 * Statement prefix for the buildColumnCreationStatement()
193 * @param bool $isCreateTable true if requirement is to get the statement
194 * for table creation
196 * @return string prefix
198 private function getStatementPrefix(bool $isCreateTable = true): string
200 return $isCreateTable ? '' : 'ADD ';
204 * Returns sql statement according to the column and index specifications as
205 * requested
207 * @param bool $isCreateTable true if requirement is to get the statement
208 * for table creation
210 * @return string sql statement
212 private function getColumnCreationStatements(bool $isCreateTable = true): string
214 $sqlStatement = '';
216 $fieldCount,
217 $fieldPrimary,
218 $fieldIndex,
219 $fieldUnique,
220 $fieldFullText,
221 $fieldSpatial,
222 ] = $this->getIndexedColumns();
223 $definitions = $this->buildColumnCreationStatement($fieldCount, $isCreateTable);
225 // Builds the PRIMARY KEY statements
226 if (isset($fieldPrimary[0])) {
227 $definitions[] = $this->buildIndexStatement($fieldPrimary[0], 'PRIMARY KEY', $isCreateTable);
230 // Builds the INDEX statements
231 foreach ($fieldIndex as $index) {
232 $definitions[] = $this->buildIndexStatement($index, 'INDEX', $isCreateTable);
235 // Builds the UNIQUE statements
236 foreach ($fieldUnique as $index) {
237 $definitions[] = $this->buildIndexStatement($index, 'UNIQUE', $isCreateTable);
240 // Builds the FULLTEXT statements
241 foreach ($fieldFullText as $index) {
242 $definitions[] = $this->buildIndexStatement($index, 'FULLTEXT', $isCreateTable);
245 // Builds the SPATIAL statements
246 foreach ($fieldSpatial as $index) {
247 $definitions[] = $this->buildIndexStatement($index, 'SPATIAL', $isCreateTable);
250 if ($definitions !== []) {
251 $sqlStatement = implode(', ', $definitions);
254 return preg_replace('@, $@', '', $sqlStatement) ?? '';
258 * Returns the partitioning clause
260 * @return string partitioning clause
262 public function getPartitionsDefinition(): string
264 $sqlQuery = '';
265 if (
266 ! empty($_POST['partition_by'])
267 && ! empty($_POST['partition_expr'])
268 && ! empty($_POST['partition_count'])
269 && $_POST['partition_count'] > 1
271 $sqlQuery .= ' PARTITION BY ' . $_POST['partition_by']
272 . ' (' . $_POST['partition_expr'] . ')'
273 . ' PARTITIONS ' . $_POST['partition_count'];
276 if (
277 ! empty($_POST['subpartition_by'])
278 && ! empty($_POST['subpartition_expr'])
279 && ! empty($_POST['subpartition_count'])
280 && $_POST['subpartition_count'] > 1
282 $sqlQuery .= ' SUBPARTITION BY ' . $_POST['subpartition_by']
283 . ' (' . $_POST['subpartition_expr'] . ')'
284 . ' SUBPARTITIONS ' . $_POST['subpartition_count'];
287 if (! empty($_POST['partitions'])) {
288 $partitions = [];
289 foreach ($_POST['partitions'] as $partition) {
290 $partitions[] = $this->getPartitionDefinition($partition);
293 $sqlQuery .= ' (' . implode(', ', $partitions) . ')';
296 return $sqlQuery;
300 * Returns the definition of a partition/subpartition
302 * @param mixed[] $partition array of partition/subpartition details
303 * @param bool $isSubPartition whether a subpartition
305 * @return string partition/subpartition definition
307 private function getPartitionDefinition(
308 array $partition,
309 bool $isSubPartition = false,
310 ): string {
311 $sqlQuery = ' ' . ($isSubPartition ? 'SUB' : '') . 'PARTITION ';
312 $sqlQuery .= $partition['name'];
314 if (! empty($partition['value_type'])) {
315 $sqlQuery .= ' VALUES ' . $partition['value_type'];
317 if ($partition['value_type'] !== 'LESS THAN MAXVALUE') {
318 $sqlQuery .= ' (' . $partition['value'] . ')';
322 if (! empty($partition['engine'])) {
323 $sqlQuery .= ' ENGINE = ' . $partition['engine'];
326 if (! empty($partition['comment'])) {
327 $sqlQuery .= " COMMENT = '" . $partition['comment'] . "'";
330 if (! empty($partition['data_directory'])) {
331 $sqlQuery .= " DATA DIRECTORY = '" . $partition['data_directory'] . "'";
334 if (! empty($partition['index_directory'])) {
335 $sqlQuery .= " INDEX_DIRECTORY = '" . $partition['index_directory'] . "'";
338 if (! empty($partition['max_rows'])) {
339 $sqlQuery .= ' MAX_ROWS = ' . $partition['max_rows'];
342 if (! empty($partition['min_rows'])) {
343 $sqlQuery .= ' MIN_ROWS = ' . $partition['min_rows'];
346 if (! empty($partition['tablespace'])) {
347 $sqlQuery .= ' TABLESPACE = ' . $partition['tablespace'];
350 if (! empty($partition['node_group'])) {
351 $sqlQuery .= ' NODEGROUP = ' . $partition['node_group'];
354 if (! empty($partition['subpartitions'])) {
355 $subpartitions = [];
356 foreach ($partition['subpartitions'] as $subpartition) {
357 $subpartitions[] = $this->getPartitionDefinition($subpartition, true);
360 $sqlQuery .= ' (' . implode(', ', $subpartitions) . ')';
363 return $sqlQuery;
367 * Function to get table creation sql query
369 * @param string $db database name
370 * @param string $table table name
372 public function getTableCreationQuery(string $db, string $table): string
374 // get column addition statements
375 $sqlStatement = $this->getColumnCreationStatements();
377 // Builds the 'create table' statement
378 $sqlQuery = 'CREATE TABLE ' . Util::backquote($db) . '.'
379 . Util::backquote(trim($table)) . ' (' . $sqlStatement . ')';
381 // Adds table type, character set, comments and partition definition
382 if (
383 ! empty($_POST['tbl_storage_engine'])
384 && $_POST['tbl_storage_engine'] !== 'Default'
385 && StorageEngine::isValid($_POST['tbl_storage_engine'])
387 $sqlQuery .= ' ENGINE = ' . $_POST['tbl_storage_engine'];
390 if (! empty($_POST['tbl_collation'])) {
391 $sqlQuery .= Util::getCharsetQueryPart($_POST['tbl_collation']);
394 if (
395 ! empty($_POST['connection'])
396 && ! empty($_POST['tbl_storage_engine'])
397 && $_POST['tbl_storage_engine'] === 'FEDERATED'
399 $sqlQuery .= ' CONNECTION = ' . $this->dbi->quoteString($_POST['connection']);
402 if (! empty($_POST['comment'])) {
403 $sqlQuery .= ' COMMENT = ' . $this->dbi->quoteString($_POST['comment']);
406 $sqlQuery .= $this->getPartitionsDefinition();
407 $sqlQuery .= ';';
409 return $sqlQuery;
413 * Function to get the column creation statement
415 * @param string $table current table
417 public function getColumnCreationQuery(
418 string $table,
419 ): string {
420 // get column addition statements
421 $sqlStatement = $this->getColumnCreationStatements(false);
423 $sqlQuery = 'ALTER TABLE ' . Util::backquote($table) . ' ' . $sqlStatement;
424 if (isset($_POST['online_transaction'])) {
425 $sqlQuery .= ', ALGORITHM=INPLACE, LOCK=NONE';
428 return $sqlQuery . ';';
432 * Function to execute the column creation statement
434 * @param DatabaseName $db current database
435 * @param string $sqlQuery the query to run
436 * @param string $errorUrl error page url
438 public function tryColumnCreationQuery(
439 DatabaseName $db,
440 string $sqlQuery,
441 string $errorUrl,
442 ): bool {
443 // To allow replication, we first select the db to use and then run queries
444 // on this db.
445 if (! $this->dbi->selectDb($db)) {
446 Generator::mysqlDie(
447 $this->dbi->getError(),
448 'USE ' . Util::backquote($db->getName()),
449 false,
450 $errorUrl,
454 return (bool) $this->dbi->tryQuery($sqlQuery);