CreateAddField.php 18 KB

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