normalization.lib.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * set of functions used for normalization
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. if (! defined('PHPMYADMIN')) {
  9. exit;
  10. }
  11. require_once 'libraries/Template.class.php';
  12. /**
  13. * build the html for columns of $colTypeCategory category
  14. * in form of given $listType in a table
  15. *
  16. * @param string $db current database
  17. * @param string $table current table
  18. * @param string $colTypeCategory supported all|Numeric|String|Spatial
  19. * |Date and time using the _pgettext() format
  20. * @param string $listType type of list to build, supported dropdown|checkbox
  21. *
  22. * @return string HTML for list of columns in form of given list types
  23. */
  24. function PMA_getHtmlForColumnsList(
  25. $db, $table, $colTypeCategory='all', $listType='dropdown'
  26. ) {
  27. $columnTypeList = array();
  28. if ($colTypeCategory != 'all') {
  29. $types = $GLOBALS['PMA_Types']->getColumns();
  30. $columnTypeList = $types[$colTypeCategory];
  31. }
  32. $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']);
  33. $columns = $GLOBALS['dbi']->getColumns(
  34. $db, $table, null,
  35. true, $GLOBALS['userlink']
  36. );
  37. $type = "";
  38. $selectColHtml = "";
  39. foreach ($columns as $column => $def) {
  40. if (isset($def['Type'])) {
  41. $extracted_columnspec = PMA_Util::extractColumnSpec($def['Type']);
  42. $type = $extracted_columnspec['type'];
  43. }
  44. if (empty($columnTypeList)
  45. || in_array(/*overload*/mb_strtoupper($type), $columnTypeList)
  46. ) {
  47. if ($listType == 'checkbox') {
  48. $selectColHtml .= '<input type="checkbox" value="'
  49. . htmlspecialchars($column) . '"/>'
  50. . htmlspecialchars($column) . ' [ '
  51. . htmlspecialchars($def['Type']) . ' ]</br>';
  52. } else {
  53. $selectColHtml .= '<option value="' . htmlspecialchars($column) . ''
  54. . '">' . htmlspecialchars($column)
  55. . ' [ ' . htmlspecialchars($def['Type']) . ' ]'
  56. . '</option>';
  57. }
  58. }
  59. }
  60. return $selectColHtml;
  61. }
  62. /**
  63. * get the html of the form to add the new column to given table
  64. *
  65. * @param integer $num_fields number of columns to add
  66. * @param string $db current database
  67. * @param string $table current table
  68. * @param array $columnMeta array containing default values for the fields
  69. *
  70. * @return string HTML
  71. */
  72. function PMA_getHtmlForCreateNewColumn(
  73. $num_fields, $db, $table, $columnMeta=array()
  74. ) {
  75. $cfgRelation = PMA_getRelationsParam();
  76. $content_cells = array();
  77. $available_mime = array();
  78. $mime_map = array();
  79. if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) {
  80. $mime_map = PMA_getMIME($db, $table);
  81. $available_mime = PMA_getAvailableMIMEtypes();
  82. }
  83. $comments_map = PMA_getComments($db, $table);
  84. for ($columnNumber = 0; $columnNumber < $num_fields; $columnNumber++) {
  85. $content_cells[$columnNumber] = array(
  86. 'columnNumber' => $columnNumber,
  87. 'columnMeta' => $columnMeta,
  88. 'type_upper' => '',
  89. 'length_values_input_size' => 8,
  90. 'length' => '',
  91. 'extracted_columnspec' => array(),
  92. 'submit_attribute' => null,
  93. 'comments_map' => $comments_map,
  94. 'fields_meta' => null,
  95. 'is_backup' => true,
  96. 'move_columns' => array(),
  97. 'cfgRelation' => $cfgRelation,
  98. 'available_mime' => isset($available_mime)?$available_mime:array(),
  99. 'mime_map' => $mime_map
  100. );
  101. }
  102. return PMA\Template::get('columns_definitions/table_fields_definitions')
  103. ->render(
  104. array(
  105. 'is_backup' => true,
  106. 'fields_meta' => null,
  107. 'mimework' => $cfgRelation['mimework'],
  108. 'content_cells' => $content_cells
  109. )
  110. );
  111. }
  112. /**
  113. * build the html for step 1.1 of normalization
  114. *
  115. * @param string $db current database
  116. * @param string $table current table
  117. * @param string $normalizedTo up to which step normalization will go,
  118. * possible values 1nf|2nf|3nf
  119. *
  120. * @return string HTML for step 1.1
  121. */
  122. function PMA_getHtmlFor1NFStep1($db, $table, $normalizedTo)
  123. {
  124. $step = 1;
  125. $stepTxt = __('Make all columns atomic');
  126. $html = "<h3 class='center'>"
  127. . __('First step of normalization (1NF)') . "</h3>";
  128. $html .= "<div id='mainContent' data-normalizeto='" . $normalizedTo . "'>" .
  129. "<fieldset>" .
  130. "<legend>" . __('Step 1.') . $step . " " . $stepTxt . "</legend>" .
  131. "<h4>" . __(
  132. 'Do you have any column which can be split into more than'
  133. . ' one column? '
  134. . 'For example: address can be split into street, city, country and zip.'
  135. )
  136. . "</br>(<a class='central_columns_dialog' data-maxrows='25' "
  137. . "data-pick=false href='#'> "
  138. . __(
  139. 'Show me the central list of columns that are not already in this table'
  140. ) . " </a>)</h4>"
  141. . "<p class='cm-em'>" . __(
  142. 'Select a column which can be split into more '
  143. . 'than one. (on select of \'no such column\', it\'ll move to next step)'
  144. )
  145. . "</p>"
  146. . "<div id='extra'>"
  147. . "<select id='selectNonAtomicCol' name='makeAtomic'>"
  148. . '<option selected="selected" disabled="disabled">'
  149. . __('Select one…') . "</option>"
  150. . "<option value='no_such_col'>" . __('No such column') . "</option>"
  151. . PMA_getHtmlForColumnsList(
  152. $db,
  153. $table,
  154. _pgettext('string types', 'String')
  155. )
  156. . "</select>"
  157. . "<span>" . __('split into ')
  158. . "</span><input id='numField' type='number' value='2'>"
  159. . "<input type='submit' id='splitGo' value='" . __('Go') . "'/></div>"
  160. . "<div id='newCols'></div>"
  161. . "</fieldset><fieldset class='tblFooters'>"
  162. . "</fieldset>"
  163. . "</div>";
  164. return $html;
  165. }
  166. /**
  167. * build the html contents of various html elements in step 1.2
  168. *
  169. * @param string $db current database
  170. * @param string $table current table
  171. *
  172. * @return string HTML contents for step 1.2
  173. */
  174. function PMA_getHtmlContentsFor1NFStep2($db, $table)
  175. {
  176. $step = 2;
  177. $stepTxt = __('Have a primary key');
  178. $primary = PMA_Index::getPrimary($table, $db);
  179. $hasPrimaryKey = "0";
  180. $legendText = __('Step 1.') . $step . " " . $stepTxt;
  181. $extra = '';
  182. if ($primary) {
  183. $headText = __("Primary key already exists.");
  184. $subText = __("Taking you to next step…");
  185. $hasPrimaryKey = "1";
  186. } else {
  187. $headText = __(
  188. "There is no primary key; please add one.<br/>"
  189. . "Hint: A primary key is a column "
  190. . "(or combination of columns) that uniquely identify all rows."
  191. );
  192. $subText = '<a href="#" id="createPrimaryKey">'
  193. . PMA_Util::getIcon(
  194. 'b_index_add.png', __(
  195. 'Add a primary key on existing column(s)'
  196. )
  197. )
  198. . '</a>';
  199. $extra = __(
  200. "If it's not possible to make existing "
  201. . "column combinations as primary key"
  202. ) . "<br/>"
  203. . '<a href="#" id="addNewPrimary">'
  204. . __('+ Add a new primary key column') . '</a>';
  205. }
  206. $res = array(
  207. 'legendText' => $legendText,
  208. 'headText' => $headText,
  209. 'subText' => $subText,
  210. 'hasPrimaryKey' => $hasPrimaryKey,
  211. 'extra' => $extra
  212. );
  213. return $res;
  214. }
  215. /**
  216. * build the html contents of various html elements in step 1.4
  217. *
  218. * @param string $db current database
  219. * @param string $table current table
  220. *
  221. * @return string HTML contents for step 1.4
  222. */
  223. function PMA_getHtmlContentsFor1NFStep4($db, $table)
  224. {
  225. $step = 4;
  226. $stepTxt = __('Remove redundant columns');
  227. $legendText = __('Step 1.') . $step . " " . $stepTxt;
  228. $headText = __(
  229. "Do you have a group of columns which on combining gives an existing"
  230. . " column? For example, if you have first_name, last_name and"
  231. . " full_name then combining first_name and last_name gives full_name"
  232. . " which is redundant."
  233. );
  234. $subText = __(
  235. "Check the columns which are redundant and click on remove. "
  236. . "If no redundant column, click on 'No redundant column'"
  237. );
  238. $extra = PMA_getHtmlForColumnsList($db, $table, 'all', "checkbox") . "</br>"
  239. . '<input type="submit" id="removeRedundant" value="'
  240. . __('Remove selected') . '"/>'
  241. . '<input type="submit" value="' . __('No redundant column')
  242. . '" onclick="goToFinish1NF();"'
  243. . '/>';
  244. $res = array(
  245. 'legendText' => $legendText,
  246. 'headText' => $headText,
  247. 'subText' => $subText,
  248. 'extra' => $extra
  249. );
  250. return $res;
  251. }
  252. /**
  253. * build the html contents of various html elements in step 1.3
  254. *
  255. * @param string $db current database
  256. * @param string $table current table
  257. *
  258. * @return string HTML contents for step 1.3
  259. */
  260. function PMA_getHtmlContentsFor1NFStep3($db, $table)
  261. {
  262. $step = 3;
  263. $stepTxt = __('Move repeating groups');
  264. $legendText = __('Step 1.') . $step . " " . $stepTxt;
  265. $headText = __(
  266. "Do you have a group of two or more columns that are closely "
  267. . "related and are all repeating the same attribute? For example, "
  268. . "a table that holds data on books might have columns such as book_id, "
  269. . "author1, author2, author3 and so on which form a "
  270. . "repeating group. In this case a new table (book_id, author) should "
  271. . "be created."
  272. );
  273. $subText = __(
  274. "Check the columns which form a repeating group. "
  275. . "If no such group, click on 'No repeating group'"
  276. );
  277. $extra = PMA_getHtmlForColumnsList($db, $table, 'all', "checkbox") . "</br>"
  278. . '<input type="submit" id="moveRepeatingGroup" value="'
  279. . __('Done') . '"/>'
  280. . '<input type="submit" value="' . __('No repeating group')
  281. . '" onclick="goToStep4();"'
  282. . '/>';
  283. $primary = PMA_Index::getPrimary($table, $db);
  284. $primarycols = $primary->getColumns();
  285. $pk = array();
  286. foreach ($primarycols as $col) {
  287. $pk[] = $col->getName();
  288. }
  289. $res = array(
  290. 'legendText' => $legendText,
  291. 'headText' => $headText,
  292. 'subText' => $subText,
  293. 'extra' => $extra,
  294. 'primary_key' => json_encode($pk)
  295. );
  296. return $res;
  297. }
  298. /**
  299. * build html contents for 2NF step 2.1
  300. *
  301. * @param string $db current database
  302. * @param string $table current table
  303. *
  304. * @return string HTML contents for 2NF step 2.1
  305. */
  306. function PMA_getHtmlFor2NFstep1($db, $table)
  307. {
  308. $legendText = __('Step 2.') . "1 " . __('Find partial dependencies');
  309. $primary = PMA_Index::getPrimary($table, $db);
  310. $primarycols = $primary->getColumns();
  311. $pk = array();
  312. $subText = '';
  313. $selectPkForm = "";
  314. $extra = "";
  315. foreach ($primarycols as $col) {
  316. $pk[] = $col->getName();
  317. $selectPkForm .= '<input type="checkbox" name="pd" value="'
  318. . htmlspecialchars($col->getName()) . '">'
  319. . htmlspecialchars($col->getName());
  320. }
  321. $key = implode(', ', $pk);
  322. if (count($primarycols) > 1) {
  323. $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']);
  324. $columns = (array) $GLOBALS['dbi']->getColumnNames(
  325. $db, $table, $GLOBALS['userlink']
  326. );
  327. if (count($pk) == count($columns)) {
  328. $headText = sprintf(
  329. __(
  330. 'No partial dependencies possible as '
  331. . 'no non-primary column exists since primary key ( %1$s ) '
  332. . 'is composed of all the columns in the table.'
  333. ), htmlspecialchars($key)
  334. ) . '<br/>';
  335. $extra = '<h3>' . __('Table is already in second normal form.')
  336. . '</h3>';
  337. } else {
  338. $headText = sprintf(
  339. __(
  340. 'The primary key ( %1$s ) consists of more than one column '
  341. . 'so we need to find the partial dependencies.'
  342. ), htmlspecialchars($key)
  343. ) . '<br/>' . __(
  344. 'Please answer the following question(s) '
  345. . 'carefully to obtain a correct normalization.'
  346. )
  347. . '<br/><a href="#" id="showPossiblePd">' . __(
  348. '+ Show me the possible partial dependencies '
  349. . 'based on data in the table'
  350. ) . '</a>';
  351. $subText = __(
  352. 'For each column below, '
  353. . 'please select the <b>minimal set</b> of columns among given set '
  354. . 'whose values combined together are sufficient'
  355. . ' to determine the value of the column.'
  356. );
  357. $cnt = 0;
  358. foreach ($columns as $column) {
  359. if (!in_array($column, $pk)) {
  360. $cnt++;
  361. $extra .= "<b>" . sprintf(
  362. __('\'%1$s\' depends on:'), htmlspecialchars($column)
  363. ) . "</b><br>";
  364. $extra .= '<form id="pk_' . $cnt . '" data-colname="'
  365. . htmlspecialchars($column) . '" class="smallIndent">'
  366. . $selectPkForm . '</form><br/><br/>';
  367. }
  368. }
  369. }
  370. } else {
  371. $headText = sprintf(
  372. __(
  373. 'No partial dependencies possible as the primary key'
  374. . ' ( %1$s ) has just one column.'
  375. ), htmlspecialchars($key)
  376. ) . '<br/>';
  377. $extra = '<h3>' . __('Table is already in second normal form.') . '</h3>';
  378. }
  379. $res = array(
  380. 'legendText' => $legendText,
  381. 'headText' => $headText,
  382. 'subText' => $subText,
  383. 'extra' => $extra,
  384. 'primary_key' => $key
  385. );
  386. return $res;
  387. }
  388. /**
  389. * build the html for showing the tables to have in order to put current table in 2NF
  390. *
  391. * @param array $partialDependencies array containing all the dependencies
  392. * @param string $table current table
  393. *
  394. * @return string HTML
  395. */
  396. function PMA_getHtmlForNewTables2NF($partialDependencies,$table)
  397. {
  398. $html = '<p><b>' . sprintf(
  399. __(
  400. 'In order to put the '
  401. . 'original table \'%1$s\' into Second normal form we need '
  402. . 'to create the following tables:'
  403. ), htmlspecialchars($table)
  404. ) . '</b></p>';
  405. $tableName = $table;
  406. $i = 1;
  407. foreach ($partialDependencies as $key=>$dependents) {
  408. $html .= '<p><input type="text" name="' . htmlspecialchars($key)
  409. . '" value="' . htmlspecialchars($tableName) . '"/>'
  410. . '( <u>' . htmlspecialchars($key) . '</u>'
  411. . (count($dependents)>0?', ':'')
  412. . htmlspecialchars(implode(', ', $dependents)) . ' )';
  413. $i++;
  414. $tableName = 'table' . $i;
  415. }
  416. return $html;
  417. }
  418. /**
  419. * create/alter the tables needed for 2NF
  420. *
  421. * @param array $partialDependencies array containing all the partial dependencies
  422. * @param object $tablesName name of new tables
  423. * @param string $table current table
  424. * @param string $db current database
  425. *
  426. * @return array
  427. */
  428. function PMA_createNewTablesFor2NF($partialDependencies, $tablesName, $table, $db)
  429. {
  430. $dropCols = false;
  431. $nonPKCols = array();
  432. $queries = array();
  433. $error = false;
  434. $headText = '<h3>' . sprintf(
  435. __('The second step of normalization is complete for table \'%1$s\'.'),
  436. htmlspecialchars($table)
  437. ) . '</h3>';
  438. if (count((array)$partialDependencies) == 1) {
  439. return array(
  440. 'legendText'=>__('End of step'), 'headText'=>$headText,
  441. 'queryError'=>$error
  442. );
  443. }
  444. $message = '';
  445. $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']);
  446. foreach ($partialDependencies as $key=>$dependents) {
  447. if ($tablesName->$key != $table) {
  448. $backquotedKey = implode(', ', PMA_Util::backquote(explode(', ', $key)));
  449. $queries[] = 'CREATE TABLE ' . PMA_Util::backquote($tablesName->$key)
  450. . ' SELECT DISTINCT ' . $backquotedKey
  451. . (count($dependents)>0?', ':'')
  452. . implode(',', PMA_Util::backquote($dependents))
  453. . ' FROM ' . PMA_Util::backquote($table) . ';';
  454. $queries[] = 'ALTER TABLE ' . PMA_Util::backquote($tablesName->$key)
  455. . ' ADD PRIMARY KEY(' . $backquotedKey . ');';
  456. $nonPKCols = array_merge($nonPKCols, $dependents);
  457. } else {
  458. $dropCols = true;
  459. }
  460. }
  461. if ($dropCols) {
  462. $query = 'ALTER TABLE ' . PMA_Util::backquote($table);
  463. foreach ($nonPKCols as $col) {
  464. $query .= ' DROP ' . PMA_Util::backquote($col) . ',';
  465. }
  466. $query = trim($query, ', ');
  467. $query .= ';';
  468. $queries[] = $query;
  469. } else {
  470. $queries[] = 'DROP TABLE ' . PMA_Util::backquote($table);
  471. }
  472. foreach ($queries as $query) {
  473. if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) {
  474. $message = PMA_Message::error(__('Error in processing!'));
  475. $message->addMessage('<br /><br />');
  476. $message->addMessage(
  477. PMA_Message::rawError(
  478. $GLOBALS['dbi']->getError($GLOBALS['userlink'])
  479. )
  480. );
  481. $error = true;
  482. break;
  483. }
  484. }
  485. return array(
  486. 'legendText' => __('End of step'),
  487. 'headText' => $headText,
  488. 'queryError' => $error,
  489. 'extra' => $message
  490. );
  491. }
  492. /**
  493. * build the html for showing the new tables to have in order
  494. * to put given tables in 3NF
  495. *
  496. * @param object $dependencies containing all the dependencies
  497. * @param array $tables tables formed after 2NF and need to convert to 3NF
  498. * @param string $db current database
  499. *
  500. * @return array containing html and the list of new tables
  501. */
  502. function PMA_getHtmlForNewTables3NF($dependencies, $tables, $db)
  503. {
  504. $html = "";
  505. $i = 1;
  506. $newTables = array();
  507. foreach ($tables as $table=>$arrDependson) {
  508. if (count(array_unique($arrDependson)) == 1) {
  509. continue;
  510. }
  511. $primary = PMA_Index::getPrimary($table, $db);
  512. $primarycols = $primary->getColumns();
  513. $pk = array();
  514. foreach ($primarycols as $col) {
  515. $pk[] = $col->getName();
  516. }
  517. $html .= '<p><b>' . sprintf(
  518. __(
  519. 'In order to put the '
  520. . 'original table \'%1$s\' into Third normal form we need '
  521. . 'to create the following tables:'
  522. ), htmlspecialchars($table)
  523. ) . '</b></p>';
  524. $tableName = $table;
  525. $columnList = array();
  526. foreach ($arrDependson as $key) {
  527. $dependents = $dependencies->$key;
  528. if ($key == $table) {
  529. $key = implode(', ', $pk);
  530. }
  531. $tmpTableCols =array_merge(explode(', ', $key), $dependents);
  532. sort($tmpTableCols);
  533. if (!in_array($tmpTableCols, $columnList)) {
  534. $columnList[] = $tmpTableCols;
  535. $html .= '<p><input type="text" name="'
  536. . htmlspecialchars($tableName)
  537. . '" value="' . htmlspecialchars($tableName) . '"/>'
  538. . '( <u>' . htmlspecialchars($key) . '</u>'
  539. . (count($dependents)>0?', ':'')
  540. . htmlspecialchars(implode(', ', $dependents)) . ' )';
  541. $newTables[$table][$tableName] = array(
  542. "pk"=>$key, "nonpk"=>implode(', ', $dependents)
  543. );
  544. $i++;
  545. $tableName = 'table' . $i;
  546. }
  547. }
  548. }
  549. return array('html' => $html, 'newTables' => $newTables);
  550. }
  551. /**
  552. * create new tables or alter existing to get 3NF
  553. *
  554. * @param array $newTables list of new tables to be created
  555. * @param string $db current database
  556. *
  557. * @return array
  558. */
  559. function PMA_createNewTablesFor3NF($newTables, $db)
  560. {
  561. $queries = array();
  562. $dropCols = false;
  563. $error = false;
  564. $headText = '<h3>' .
  565. __('The third step of normalization is complete.')
  566. . '</h3>';
  567. if (count((array)$newTables) == 0) {
  568. return array(
  569. 'legendText'=>__('End of step'), 'headText'=>$headText,
  570. 'queryError'=>$error
  571. );
  572. }
  573. $message = '';
  574. $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']);
  575. foreach ($newTables as $originalTable=>$tablesList) {
  576. foreach ($tablesList as $table=>$cols) {
  577. if ($table != $originalTable) {
  578. $quotedPk = implode(
  579. ', ', PMA_Util::backquote(explode(', ', $cols->pk))
  580. );
  581. $quotedNonpk = implode(
  582. ', ', PMA_Util::backquote(explode(', ', $cols->nonpk))
  583. );
  584. $queries[] = 'CREATE TABLE ' . PMA_Util::backquote($table)
  585. . ' SELECT DISTINCT ' . $quotedPk
  586. . ', ' . $quotedNonpk
  587. . ' FROM ' . PMA_Util::backquote($originalTable) . ';';
  588. $queries[] = 'ALTER TABLE ' . PMA_Util::backquote($table)
  589. . ' ADD PRIMARY KEY(' . $quotedPk . ');';
  590. } else {
  591. $dropCols = $cols;
  592. }
  593. }
  594. if ($dropCols) {
  595. $columns = (array) $GLOBALS['dbi']->getColumnNames(
  596. $db, $originalTable, $GLOBALS['userlink']
  597. );
  598. $colPresent = array_merge(
  599. explode(', ', $dropCols->pk), explode(', ', $dropCols->nonpk)
  600. );
  601. $query = 'ALTER TABLE ' . PMA_Util::backquote($originalTable);
  602. foreach ($columns as $col) {
  603. if (!in_array($col, $colPresent)) {
  604. $query .= ' DROP ' . PMA_Util::backquote($col) . ',';
  605. }
  606. }
  607. $query = trim($query, ', ');
  608. $query .= ';';
  609. $queries[] = $query;
  610. } else {
  611. $queries[] = 'DROP TABLE ' . PMA_Util::backquote($originalTable);
  612. }
  613. $dropCols = false;
  614. }
  615. foreach ($queries as $query) {
  616. if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) {
  617. $message = PMA_Message::error(__('Error in processing!'));
  618. $message->addMessage('<br /><br />');
  619. $message->addMessage(
  620. PMA_Message::rawError(
  621. $GLOBALS['dbi']->getError($GLOBALS['userlink'])
  622. )
  623. );
  624. $error = true;
  625. break;
  626. }
  627. }
  628. return array(
  629. 'legendText' => __('End of step'),
  630. 'headText' => $headText,
  631. 'queryError' => $error,
  632. 'extra' => $message
  633. );
  634. }
  635. /**
  636. * move the repeating group of columns to a new table
  637. *
  638. * @param string $repeatingColumns comma separated list of repeating group columns
  639. * @param string $primary_columns comma separated list of column in primary key
  640. * of $table
  641. * @param string $newTable name of the new table to be created
  642. * @param string $newColumn name of the new column in the new table
  643. * @param string $table current table
  644. * @param string $db current database
  645. *
  646. * @return array
  647. */
  648. function PMA_moveRepeatingGroup(
  649. $repeatingColumns, $primary_columns, $newTable, $newColumn, $table, $db
  650. ) {
  651. $repeatingColumnsArr = (array)PMA_Util::backquote(
  652. explode(', ', $repeatingColumns)
  653. );
  654. $primary_columns = implode(
  655. ',', PMA_Util::backquote(explode(',', $primary_columns))
  656. );
  657. $query1 = 'CREATE TABLE ' . PMA_Util::backquote($newTable);
  658. $query2 = 'ALTER TABLE ' . PMA_Util::backquote($table);
  659. $message = PMA_Message::success(
  660. sprintf(
  661. __('Selected repeating group has been moved to the table \'%s\''),
  662. htmlspecialchars($table)
  663. )
  664. );
  665. $first = true;
  666. $error = false;
  667. foreach ($repeatingColumnsArr as $repeatingColumn) {
  668. if (!$first) {
  669. $query1 .= ' UNION ';
  670. }
  671. $first = false;
  672. $query1 .= ' SELECT ' . $primary_columns . ',' . $repeatingColumn
  673. . ' as ' . PMA_Util::backquote($newColumn)
  674. . ' FROM ' . PMA_Util::backquote($table);
  675. $query2 .= ' DROP ' . $repeatingColumn . ',';
  676. }
  677. $query2 = trim($query2, ',');
  678. $queries = array($query1, $query2);
  679. $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']);
  680. foreach ($queries as $query) {
  681. if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) {
  682. $message = PMA_Message::error(__('Error in processing!'));
  683. $message->addMessage('<br /><br />');
  684. $message->addMessage(
  685. PMA_Message::rawError(
  686. $GLOBALS['dbi']->getError($GLOBALS['userlink'])
  687. )
  688. );
  689. $error = true;
  690. break;
  691. }
  692. }
  693. return array(
  694. 'queryError' => $error, 'message' => $message
  695. );
  696. }
  697. /**
  698. * build html for 3NF step 1 to find the transitive dependencies
  699. *
  700. * @param string $db current database
  701. * @param array $tables tables formed after 2NF and need to process for 3NF
  702. *
  703. * @return string
  704. */
  705. function PMA_getHtmlFor3NFstep1($db, $tables)
  706. {
  707. $legendText = __('Step 3.') . "1 " . __('Find transitive dependencies');
  708. $extra = "";
  709. $headText = __(
  710. 'Please answer the following question(s) '
  711. . 'carefully to obtain a correct normalization.'
  712. );
  713. $subText = __(
  714. 'For each column below, '
  715. . 'please select the <b>minimal set</b> of columns among given set '
  716. . 'whose values combined together are sufficient'
  717. . ' to determine the value of the column.<br />'
  718. . 'Note: A column may have no transitive dependency, '
  719. . 'in that case you don\'t have to select any.'
  720. );
  721. $cnt = 0;
  722. foreach ($tables as $table) {
  723. $primary = PMA_Index::getPrimary($table, $db);
  724. $primarycols = $primary->getColumns();
  725. $selectTdForm = "";
  726. $pk = array();
  727. foreach ($primarycols as $col) {
  728. $pk[] = $col->getName();
  729. }
  730. $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']);
  731. $columns = (array) $GLOBALS['dbi']->getColumnNames(
  732. $db, $table, $GLOBALS['userlink']
  733. );
  734. if (count($columns) - count($pk) <= 1) {
  735. continue;
  736. }
  737. foreach ($columns as $column) {
  738. if (!in_array($column, $pk)) {
  739. $selectTdForm .= '<input type="checkbox" name="pd" value="'
  740. . htmlspecialchars($column) . '">'
  741. . '<span>' . htmlspecialchars($column) . '</span>';
  742. }
  743. }
  744. foreach ($columns as $column) {
  745. if (!in_array($column, $pk)) {
  746. $cnt++;
  747. $extra .= "<b>" . sprintf(
  748. __('\'%1$s\' depends on:'), htmlspecialchars($column)
  749. )
  750. . "</b><br>";
  751. $extra .= '<form id="td_' . $cnt . '" data-colname="'
  752. . htmlspecialchars($column) . '" data-tablename="'
  753. . htmlspecialchars($table) . '" class="smallIndent">'
  754. . $selectTdForm
  755. . '</form><br/><br/>';
  756. }
  757. }
  758. }
  759. if ($extra == "") {
  760. $headText = __(
  761. "No Transitive dependencies possible as the table "
  762. . "doesn't have any non primary key columns"
  763. );
  764. $subText = "";
  765. $extra = "<h3>" . __("Table is already in Third normal form!") . "</h3>";
  766. }
  767. $res = array(
  768. 'legendText' => $legendText,
  769. 'headText' => $headText,
  770. 'subText' => $subText,
  771. 'extra' => $extra
  772. );
  773. return $res;
  774. }
  775. /**
  776. * get html for options to normalize table
  777. *
  778. * @return string HTML
  779. */
  780. function PMA_getHtmlForNormalizetable()
  781. {
  782. $html_output = '<form method="post" action="normalization.php" '
  783. . 'name="normalize" '
  784. . 'id="normalizeTable" '
  785. . '>'
  786. . PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table'])
  787. . '<input type="hidden" name="step1" value="1">';
  788. $html_output .= '<fieldset>';
  789. $html_output .= '<legend>'
  790. . __('Improve table structure (Normalization):') . '</legend>';
  791. $html_output .= '<h3>' . __('Select up to what step you want to normalize')
  792. . '</h3>';
  793. $choices = array(
  794. '1nf' => __('First step of normalization (1NF)'),
  795. '2nf' => __('Second step of normalization (1NF+2NF)'),
  796. '3nf' => __('Third step of normalization (1NF+2NF+3NF)'));
  797. $html_output .= PMA_Util::getRadioFields(
  798. 'normalizeTo', $choices, '1nf', true
  799. );
  800. $html_output .= '</fieldset><fieldset class="tblFooters">'
  801. . "<span class='floatleft'>" . __(
  802. 'Hint: Please follow the procedure carefully in order '
  803. . 'to obtain correct normalization'
  804. ) . "</span>"
  805. . '<input type="submit" name="submit_normalize" value="' . __('Go') . '" />'
  806. . '</fieldset>'
  807. . '</form>'
  808. . '</div>';
  809. return $html_output;
  810. }
  811. /**
  812. * find all the possible partial dependencies based on data in the table.
  813. *
  814. * @param string $table current table
  815. * @param string $db current database
  816. *
  817. * @return string HTML containing the list of all the possible partial dependencies
  818. */
  819. function PMA_findPartialDependencies($table, $db)
  820. {
  821. $dependencyList = array();
  822. $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']);
  823. $columns = (array) $GLOBALS['dbi']->getColumnNames(
  824. $db, $table, $GLOBALS['userlink']
  825. );
  826. $columns = (array)PMA_Util::backquote($columns);
  827. $totalRowsRes = $GLOBALS['dbi']->fetchResult(
  828. 'SELECT COUNT(*) FROM (SELECT * FROM '
  829. . PMA_Util::backquote($table) . ' LIMIT 500) as dt;'
  830. );
  831. $totalRows = $totalRowsRes[0];
  832. $primary = PMA_Index::getPrimary($table, $db);
  833. $primarycols = $primary->getColumns();
  834. $pk = array();
  835. foreach ($primarycols as $col) {
  836. $pk[] = PMA_Util::backquote($col->getName());
  837. }
  838. $partialKeys = PMA_getAllCombinationPartialKeys($pk);
  839. $distinctValCount = PMA_findDistinctValuesCount(
  840. array_unique(
  841. array_merge($columns, $partialKeys)
  842. ), $table
  843. );
  844. foreach ($columns as $column) {
  845. if (!in_array($column, $pk)) {
  846. foreach ($partialKeys as $partialKey) {
  847. if ($partialKey
  848. && PMA_checkPartialDependency(
  849. $partialKey, $column, $table,
  850. $distinctValCount[$partialKey],
  851. $distinctValCount[$column], $totalRows
  852. )
  853. ) {
  854. $dependencyList[$partialKey][] = $column;
  855. }
  856. }
  857. }
  858. }
  859. $html = __(
  860. 'This list is based on a subset of the table\'s data '
  861. . 'and is not necessarily accurate. '
  862. )
  863. . '<div class="dependencies_box">';
  864. foreach ($dependencyList as $dependon=>$colList) {
  865. $html .= '<span class="displayblock">'
  866. . '<input type="button" class="pickPd" value="' . __('Pick') . '"/>'
  867. . '<span class="determinants">'
  868. . htmlspecialchars(str_replace('`', '', $dependon)) . '</span> -> '
  869. . '<span class="dependents">'
  870. . htmlspecialchars(str_replace('`', '', implode(', ', $colList)))
  871. . '</span>'
  872. . '</span>';
  873. }
  874. if (empty($dependencyList)) {
  875. $html .= '<p class="displayblock desc">'
  876. . __('No partial dependencies found!') . '</p>';
  877. }
  878. $html .= '</div>';
  879. return $html;
  880. }
  881. /**
  882. * check whether a particular column is dependent on given subset of primary key
  883. *
  884. * @param string $partialKey the partial key, subset of primary key,
  885. * each column in key supposed to be backquoted
  886. * @param string $column backquoted column on whose dependency being checked
  887. * @param string $table current table
  888. * @param integer $pkCnt distinct value count for given partial key
  889. * @param integer $colCnt distinct value count for given column
  890. * @param integer $totalRows total distinct rows count of the table
  891. *
  892. * @return boolean TRUE if $column is dependent on $partialKey, False otherwise
  893. */
  894. function PMA_checkPartialDependency(
  895. $partialKey, $column, $table, $pkCnt, $colCnt, $totalRows
  896. ) {
  897. $query = 'SELECT '
  898. . 'COUNT(DISTINCT ' . $partialKey . ',' . $column . ') as pkColCnt '
  899. . 'FROM (SELECT * FROM ' . PMA_Util::backquote($table)
  900. . ' LIMIT 500) as dt' . ';';
  901. $res = $GLOBALS['dbi']->fetchResult($query, null, null, $GLOBALS['userlink']);
  902. $pkColCnt = $res[0];
  903. if ($pkCnt && $pkCnt == $colCnt && $colCnt == $pkColCnt) {
  904. return true;
  905. }
  906. if ($totalRows && $totalRows == $pkCnt) {
  907. return true;
  908. }
  909. return false;
  910. }
  911. /**
  912. * function to get distinct values count of all the column in the array $columns
  913. *
  914. * @param array $columns array of backquoted columns whose distinct values
  915. * need to be counted.
  916. * @param string $table table to which these columns belong
  917. *
  918. * @return array associative array containing the count
  919. */
  920. function PMA_findDistinctValuesCount($columns, $table)
  921. {
  922. $result = array();
  923. $query = 'SELECT ';
  924. foreach ($columns as $column) {
  925. if ($column) { //each column is already backquoted
  926. $query .= 'COUNT(DISTINCT ' . $column . ') as \''
  927. . $column . '_cnt\', ';
  928. }
  929. }
  930. $query = trim($query, ', ');
  931. $query .= ' FROM (SELECT * FROM ' . PMA_Util::backquote($table)
  932. . ' LIMIT 500) as dt' . ';';
  933. $res = $GLOBALS['dbi']->fetchResult($query, null, null, $GLOBALS['userlink']);
  934. foreach ($columns as $column) {
  935. if ($column) {
  936. $result[$column] = $res[0][$column . '_cnt'];
  937. }
  938. }
  939. return $result;
  940. }
  941. /**
  942. * find all the possible partial keys
  943. *
  944. * @param array $primaryKey array containing all the column present in primary key
  945. *
  946. * @return array containing all the possible partial keys(subset of primary key)
  947. */
  948. function PMA_getAllCombinationPartialKeys($primaryKey)
  949. {
  950. $results = array('');
  951. foreach ($primaryKey as $element) {
  952. foreach ($results as $combination) {
  953. array_push(
  954. $results, trim($element . ',' . $combination, ',')
  955. );
  956. }
  957. }
  958. array_pop($results); //remove key which consist of all primary key columns
  959. return $results;
  960. }