CreateAddField.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Holds the PhpMyAdmin\CreateAddField class
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin;
  10. /**
  11. * Set of functions for tbl_create.php and tbl_addfield.php
  12. *
  13. * @package PhpMyAdmin
  14. */
  15. class CreateAddField
  16. {
  17. /**
  18. * @var DatabaseInterface
  19. */
  20. private $dbi;
  21. /**
  22. * Constructor
  23. *
  24. * @param DatabaseInterface $dbi DatabaseInterface interface
  25. */
  26. public function __construct(DatabaseInterface $dbi)
  27. {
  28. $this->dbi = $dbi;
  29. }
  30. /**
  31. * Transforms the radio button field_key into 4 arrays
  32. *
  33. * @return array An array of arrays which represents column keys for each index type
  34. */
  35. private function getIndexedColumns(): array
  36. {
  37. $fieldCount = count($_POST['field_name']);
  38. $fieldPrimary = json_decode($_POST['primary_indexes'], true);
  39. $fieldIndex = json_decode($_POST['indexes'], true);
  40. $fieldUnique = json_decode($_POST['unique_indexes'], true);
  41. $fieldFullText = json_decode($_POST['fulltext_indexes'], true);
  42. $fieldSpatial = json_decode($_POST['spatial_indexes'], true);
  43. return [
  44. $fieldCount,
  45. $fieldPrimary,
  46. $fieldIndex,
  47. $fieldUnique,
  48. $fieldFullText,
  49. $fieldSpatial,
  50. ];
  51. }
  52. /**
  53. * Initiate the column creation statement according to the table creation or
  54. * add columns to a existing table
  55. *
  56. * @param int $fieldCount number of columns
  57. * @param boolean $isCreateTable true if requirement is to get the statement
  58. * for table creation
  59. *
  60. * @return array An array of initial sql statements
  61. * according to the request
  62. */
  63. private function buildColumnCreationStatement(
  64. int $fieldCount,
  65. bool $isCreateTable = true
  66. ): array {
  67. $definitions = [];
  68. $previousField = -1;
  69. for ($i = 0; $i < $fieldCount; ++$i) {
  70. // '0' is also empty for php :-(
  71. if (strlen($_POST['field_name'][$i]) === 0) {
  72. continue;
  73. }
  74. $definition = $this->getStatementPrefix($isCreateTable) .
  75. Table::generateFieldSpec(
  76. trim($_POST['field_name'][$i]),
  77. $_POST['field_type'][$i],
  78. $_POST['field_length'][$i],
  79. $_POST['field_attribute'][$i],
  80. isset($_POST['field_collation'][$i])
  81. ? $_POST['field_collation'][$i]
  82. : '',
  83. isset($_POST['field_null'][$i])
  84. ? $_POST['field_null'][$i]
  85. : 'NO',
  86. $_POST['field_default_type'][$i],
  87. $_POST['field_default_value'][$i],
  88. isset($_POST['field_extra'][$i])
  89. ? $_POST['field_extra'][$i]
  90. : false,
  91. isset($_POST['field_comments'][$i])
  92. ? $_POST['field_comments'][$i]
  93. : '',
  94. isset($_POST['field_virtuality'][$i])
  95. ? $_POST['field_virtuality'][$i]
  96. : '',
  97. isset($_POST['field_expression'][$i])
  98. ? $_POST['field_expression'][$i]
  99. : ''
  100. );
  101. $definition .= $this->setColumnCreationStatementSuffix(
  102. $previousField,
  103. $isCreateTable
  104. );
  105. $previousField = $i;
  106. $definitions[] = $definition;
  107. } // end for
  108. return $definitions;
  109. }
  110. /**
  111. * Set column creation suffix according to requested position of the new column
  112. *
  113. * @param int $previousField previous field for ALTER statement
  114. * @param bool $isCreateTable true if requirement is to get the statement
  115. * for table creation
  116. *
  117. * @return string suffix
  118. */
  119. private function setColumnCreationStatementSuffix(
  120. int $previousField,
  121. bool $isCreateTable = true
  122. ): string {
  123. // no suffix is needed if request is a table creation
  124. $sqlSuffix = ' ';
  125. if ($isCreateTable) {
  126. return $sqlSuffix;
  127. }
  128. if ((string) $_POST['field_where'] === 'last') {
  129. return $sqlSuffix;
  130. }
  131. // Only the first field can be added somewhere other than at the end
  132. if ($previousField == -1) {
  133. if ((string) $_POST['field_where'] === 'first') {
  134. $sqlSuffix .= ' FIRST';
  135. } elseif (! empty($_POST['after_field'])) {
  136. $sqlSuffix .= ' AFTER '
  137. . Util::backquote($_POST['after_field']);
  138. }
  139. } else {
  140. $sqlSuffix .= ' AFTER '
  141. . Util::backquote(
  142. $_POST['field_name'][$previousField]
  143. );
  144. }
  145. return $sqlSuffix;
  146. }
  147. /**
  148. * Create relevant index statements
  149. *
  150. * @param array $index an array of index columns
  151. * @param string $indexChoice index choice that which represents
  152. * the index type of $indexed_fields
  153. * @param boolean $isCreateTable true if requirement is to get the statement
  154. * for table creation
  155. *
  156. * @return array an array of sql statements for indexes
  157. */
  158. private function buildIndexStatements(
  159. array $index,
  160. string $indexChoice,
  161. bool $isCreateTable = true
  162. ): array {
  163. $statement = [];
  164. if (! count($index)) {
  165. return $statement;
  166. }
  167. $sqlQuery = $this->getStatementPrefix($isCreateTable)
  168. . ' ' . $indexChoice;
  169. if (! empty($index['Key_name']) && $index['Key_name'] != 'PRIMARY') {
  170. $sqlQuery .= ' ' . Util::backquote($index['Key_name']);
  171. }
  172. $indexFields = [];
  173. foreach ($index['columns'] as $key => $column) {
  174. $indexFields[$key] = Util::backquote(
  175. $_POST['field_name'][$column['col_index']]
  176. );
  177. if ($column['size']) {
  178. $indexFields[$key] .= '(' . $column['size'] . ')';
  179. }
  180. }
  181. $sqlQuery .= ' (' . implode(', ', $indexFields) . ')';
  182. $keyBlockSizes = $index['Key_block_size'];
  183. if (! empty($keyBlockSizes)) {
  184. $sqlQuery .= " KEY_BLOCK_SIZE = "
  185. . $this->dbi->escapeString($keyBlockSizes);
  186. }
  187. // specifying index type is allowed only for primary, unique and index only
  188. $type = $index['Index_type'];
  189. if ($index['Index_choice'] != 'SPATIAL'
  190. && $index['Index_choice'] != 'FULLTEXT'
  191. && in_array($type, Index::getIndexTypes())
  192. ) {
  193. $sqlQuery .= ' USING ' . $type;
  194. }
  195. $parser = $index['Parser'];
  196. if ($index['Index_choice'] == 'FULLTEXT' && ! empty($parser)) {
  197. $sqlQuery .= " WITH PARSER " . $this->dbi->escapeString($parser);
  198. }
  199. $comment = $index['Index_comment'];
  200. if (! empty($comment)) {
  201. $sqlQuery .= " COMMENT '" . $this->dbi->escapeString($comment)
  202. . "'";
  203. }
  204. $statement[] = $sqlQuery;
  205. return $statement;
  206. }
  207. /**
  208. * Statement prefix for the buildColumnCreationStatement()
  209. *
  210. * @param boolean $isCreateTable true if requirement is to get the statement
  211. * for table creation
  212. *
  213. * @return string prefix
  214. */
  215. private function getStatementPrefix(bool $isCreateTable = true): string
  216. {
  217. $sqlPrefix = " ";
  218. if (! $isCreateTable) {
  219. $sqlPrefix = ' ADD ';
  220. }
  221. return $sqlPrefix;
  222. }
  223. /**
  224. * Merge index definitions for one type of index
  225. *
  226. * @param array $definitions the index definitions to merge to
  227. * @param boolean $isCreateTable true if requirement is to get the statement
  228. * for table creation
  229. * @param array $indexedColumns the columns for one type of index
  230. * @param string $indexKeyword the index keyword to use in the definition
  231. *
  232. * @return array
  233. */
  234. private function mergeIndexStatements(
  235. array $definitions,
  236. bool $isCreateTable,
  237. array $indexedColumns,
  238. string $indexKeyword
  239. ): array {
  240. foreach ($indexedColumns as $index) {
  241. $statements = $this->buildIndexStatements(
  242. $index,
  243. " " . $indexKeyword . " ",
  244. $isCreateTable
  245. );
  246. $definitions = array_merge($definitions, $statements);
  247. }
  248. return $definitions;
  249. }
  250. /**
  251. * Returns sql statement according to the column and index specifications as
  252. * requested
  253. *
  254. * @param boolean $isCreateTable true if requirement is to get the statement
  255. * for table creation
  256. *
  257. * @return string sql statement
  258. */
  259. private function getColumnCreationStatements(bool $isCreateTable = true): string
  260. {
  261. $sqlStatement = "";
  262. list(
  263. $fieldCount,
  264. $fieldPrimary,
  265. $fieldIndex,
  266. $fieldUnique,
  267. $fieldFullText,
  268. $fieldSpatial
  269. ) = $this->getIndexedColumns();
  270. $definitions = $this->buildColumnCreationStatement(
  271. $fieldCount,
  272. $isCreateTable
  273. );
  274. // Builds the PRIMARY KEY statements
  275. $primaryKeyStatements = $this->buildIndexStatements(
  276. isset($fieldPrimary[0]) ? $fieldPrimary[0] : [],
  277. " PRIMARY KEY ",
  278. $isCreateTable
  279. );
  280. $definitions = array_merge($definitions, $primaryKeyStatements);
  281. // Builds the INDEX statements
  282. $definitions = $this->mergeIndexStatements(
  283. $definitions,
  284. $isCreateTable,
  285. $fieldIndex,
  286. "INDEX"
  287. );
  288. // Builds the UNIQUE statements
  289. $definitions = $this->mergeIndexStatements(
  290. $definitions,
  291. $isCreateTable,
  292. $fieldUnique,
  293. "UNIQUE"
  294. );
  295. // Builds the FULLTEXT statements
  296. $definitions = $this->mergeIndexStatements(
  297. $definitions,
  298. $isCreateTable,
  299. $fieldFullText,
  300. "FULLTEXT"
  301. );
  302. // Builds the SPATIAL statements
  303. $definitions = $this->mergeIndexStatements(
  304. $definitions,
  305. $isCreateTable,
  306. $fieldSpatial,
  307. "SPATIAL"
  308. );
  309. if (count($definitions)) {
  310. $sqlStatement = implode(', ', $definitions);
  311. }
  312. return preg_replace('@, $@', '', $sqlStatement);
  313. }
  314. /**
  315. * Returns the partitioning clause
  316. *
  317. * @return string partitioning clause
  318. */
  319. public function getPartitionsDefinition(): string
  320. {
  321. $sqlQuery = "";
  322. if (! empty($_POST['partition_by'])
  323. && ! empty($_POST['partition_expr'])
  324. && ! empty($_POST['partition_count'])
  325. && $_POST['partition_count'] > 1
  326. ) {
  327. $sqlQuery .= " PARTITION BY " . $_POST['partition_by']
  328. . " (" . $_POST['partition_expr'] . ")"
  329. . " PARTITIONS " . $_POST['partition_count'];
  330. }
  331. if (! empty($_POST['subpartition_by'])
  332. && ! empty($_POST['subpartition_expr'])
  333. && ! empty($_POST['subpartition_count'])
  334. && $_POST['subpartition_count'] > 1
  335. ) {
  336. $sqlQuery .= " SUBPARTITION BY " . $_POST['subpartition_by']
  337. . " (" . $_POST['subpartition_expr'] . ")"
  338. . " SUBPARTITIONS " . $_POST['subpartition_count'];
  339. }
  340. if (! empty($_POST['partitions'])) {
  341. $partitions = [];
  342. foreach ($_POST['partitions'] as $partition) {
  343. $partitions[] = $this->getPartitionDefinition($partition);
  344. }
  345. $sqlQuery .= " (" . implode(", ", $partitions) . ")";
  346. }
  347. return $sqlQuery;
  348. }
  349. /**
  350. * Returns the definition of a partition/subpartition
  351. *
  352. * @param array $partition array of partition/subpartition detiails
  353. * @param boolean $isSubPartition whether a subpartition
  354. *
  355. * @return string partition/subpartition definition
  356. */
  357. private function getPartitionDefinition(
  358. array $partition,
  359. bool $isSubPartition = false
  360. ): string {
  361. $sqlQuery = " " . ($isSubPartition ? "SUB" : "") . "PARTITION ";
  362. $sqlQuery .= $partition['name'];
  363. if (! empty($partition['value_type'])) {
  364. $sqlQuery .= " VALUES " . $partition['value_type'];
  365. if ($partition['value_type'] != 'LESS THAN MAXVALUE') {
  366. $sqlQuery .= " (" . $partition['value'] . ")";
  367. }
  368. }
  369. if (! empty($partition['engine'])) {
  370. $sqlQuery .= " ENGINE = " . $partition['engine'];
  371. }
  372. if (! empty($partition['comment'])) {
  373. $sqlQuery .= " COMMENT = '" . $partition['comment'] . "'";
  374. }
  375. if (! empty($partition['data_directory'])) {
  376. $sqlQuery .= " DATA DIRECTORY = '" . $partition['data_directory'] . "'";
  377. }
  378. if (! empty($partition['index_directory'])) {
  379. $sqlQuery .= " INDEX_DIRECTORY = '" . $partition['index_directory'] . "'";
  380. }
  381. if (! empty($partition['max_rows'])) {
  382. $sqlQuery .= " MAX_ROWS = " . $partition['max_rows'];
  383. }
  384. if (! empty($partition['min_rows'])) {
  385. $sqlQuery .= " MIN_ROWS = " . $partition['min_rows'];
  386. }
  387. if (! empty($partition['tablespace'])) {
  388. $sqlQuery .= " TABLESPACE = " . $partition['tablespace'];
  389. }
  390. if (! empty($partition['node_group'])) {
  391. $sqlQuery .= " NODEGROUP = " . $partition['node_group'];
  392. }
  393. if (! empty($partition['subpartitions'])) {
  394. $subpartitions = [];
  395. foreach ($partition['subpartitions'] as $subpartition) {
  396. $subpartitions[] = $this->getPartitionDefinition(
  397. $subpartition,
  398. true
  399. );
  400. }
  401. $sqlQuery .= " (" . implode(", ", $subpartitions) . ")";
  402. }
  403. return $sqlQuery;
  404. }
  405. /**
  406. * Function to get table creation sql query
  407. *
  408. * @param string $db database name
  409. * @param string $table table name
  410. *
  411. * @return string
  412. */
  413. public function getTableCreationQuery(string $db, string $table): string
  414. {
  415. // get column addition statements
  416. $sqlStatement = $this->getColumnCreationStatements(true);
  417. // Builds the 'create table' statement
  418. $sqlQuery = 'CREATE TABLE ' . Util::backquote($db) . '.'
  419. . Util::backquote(trim($table)) . ' (' . $sqlStatement . ')';
  420. // Adds table type, character set, comments and partition definition
  421. if (! empty($_POST['tbl_storage_engine'])
  422. && ($_POST['tbl_storage_engine'] != 'Default')
  423. ) {
  424. $sqlQuery .= ' ENGINE = ' . $this->dbi->escapeString($_POST['tbl_storage_engine']);
  425. }
  426. if (! empty($_POST['tbl_collation'])) {
  427. $sqlQuery .= Util::getCharsetQueryPart($_POST['tbl_collation']);
  428. }
  429. if (! empty($_POST['connection'])
  430. && ! empty($_POST['tbl_storage_engine'])
  431. && $_POST['tbl_storage_engine'] == 'FEDERATED'
  432. ) {
  433. $sqlQuery .= " CONNECTION = '"
  434. . $this->dbi->escapeString($_POST['connection']) . "'";
  435. }
  436. if (! empty($_POST['comment'])) {
  437. $sqlQuery .= ' COMMENT = \''
  438. . $this->dbi->escapeString($_POST['comment']) . '\'';
  439. }
  440. $sqlQuery .= $this->getPartitionsDefinition();
  441. $sqlQuery .= ';';
  442. return $sqlQuery;
  443. }
  444. /**
  445. * Function to get the number of fields for the table creation form
  446. *
  447. * @return int
  448. */
  449. public function getNumberOfFieldsFromRequest(): int
  450. {
  451. // Limit to 4096 fields (MySQL maximal value)
  452. $mysqlLimit = 4096;
  453. if (isset($_POST['submit_num_fields'])) { // adding new fields
  454. $numberOfFields = intval($_POST['orig_num_fields']) + intval($_POST['added_fields']);
  455. } elseif (isset($_POST['orig_num_fields'])) { // retaining existing fields
  456. $numberOfFields = intval($_POST['orig_num_fields']);
  457. } elseif (isset($_POST['num_fields'])
  458. && intval($_POST['num_fields']) > 0
  459. ) { // new table with specified number of fields
  460. $numberOfFields = intval($_POST['num_fields']);
  461. } else { // new table with unspecified number of fields
  462. $numberOfFields = 4;
  463. }
  464. return min($numberOfFields, $mysqlLimit);
  465. }
  466. /**
  467. * Function to execute the column creation statement
  468. *
  469. * @param string $db current database
  470. * @param string $table current table
  471. * @param string $errorUrl error page url
  472. *
  473. * @return array
  474. */
  475. public function tryColumnCreationQuery(
  476. string $db,
  477. string $table,
  478. string $errorUrl
  479. ): array {
  480. // get column addition statements
  481. $sqlStatement = $this->getColumnCreationStatements(false);
  482. // To allow replication, we first select the db to use and then run queries
  483. // on this db.
  484. if (! $this->dbi->selectDb($db)) {
  485. Util::mysqlDie(
  486. $this->dbi->getError(),
  487. 'USE ' . Util::backquote($db),
  488. false,
  489. $errorUrl
  490. );
  491. }
  492. $sqlQuery = 'ALTER TABLE ' .
  493. Util::backquote($table) . ' ' . $sqlStatement . ';';
  494. // If there is a request for SQL previewing.
  495. if (isset($_POST['preview_sql'])) {
  496. Core::previewSQL($sqlQuery);
  497. }
  498. return [
  499. $this->dbi->tryQuery($sqlQuery),
  500. $sqlQuery,
  501. ];
  502. }
  503. }