From 67e77113689fadcf00f42cdcb64c4350c0a0b12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Tue, 5 Sep 2017 23:03:43 -0300 Subject: [PATCH] Refactor normalization functions to static methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurício Meneghini Fauth --- libraries/classes/Normalization.php | 997 ++++++++++++++++++ libraries/normalization.lib.php | 982 ----------------- normalization.php | 36 +- .../NormalizationTest.php} | 100 +- 4 files changed, 1065 insertions(+), 1050 deletions(-) create mode 100644 libraries/classes/Normalization.php delete mode 100644 libraries/normalization.lib.php rename test/{libraries/PMA_normalization_test.php => classes/NormalizationTest.php} (82%) diff --git a/libraries/classes/Normalization.php b/libraries/classes/Normalization.php new file mode 100644 index 0000000000..ee4131af18 --- /dev/null +++ b/libraries/classes/Normalization.php @@ -0,0 +1,997 @@ +getColumns(); + $columnTypeList = $types[$colTypeCategory]; + } + $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); + $columns = $GLOBALS['dbi']->getColumns( + $db, $table, null, + true, $GLOBALS['userlink'] + ); + $type = ""; + $selectColHtml = ""; + foreach ($columns as $column => $def) { + if (isset($def['Type'])) { + $extracted_columnspec = Util::extractColumnSpec($def['Type']); + $type = $extracted_columnspec['type']; + } + if (empty($columnTypeList) + || in_array(mb_strtoupper($type), $columnTypeList) + ) { + if ($listType == 'checkbox') { + $selectColHtml .= '' + . htmlspecialchars($column) . ' [ ' + . htmlspecialchars($def['Type']) . ' ]
'; + } else { + $selectColHtml .= ''; + } + } + } + return $selectColHtml; + } + + /** + * get the html of the form to add the new column to given table + * + * @param integer $num_fields number of columns to add + * @param string $db current database + * @param string $table current table + * @param array $columnMeta array containing default values for the fields + * + * @return string HTML + */ + public static function getHtmlForCreateNewColumn( + $num_fields, $db, $table, $columnMeta=array() + ) { + $cfgRelation = Relation::getRelationsParam(); + $content_cells = array(); + $available_mime = array(); + $mime_map = array(); + if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) { + $mime_map = Transformations::getMIME($db, $table); + $available_mime = Transformations::getAvailableMIMEtypes(); + } + $comments_map = Relation::getComments($db, $table); + for ($columnNumber = 0; $columnNumber < $num_fields; $columnNumber++) { + $content_cells[$columnNumber] = array( + 'columnNumber' => $columnNumber, + 'columnMeta' => $columnMeta, + 'type_upper' => '', + 'length_values_input_size' => 8, + 'length' => '', + 'extracted_columnspec' => array(), + 'submit_attribute' => null, + 'comments_map' => $comments_map, + 'fields_meta' => null, + 'is_backup' => true, + 'move_columns' => array(), + 'cfgRelation' => $cfgRelation, + 'available_mime' => isset($available_mime)?$available_mime:array(), + 'mime_map' => $mime_map + ); + } + + return Template::get( + 'columns_definitions/table_fields_definitions' + ) + ->render( + array( + 'is_backup' => true, + 'fields_meta' => null, + 'mimework' => $cfgRelation['mimework'], + 'content_cells' => $content_cells + ) + ); + } + /** + * build the html for step 1.1 of normalization + * + * @param string $db current database + * @param string $table current table + * @param string $normalizedTo up to which step normalization will go, + * possible values 1nf|2nf|3nf + * + * @return string HTML for step 1.1 + */ + public static function getHtmlFor1NFStep1($db, $table, $normalizedTo) + { + $step = 1; + $stepTxt = __('Make all columns atomic'); + $html = "

" + . __('First step of normalization (1NF)') . "

"; + $html .= "
" . + "
" . + "" . __('Step 1.') . $step . " " . $stepTxt . "" . + "

" . __( + 'Do you have any column which can be split into more than' + . ' one column? ' + . 'For example: address can be split into street, city, country and zip.' + ) + . "
( " + . __( + 'Show me the central list of columns that are not already in this table' + ) . " )

" + . "

" . __( + 'Select a column which can be split into more ' + . 'than one (on select of \'no such column\', it\'ll move to next step).' + ) + . "

" + . "
" + . "" + . "" . __('split into ') + . "" + . "
" + . "
" + . "
" + . "
" + . "
"; + return $html; + } + + /** + * build the html contents of various html elements in step 1.2 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.2 + */ + public static function getHtmlContentsFor1NFStep2($db, $table) + { + $step = 2; + $stepTxt = __('Have a primary key'); + $primary = Index::getPrimary($table, $db); + $hasPrimaryKey = "0"; + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $extra = ''; + if ($primary) { + $headText = __("Primary key already exists."); + $subText = __("Taking you to next step…"); + $hasPrimaryKey = "1"; + } else { + $headText = __( + "There is no primary key; please add one.
" + . "Hint: A primary key is a column " + . "(or combination of columns) that uniquely identify all rows." + ); + $subText = '' + . Util::getIcon( + 'b_index_add.png', __( + 'Add a primary key on existing column(s)' + ) + ) + . ''; + $extra = __( + "If it's not possible to make existing " + . "column combinations as primary key" + ) . "
" + . '' + . __('+ Add a new primary key column') . ''; + } + $res = array( + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'hasPrimaryKey' => $hasPrimaryKey, + 'extra' => $extra + ); + return $res; + } + + /** + * build the html contents of various html elements in step 1.4 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.4 + */ + public static function getHtmlContentsFor1NFStep4($db, $table) + { + $step = 4; + $stepTxt = __('Remove redundant columns'); + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $headText = __( + "Do you have a group of columns which on combining gives an existing" + . " column? For example, if you have first_name, last_name and" + . " full_name then combining first_name and last_name gives full_name" + . " which is redundant." + ); + $subText = __( + "Check the columns which are redundant and click on remove. " + . "If no redundant column, click on 'No redundant column'" + ); + $extra = self::getHtmlForColumnsList($db, $table, 'all', "checkbox") . "
" + . '' + . ''; + $res = array( + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra + ); + return $res; + } + + /** + * build the html contents of various html elements in step 1.3 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.3 + */ + public static function getHtmlContentsFor1NFStep3($db, $table) + { + $step = 3; + $stepTxt = __('Move repeating groups'); + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $headText = __( + "Do you have a group of two or more columns that are closely " + . "related and are all repeating the same attribute? For example, " + . "a table that holds data on books might have columns such as book_id, " + . "author1, author2, author3 and so on which form a " + . "repeating group. In this case a new table (book_id, author) should " + . "be created." + ); + $subText = __( + "Check the columns which form a repeating group. " + . "If no such group, click on 'No repeating group'" + ); + $extra = self::getHtmlForColumnsList($db, $table, 'all', "checkbox") . "
" + . '' + . ''; + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = array(); + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $res = array( + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra, + 'primary_key' => json_encode($pk) + ); + return $res; + } + + /** + * build html contents for 2NF step 2.1 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for 2NF step 2.1 + */ + public static function getHtmlFor2NFstep1($db, $table) + { + $legendText = __('Step 2.') . "1 " . __('Find partial dependencies'); + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = array(); + $subText = ''; + $selectPkForm = ""; + $extra = ""; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + $selectPkForm .= '' + . htmlspecialchars($col->getName()); + } + $key = implode(', ', $pk); + if (count($primarycols) > 1) { + $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); + $columns = (array) $GLOBALS['dbi']->getColumnNames( + $db, $table, $GLOBALS['userlink'] + ); + if (count($pk) == count($columns)) { + $headText = sprintf( + __( + 'No partial dependencies possible as ' + . 'no non-primary column exists since primary key ( %1$s ) ' + . 'is composed of all the columns in the table.' + ), htmlspecialchars($key) + ) . '
'; + $extra = '

' . __('Table is already in second normal form.') + . '

'; + } else { + $headText = sprintf( + __( + 'The primary key ( %1$s ) consists of more than one column ' + . 'so we need to find the partial dependencies.' + ), htmlspecialchars($key) + ) . '
' . __( + 'Please answer the following question(s) ' + . 'carefully to obtain a correct normalization.' + ) + . '
' . __( + '+ Show me the possible partial dependencies ' + . 'based on data in the table' + ) . ''; + $subText = __( + 'For each column below, ' + . 'please select the minimal set of columns among given set ' + . 'whose values combined together are sufficient' + . ' to determine the value of the column.' + ); + $cnt = 0; + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $cnt++; + $extra .= "" . sprintf( + __('\'%1$s\' depends on:'), htmlspecialchars($column) + ) . "
"; + $extra .= '
' + . $selectPkForm . '


'; + } + } + } + } else { + $headText = sprintf( + __( + 'No partial dependencies possible as the primary key' + . ' ( %1$s ) has just one column.' + ), htmlspecialchars($key) + ) . '
'; + $extra = '

' . __('Table is already in second normal form.') . '

'; + } + $res = array( + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra, + 'primary_key' => $key + ); + return $res; + } + + /** + * build the html for showing the tables to have in order to put current table in 2NF + * + * @param array $partialDependencies array containing all the dependencies + * @param string $table current table + * + * @return string HTML + */ + public static function getHtmlForNewTables2NF($partialDependencies,$table) + { + $html = '

' . sprintf( + __( + 'In order to put the ' + . 'original table \'%1$s\' into Second normal form we need ' + . 'to create the following tables:' + ), htmlspecialchars($table) + ) . '

'; + $tableName = $table; + $i = 1; + foreach ($partialDependencies as $key=>$dependents) { + $html .= '

' + . '( ' . htmlspecialchars($key) . '' + . (count($dependents)>0?', ':'') + . htmlspecialchars(implode(', ', $dependents)) . ' )'; + $i++; + $tableName = 'table' . $i; + } + return $html; + } + + /** + * create/alter the tables needed for 2NF + * + * @param array $partialDependencies array containing all the partial dependencies + * @param object $tablesName name of new tables + * @param string $table current table + * @param string $db current database + * + * @return array + */ + public static function createNewTablesFor2NF($partialDependencies, $tablesName, $table, $db) + { + $dropCols = false; + $nonPKCols = array(); + $queries = array(); + $error = false; + $headText = '

' . sprintf( + __('The second step of normalization is complete for table \'%1$s\'.'), + htmlspecialchars($table) + ) . '

'; + if (count((array)$partialDependencies) == 1) { + return array( + 'legendText'=>__('End of step'), 'headText'=>$headText, + 'queryError'=>$error + ); + } + $message = ''; + $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); + foreach ($partialDependencies as $key=>$dependents) { + if ($tablesName->$key != $table) { + $backquotedKey = implode(', ', Util::backquote(explode(', ', $key))); + $queries[] = 'CREATE TABLE ' . Util::backquote($tablesName->$key) + . ' SELECT DISTINCT ' . $backquotedKey + . (count($dependents)>0?', ':'') + . implode(',', Util::backquote($dependents)) + . ' FROM ' . Util::backquote($table) . ';'; + $queries[] = 'ALTER TABLE ' . Util::backquote($tablesName->$key) + . ' ADD PRIMARY KEY(' . $backquotedKey . ');'; + $nonPKCols = array_merge($nonPKCols, $dependents); + } else { + $dropCols = true; + } + } + + if ($dropCols) { + $query = 'ALTER TABLE ' . Util::backquote($table); + foreach ($nonPKCols as $col) { + $query .= ' DROP ' . Util::backquote($col) . ','; + } + $query = trim($query, ', '); + $query .= ';'; + $queries[] = $query; + } else { + $queries[] = 'DROP TABLE ' . Util::backquote($table); + } + foreach ($queries as $query) { + if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $GLOBALS['dbi']->getError($GLOBALS['userlink']) + ), + '

' + ); + $error = true; + break; + } + } + return array( + 'legendText' => __('End of step'), + 'headText' => $headText, + 'queryError' => $error, + 'extra' => $message + ); + } + + /** + * build the html for showing the new tables to have in order + * to put given tables in 3NF + * + * @param object $dependencies containing all the dependencies + * @param array $tables tables formed after 2NF and need to convert to 3NF + * @param string $db current database + * + * @return array containing html and the list of new tables + */ + public static function getHtmlForNewTables3NF($dependencies, $tables, $db) + { + $html = ""; + $i = 1; + $newTables = array(); + foreach ($tables as $table=>$arrDependson) { + if (count(array_unique($arrDependson)) == 1) { + continue; + } + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = array(); + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $html .= '

' . sprintf( + __( + 'In order to put the ' + . 'original table \'%1$s\' into Third normal form we need ' + . 'to create the following tables:' + ), htmlspecialchars($table) + ) . '

'; + $tableName = $table; + $columnList = array(); + foreach ($arrDependson as $key) { + $dependents = $dependencies->$key; + if ($key == $table) { + $key = implode(', ', $pk); + } + $tmpTableCols =array_merge(explode(', ', $key), $dependents); + sort($tmpTableCols); + if (!in_array($tmpTableCols, $columnList)) { + $columnList[] = $tmpTableCols; + $html .= '

' + . '( ' . htmlspecialchars($key) . '' + . (count($dependents)>0?', ':'') + . htmlspecialchars(implode(', ', $dependents)) . ' )'; + $newTables[$table][$tableName] = array( + "pk"=>$key, "nonpk"=>implode(', ', $dependents) + ); + $i++; + $tableName = 'table' . $i; + } + } + } + return array('html' => $html, 'newTables' => $newTables, 'success' => true); + } + + /** + * create new tables or alter existing to get 3NF + * + * @param array $newTables list of new tables to be created + * @param string $db current database + * + * @return array + */ + public static function createNewTablesFor3NF($newTables, $db) + { + $queries = array(); + $dropCols = false; + $error = false; + $headText = '

' . + __('The third step of normalization is complete.') + . '

'; + if (count((array)$newTables) == 0) { + return array( + 'legendText'=>__('End of step'), 'headText'=>$headText, + 'queryError'=>$error + ); + } + $message = ''; + $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); + foreach ($newTables as $originalTable=>$tablesList) { + foreach ($tablesList as $table=>$cols) { + if ($table != $originalTable) { + $quotedPk = implode( + ', ', Util::backquote(explode(', ', $cols->pk)) + ); + $quotedNonpk = implode( + ', ', Util::backquote(explode(', ', $cols->nonpk)) + ); + $queries[] = 'CREATE TABLE ' . Util::backquote($table) + . ' SELECT DISTINCT ' . $quotedPk + . ', ' . $quotedNonpk + . ' FROM ' . Util::backquote($originalTable) . ';'; + $queries[] = 'ALTER TABLE ' . Util::backquote($table) + . ' ADD PRIMARY KEY(' . $quotedPk . ');'; + } else { + $dropCols = $cols; + } + } + if ($dropCols) { + $columns = (array) $GLOBALS['dbi']->getColumnNames( + $db, $originalTable, $GLOBALS['userlink'] + ); + $colPresent = array_merge( + explode(', ', $dropCols->pk), explode(', ', $dropCols->nonpk) + ); + $query = 'ALTER TABLE ' . Util::backquote($originalTable); + foreach ($columns as $col) { + if (!in_array($col, $colPresent)) { + $query .= ' DROP ' . Util::backquote($col) . ','; + } + } + $query = trim($query, ', '); + $query .= ';'; + $queries[] = $query; + } else { + $queries[] = 'DROP TABLE ' . Util::backquote($originalTable); + } + $dropCols = false; + } + foreach ($queries as $query) { + if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $GLOBALS['dbi']->getError($GLOBALS['userlink']) + ), + '

' + ); + $error = true; + break; + } + } + return array( + 'legendText' => __('End of step'), + 'headText' => $headText, + 'queryError' => $error, + 'extra' => $message + ); + } + + /** + * move the repeating group of columns to a new table + * + * @param string $repeatingColumns comma separated list of repeating group columns + * @param string $primary_columns comma separated list of column in primary key + * of $table + * @param string $newTable name of the new table to be created + * @param string $newColumn name of the new column in the new table + * @param string $table current table + * @param string $db current database + * + * @return array + */ + public static function moveRepeatingGroup( + $repeatingColumns, $primary_columns, $newTable, $newColumn, $table, $db + ) { + $repeatingColumnsArr = (array)Util::backquote( + explode(', ', $repeatingColumns) + ); + $primary_columns = implode( + ',', Util::backquote(explode(',', $primary_columns)) + ); + $query1 = 'CREATE TABLE ' . Util::backquote($newTable); + $query2 = 'ALTER TABLE ' . Util::backquote($table); + $message = Message::success( + sprintf( + __('Selected repeating group has been moved to the table \'%s\''), + htmlspecialchars($table) + ) + ); + $first = true; + $error = false; + foreach ($repeatingColumnsArr as $repeatingColumn) { + if (!$first) { + $query1 .= ' UNION '; + } + $first = false; + $query1 .= ' SELECT ' . $primary_columns . ',' . $repeatingColumn + . ' as ' . Util::backquote($newColumn) + . ' FROM ' . Util::backquote($table); + $query2 .= ' DROP ' . $repeatingColumn . ','; + } + $query2 = trim($query2, ','); + $queries = array($query1, $query2); + $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); + foreach ($queries as $query) { + if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $GLOBALS['dbi']->getError($GLOBALS['userlink']) + ), + '

' + ); + $error = true; + break; + } + } + return array( + 'queryError' => $error, 'message' => $message + ); + } + + /** + * build html for 3NF step 1 to find the transitive dependencies + * + * @param string $db current database + * @param array $tables tables formed after 2NF and need to process for 3NF + * + * @return string + */ + public static function getHtmlFor3NFstep1($db, $tables) + { + $legendText = __('Step 3.') . "1 " . __('Find transitive dependencies'); + $extra = ""; + $headText = __( + 'Please answer the following question(s) ' + . 'carefully to obtain a correct normalization.' + ); + $subText = __( + 'For each column below, ' + . 'please select the minimal set of columns among given set ' + . 'whose values combined together are sufficient' + . ' to determine the value of the column.
' + . 'Note: A column may have no transitive dependency, ' + . 'in that case you don\'t have to select any.' + ); + $cnt = 0; + foreach ($tables as $table) { + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $selectTdForm = ""; + $pk = array(); + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); + $columns = (array) $GLOBALS['dbi']->getColumnNames( + $db, $table, $GLOBALS['userlink'] + ); + if (count($columns) - count($pk) <= 1) { + continue; + } + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $selectTdForm .= '' + . '' . htmlspecialchars($column) . ''; + } + } + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $cnt++; + $extra .= "" . sprintf( + __('\'%1$s\' depends on:'), htmlspecialchars($column) + ) + . "
"; + $extra .= '
' + . $selectTdForm + . '


'; + } + } + } + if ($extra == "") { + $headText = __( + "No Transitive dependencies possible as the table " + . "doesn't have any non primary key columns" + ); + $subText = ""; + $extra = "

" . __("Table is already in Third normal form!") . "

"; + } + $res = array( + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra + ); + return $res; + } + + /** + * get html for options to normalize table + * + * @return string HTML + */ + public static function getHtmlForNormalizetable() + { + $html_output = '
' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . ''; + $html_output .= '
'; + $html_output .= '' + . __('Improve table structure (Normalization):') . ''; + $html_output .= '

' . __('Select up to what step you want to normalize') + . '

'; + $choices = array( + '1nf' => __('First step of normalization (1NF)'), + '2nf' => __('Second step of normalization (1NF+2NF)'), + '3nf' => __('Third step of normalization (1NF+2NF+3NF)')); + + $html_output .= Util::getRadioFields( + 'normalizeTo', $choices, '1nf', true + ); + $html_output .= '
' + . "" . __( + 'Hint: Please follow the procedure carefully in order ' + . 'to obtain correct normalization' + ) . "" + . '' + . '
' + . '
' + . ''; + + return $html_output; + } + + /** + * find all the possible partial dependencies based on data in the table. + * + * @param string $table current table + * @param string $db current database + * + * @return string HTML containing the list of all the possible partial dependencies + */ + public static function findPartialDependencies($table, $db) + { + $dependencyList = array(); + $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); + $columns = (array) $GLOBALS['dbi']->getColumnNames( + $db, $table, $GLOBALS['userlink'] + ); + $columns = (array)Util::backquote($columns); + $totalRowsRes = $GLOBALS['dbi']->fetchResult( + 'SELECT COUNT(*) FROM (SELECT * FROM ' + . Util::backquote($table) . ' LIMIT 500) as dt;' + ); + $totalRows = $totalRowsRes[0]; + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = array(); + foreach ($primarycols as $col) { + $pk[] = Util::backquote($col->getName()); + } + $partialKeys = self::getAllCombinationPartialKeys($pk); + $distinctValCount = self::findDistinctValuesCount( + array_unique( + array_merge($columns, $partialKeys) + ), $table + ); + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + foreach ($partialKeys as $partialKey) { + if ($partialKey + && self::checkPartialDependency( + $partialKey, $column, $table, + $distinctValCount[$partialKey], + $distinctValCount[$column], $totalRows + ) + ) { + $dependencyList[$partialKey][] = $column; + } + } + } + } + + $html = __( + 'This list is based on a subset of the table\'s data ' + . 'and is not necessarily accurate. ' + ) + . '
'; + foreach ($dependencyList as $dependon=>$colList) { + $html .= '' + . '' + . '' + . htmlspecialchars(str_replace('`', '', $dependon)) . ' -> ' + . '' + . htmlspecialchars(str_replace('`', '', implode(', ', $colList))) + . '' + . ''; + } + if (empty($dependencyList)) { + $html .= '

' + . __('No partial dependencies found!') . '

'; + } + $html .= '
'; + return $html; + } + + /** + * check whether a particular column is dependent on given subset of primary key + * + * @param string $partialKey the partial key, subset of primary key, + * each column in key supposed to be backquoted + * @param string $column backquoted column on whose dependency being checked + * @param string $table current table + * @param integer $pkCnt distinct value count for given partial key + * @param integer $colCnt distinct value count for given column + * @param integer $totalRows total distinct rows count of the table + * + * @return boolean TRUE if $column is dependent on $partialKey, False otherwise + */ + public static function checkPartialDependency( + $partialKey, $column, $table, $pkCnt, $colCnt, $totalRows + ) { + $query = 'SELECT ' + . 'COUNT(DISTINCT ' . $partialKey . ',' . $column . ') as pkColCnt ' + . 'FROM (SELECT * FROM ' . Util::backquote($table) + . ' LIMIT 500) as dt' . ';'; + $res = $GLOBALS['dbi']->fetchResult($query, null, null, $GLOBALS['userlink']); + $pkColCnt = $res[0]; + if ($pkCnt && $pkCnt == $colCnt && $colCnt == $pkColCnt) { + return true; + } + if ($totalRows && $totalRows == $pkCnt) { + return true; + } + return false; + } + + /** + * function to get distinct values count of all the column in the array $columns + * + * @param array $columns array of backquoted columns whose distinct values + * need to be counted. + * @param string $table table to which these columns belong + * + * @return array associative array containing the count + */ + public static function findDistinctValuesCount($columns, $table) + { + $result = array(); + $query = 'SELECT '; + foreach ($columns as $column) { + if ($column) { //each column is already backquoted + $query .= 'COUNT(DISTINCT ' . $column . ') as \'' + . $column . '_cnt\', '; + } + } + $query = trim($query, ', '); + $query .= ' FROM (SELECT * FROM ' . Util::backquote($table) + . ' LIMIT 500) as dt' . ';'; + $res = $GLOBALS['dbi']->fetchResult($query, null, null, $GLOBALS['userlink']); + foreach ($columns as $column) { + if ($column) { + $result[$column] = $res[0][$column . '_cnt']; + } + } + return $result; + } + + /** + * find all the possible partial keys + * + * @param array $primaryKey array containing all the column present in primary key + * + * @return array containing all the possible partial keys(subset of primary key) + */ + public static function getAllCombinationPartialKeys($primaryKey) + { + $results = array(''); + foreach ($primaryKey as $element) { + foreach ($results as $combination) { + array_push( + $results, trim($element . ',' . $combination, ',') + ); + } + } + array_pop($results); //remove key which consist of all primary key columns + return $results; + } +} diff --git a/libraries/normalization.lib.php b/libraries/normalization.lib.php deleted file mode 100644 index ece61d7364..0000000000 --- a/libraries/normalization.lib.php +++ /dev/null @@ -1,982 +0,0 @@ -getColumns(); - $columnTypeList = $types[$colTypeCategory]; - } - $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); - $columns = $GLOBALS['dbi']->getColumns( - $db, $table, null, - true, $GLOBALS['userlink'] - ); - $type = ""; - $selectColHtml = ""; - foreach ($columns as $column => $def) { - if (isset($def['Type'])) { - $extracted_columnspec = Util::extractColumnSpec($def['Type']); - $type = $extracted_columnspec['type']; - } - if (empty($columnTypeList) - || in_array(mb_strtoupper($type), $columnTypeList) - ) { - if ($listType == 'checkbox') { - $selectColHtml .= '' - . htmlspecialchars($column) . ' [ ' - . htmlspecialchars($def['Type']) . ' ]
'; - } else { - $selectColHtml .= ''; - } - } - } - return $selectColHtml; -} - -/** - * get the html of the form to add the new column to given table - * - * @param integer $num_fields number of columns to add - * @param string $db current database - * @param string $table current table - * @param array $columnMeta array containing default values for the fields - * - * @return string HTML - */ -function PMA_getHtmlForCreateNewColumn( - $num_fields, $db, $table, $columnMeta=array() -) { - $cfgRelation = Relation::getRelationsParam(); - $content_cells = array(); - $available_mime = array(); - $mime_map = array(); - if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) { - $mime_map = Transformations::getMIME($db, $table); - $available_mime = Transformations::getAvailableMIMEtypes(); - } - $comments_map = Relation::getComments($db, $table); - for ($columnNumber = 0; $columnNumber < $num_fields; $columnNumber++) { - $content_cells[$columnNumber] = array( - 'columnNumber' => $columnNumber, - 'columnMeta' => $columnMeta, - 'type_upper' => '', - 'length_values_input_size' => 8, - 'length' => '', - 'extracted_columnspec' => array(), - 'submit_attribute' => null, - 'comments_map' => $comments_map, - 'fields_meta' => null, - 'is_backup' => true, - 'move_columns' => array(), - 'cfgRelation' => $cfgRelation, - 'available_mime' => isset($available_mime)?$available_mime:array(), - 'mime_map' => $mime_map - ); - } - - return PhpMyAdmin\Template::get( - 'columns_definitions/table_fields_definitions' - ) - ->render( - array( - 'is_backup' => true, - 'fields_meta' => null, - 'mimework' => $cfgRelation['mimework'], - 'content_cells' => $content_cells - ) - ); -} -/** - * build the html for step 1.1 of normalization - * - * @param string $db current database - * @param string $table current table - * @param string $normalizedTo up to which step normalization will go, - * possible values 1nf|2nf|3nf - * - * @return string HTML for step 1.1 - */ -function PMA_getHtmlFor1NFStep1($db, $table, $normalizedTo) -{ - $step = 1; - $stepTxt = __('Make all columns atomic'); - $html = "

" - . __('First step of normalization (1NF)') . "

"; - $html .= "
" . - "
" . - "" . __('Step 1.') . $step . " " . $stepTxt . "" . - "

" . __( - 'Do you have any column which can be split into more than' - . ' one column? ' - . 'For example: address can be split into street, city, country and zip.' - ) - . "
( " - . __( - 'Show me the central list of columns that are not already in this table' - ) . " )

" - . "

" . __( - 'Select a column which can be split into more ' - . 'than one (on select of \'no such column\', it\'ll move to next step).' - ) - . "

" - . "
" - . "" - . "" . __('split into ') - . "" - . "
" - . "
" - . "
" - . "
" - . "
"; - return $html; -} - -/** - * build the html contents of various html elements in step 1.2 - * - * @param string $db current database - * @param string $table current table - * - * @return string HTML contents for step 1.2 - */ -function PMA_getHtmlContentsFor1NFStep2($db, $table) -{ - $step = 2; - $stepTxt = __('Have a primary key'); - $primary = PhpMyAdmin\Index::getPrimary($table, $db); - $hasPrimaryKey = "0"; - $legendText = __('Step 1.') . $step . " " . $stepTxt; - $extra = ''; - if ($primary) { - $headText = __("Primary key already exists."); - $subText = __("Taking you to next step…"); - $hasPrimaryKey = "1"; - } else { - $headText = __( - "There is no primary key; please add one.
" - . "Hint: A primary key is a column " - . "(or combination of columns) that uniquely identify all rows." - ); - $subText = '' - . Util::getIcon( - 'b_index_add.png', __( - 'Add a primary key on existing column(s)' - ) - ) - . ''; - $extra = __( - "If it's not possible to make existing " - . "column combinations as primary key" - ) . "
" - . '' - . __('+ Add a new primary key column') . ''; - } - $res = array( - 'legendText' => $legendText, - 'headText' => $headText, - 'subText' => $subText, - 'hasPrimaryKey' => $hasPrimaryKey, - 'extra' => $extra - ); - return $res; -} - -/** - * build the html contents of various html elements in step 1.4 - * - * @param string $db current database - * @param string $table current table - * - * @return string HTML contents for step 1.4 - */ -function PMA_getHtmlContentsFor1NFStep4($db, $table) -{ - $step = 4; - $stepTxt = __('Remove redundant columns'); - $legendText = __('Step 1.') . $step . " " . $stepTxt; - $headText = __( - "Do you have a group of columns which on combining gives an existing" - . " column? For example, if you have first_name, last_name and" - . " full_name then combining first_name and last_name gives full_name" - . " which is redundant." - ); - $subText = __( - "Check the columns which are redundant and click on remove. " - . "If no redundant column, click on 'No redundant column'" - ); - $extra = PMA_getHtmlForColumnsList($db, $table, 'all', "checkbox") . "
" - . '' - . ''; - $res = array( - 'legendText' => $legendText, - 'headText' => $headText, - 'subText' => $subText, - 'extra' => $extra - ); - return $res; -} - -/** - * build the html contents of various html elements in step 1.3 - * - * @param string $db current database - * @param string $table current table - * - * @return string HTML contents for step 1.3 - */ -function PMA_getHtmlContentsFor1NFStep3($db, $table) -{ - $step = 3; - $stepTxt = __('Move repeating groups'); - $legendText = __('Step 1.') . $step . " " . $stepTxt; - $headText = __( - "Do you have a group of two or more columns that are closely " - . "related and are all repeating the same attribute? For example, " - . "a table that holds data on books might have columns such as book_id, " - . "author1, author2, author3 and so on which form a " - . "repeating group. In this case a new table (book_id, author) should " - . "be created." - ); - $subText = __( - "Check the columns which form a repeating group. " - . "If no such group, click on 'No repeating group'" - ); - $extra = PMA_getHtmlForColumnsList($db, $table, 'all', "checkbox") . "
" - . '' - . ''; - $primary = PhpMyAdmin\Index::getPrimary($table, $db); - $primarycols = $primary->getColumns(); - $pk = array(); - foreach ($primarycols as $col) { - $pk[] = $col->getName(); - } - $res = array( - 'legendText' => $legendText, - 'headText' => $headText, - 'subText' => $subText, - 'extra' => $extra, - 'primary_key' => json_encode($pk) - ); - return $res; -} - -/** - * build html contents for 2NF step 2.1 - * - * @param string $db current database - * @param string $table current table - * - * @return string HTML contents for 2NF step 2.1 - */ -function PMA_getHtmlFor2NFstep1($db, $table) -{ - $legendText = __('Step 2.') . "1 " . __('Find partial dependencies'); - $primary = PhpMyAdmin\Index::getPrimary($table, $db); - $primarycols = $primary->getColumns(); - $pk = array(); - $subText = ''; - $selectPkForm = ""; - $extra = ""; - foreach ($primarycols as $col) { - $pk[] = $col->getName(); - $selectPkForm .= '' - . htmlspecialchars($col->getName()); - } - $key = implode(', ', $pk); - if (count($primarycols) > 1) { - $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); - $columns = (array) $GLOBALS['dbi']->getColumnNames( - $db, $table, $GLOBALS['userlink'] - ); - if (count($pk) == count($columns)) { - $headText = sprintf( - __( - 'No partial dependencies possible as ' - . 'no non-primary column exists since primary key ( %1$s ) ' - . 'is composed of all the columns in the table.' - ), htmlspecialchars($key) - ) . '
'; - $extra = '

' . __('Table is already in second normal form.') - . '

'; - } else { - $headText = sprintf( - __( - 'The primary key ( %1$s ) consists of more than one column ' - . 'so we need to find the partial dependencies.' - ), htmlspecialchars($key) - ) . '
' . __( - 'Please answer the following question(s) ' - . 'carefully to obtain a correct normalization.' - ) - . '
' . __( - '+ Show me the possible partial dependencies ' - . 'based on data in the table' - ) . ''; - $subText = __( - 'For each column below, ' - . 'please select the minimal set of columns among given set ' - . 'whose values combined together are sufficient' - . ' to determine the value of the column.' - ); - $cnt = 0; - foreach ($columns as $column) { - if (!in_array($column, $pk)) { - $cnt++; - $extra .= "" . sprintf( - __('\'%1$s\' depends on:'), htmlspecialchars($column) - ) . "
"; - $extra .= '
' - . $selectPkForm . '


'; - } - } - } - } else { - $headText = sprintf( - __( - 'No partial dependencies possible as the primary key' - . ' ( %1$s ) has just one column.' - ), htmlspecialchars($key) - ) . '
'; - $extra = '

' . __('Table is already in second normal form.') . '

'; - } - $res = array( - 'legendText' => $legendText, - 'headText' => $headText, - 'subText' => $subText, - 'extra' => $extra, - 'primary_key' => $key - ); - return $res; -} - -/** - * build the html for showing the tables to have in order to put current table in 2NF - * - * @param array $partialDependencies array containing all the dependencies - * @param string $table current table - * - * @return string HTML - */ -function PMA_getHtmlForNewTables2NF($partialDependencies,$table) -{ - $html = '

' . sprintf( - __( - 'In order to put the ' - . 'original table \'%1$s\' into Second normal form we need ' - . 'to create the following tables:' - ), htmlspecialchars($table) - ) . '

'; - $tableName = $table; - $i = 1; - foreach ($partialDependencies as $key=>$dependents) { - $html .= '

' - . '( ' . htmlspecialchars($key) . '' - . (count($dependents)>0?', ':'') - . htmlspecialchars(implode(', ', $dependents)) . ' )'; - $i++; - $tableName = 'table' . $i; - } - return $html; -} - -/** - * create/alter the tables needed for 2NF - * - * @param array $partialDependencies array containing all the partial dependencies - * @param object $tablesName name of new tables - * @param string $table current table - * @param string $db current database - * - * @return array - */ -function PMA_createNewTablesFor2NF($partialDependencies, $tablesName, $table, $db) -{ - $dropCols = false; - $nonPKCols = array(); - $queries = array(); - $error = false; - $headText = '

' . sprintf( - __('The second step of normalization is complete for table \'%1$s\'.'), - htmlspecialchars($table) - ) . '

'; - if (count((array)$partialDependencies) == 1) { - return array( - 'legendText'=>__('End of step'), 'headText'=>$headText, - 'queryError'=>$error - ); - } - $message = ''; - $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); - foreach ($partialDependencies as $key=>$dependents) { - if ($tablesName->$key != $table) { - $backquotedKey = implode(', ', Util::backquote(explode(', ', $key))); - $queries[] = 'CREATE TABLE ' . Util::backquote($tablesName->$key) - . ' SELECT DISTINCT ' . $backquotedKey - . (count($dependents)>0?', ':'') - . implode(',', Util::backquote($dependents)) - . ' FROM ' . Util::backquote($table) . ';'; - $queries[] = 'ALTER TABLE ' . Util::backquote($tablesName->$key) - . ' ADD PRIMARY KEY(' . $backquotedKey . ');'; - $nonPKCols = array_merge($nonPKCols, $dependents); - } else { - $dropCols = true; - } - } - - if ($dropCols) { - $query = 'ALTER TABLE ' . Util::backquote($table); - foreach ($nonPKCols as $col) { - $query .= ' DROP ' . Util::backquote($col) . ','; - } - $query = trim($query, ', '); - $query .= ';'; - $queries[] = $query; - } else { - $queries[] = 'DROP TABLE ' . Util::backquote($table); - } - foreach ($queries as $query) { - if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) { - $message = Message::error(__('Error in processing!')); - $message->addMessage( - Message::rawError( - $GLOBALS['dbi']->getError($GLOBALS['userlink']) - ), - '

' - ); - $error = true; - break; - } - } - return array( - 'legendText' => __('End of step'), - 'headText' => $headText, - 'queryError' => $error, - 'extra' => $message - ); -} - -/** - * build the html for showing the new tables to have in order - * to put given tables in 3NF - * - * @param object $dependencies containing all the dependencies - * @param array $tables tables formed after 2NF and need to convert to 3NF - * @param string $db current database - * - * @return array containing html and the list of new tables - */ -function PMA_getHtmlForNewTables3NF($dependencies, $tables, $db) -{ - $html = ""; - $i = 1; - $newTables = array(); - foreach ($tables as $table=>$arrDependson) { - if (count(array_unique($arrDependson)) == 1) { - continue; - } - $primary = PhpMyAdmin\Index::getPrimary($table, $db); - $primarycols = $primary->getColumns(); - $pk = array(); - foreach ($primarycols as $col) { - $pk[] = $col->getName(); - } - $html .= '

' . sprintf( - __( - 'In order to put the ' - . 'original table \'%1$s\' into Third normal form we need ' - . 'to create the following tables:' - ), htmlspecialchars($table) - ) . '

'; - $tableName = $table; - $columnList = array(); - foreach ($arrDependson as $key) { - $dependents = $dependencies->$key; - if ($key == $table) { - $key = implode(', ', $pk); - } - $tmpTableCols =array_merge(explode(', ', $key), $dependents); - sort($tmpTableCols); - if (!in_array($tmpTableCols, $columnList)) { - $columnList[] = $tmpTableCols; - $html .= '

' - . '( ' . htmlspecialchars($key) . '' - . (count($dependents)>0?', ':'') - . htmlspecialchars(implode(', ', $dependents)) . ' )'; - $newTables[$table][$tableName] = array( - "pk"=>$key, "nonpk"=>implode(', ', $dependents) - ); - $i++; - $tableName = 'table' . $i; - } - } - } - return array('html' => $html, 'newTables' => $newTables, 'success' => true); -} - -/** - * create new tables or alter existing to get 3NF - * - * @param array $newTables list of new tables to be created - * @param string $db current database - * - * @return array - */ -function PMA_createNewTablesFor3NF($newTables, $db) -{ - $queries = array(); - $dropCols = false; - $error = false; - $headText = '

' . - __('The third step of normalization is complete.') - . '

'; - if (count((array)$newTables) == 0) { - return array( - 'legendText'=>__('End of step'), 'headText'=>$headText, - 'queryError'=>$error - ); - } - $message = ''; - $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); - foreach ($newTables as $originalTable=>$tablesList) { - foreach ($tablesList as $table=>$cols) { - if ($table != $originalTable) { - $quotedPk = implode( - ', ', Util::backquote(explode(', ', $cols->pk)) - ); - $quotedNonpk = implode( - ', ', Util::backquote(explode(', ', $cols->nonpk)) - ); - $queries[] = 'CREATE TABLE ' . Util::backquote($table) - . ' SELECT DISTINCT ' . $quotedPk - . ', ' . $quotedNonpk - . ' FROM ' . Util::backquote($originalTable) . ';'; - $queries[] = 'ALTER TABLE ' . Util::backquote($table) - . ' ADD PRIMARY KEY(' . $quotedPk . ');'; - } else { - $dropCols = $cols; - } - } - if ($dropCols) { - $columns = (array) $GLOBALS['dbi']->getColumnNames( - $db, $originalTable, $GLOBALS['userlink'] - ); - $colPresent = array_merge( - explode(', ', $dropCols->pk), explode(', ', $dropCols->nonpk) - ); - $query = 'ALTER TABLE ' . Util::backquote($originalTable); - foreach ($columns as $col) { - if (!in_array($col, $colPresent)) { - $query .= ' DROP ' . Util::backquote($col) . ','; - } - } - $query = trim($query, ', '); - $query .= ';'; - $queries[] = $query; - } else { - $queries[] = 'DROP TABLE ' . Util::backquote($originalTable); - } - $dropCols = false; - } - foreach ($queries as $query) { - if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) { - $message = Message::error(__('Error in processing!')); - $message->addMessage( - Message::rawError( - $GLOBALS['dbi']->getError($GLOBALS['userlink']) - ), - '

' - ); - $error = true; - break; - } - } - return array( - 'legendText' => __('End of step'), - 'headText' => $headText, - 'queryError' => $error, - 'extra' => $message - ); -} -/** - * move the repeating group of columns to a new table - * - * @param string $repeatingColumns comma separated list of repeating group columns - * @param string $primary_columns comma separated list of column in primary key - * of $table - * @param string $newTable name of the new table to be created - * @param string $newColumn name of the new column in the new table - * @param string $table current table - * @param string $db current database - * - * @return array - */ -function PMA_moveRepeatingGroup( - $repeatingColumns, $primary_columns, $newTable, $newColumn, $table, $db -) { - $repeatingColumnsArr = (array)Util::backquote( - explode(', ', $repeatingColumns) - ); - $primary_columns = implode( - ',', Util::backquote(explode(',', $primary_columns)) - ); - $query1 = 'CREATE TABLE ' . Util::backquote($newTable); - $query2 = 'ALTER TABLE ' . Util::backquote($table); - $message = Message::success( - sprintf( - __('Selected repeating group has been moved to the table \'%s\''), - htmlspecialchars($table) - ) - ); - $first = true; - $error = false; - foreach ($repeatingColumnsArr as $repeatingColumn) { - if (!$first) { - $query1 .= ' UNION '; - } - $first = false; - $query1 .= ' SELECT ' . $primary_columns . ',' . $repeatingColumn - . ' as ' . Util::backquote($newColumn) - . ' FROM ' . Util::backquote($table); - $query2 .= ' DROP ' . $repeatingColumn . ','; - } - $query2 = trim($query2, ','); - $queries = array($query1, $query2); - $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); - foreach ($queries as $query) { - if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) { - $message = Message::error(__('Error in processing!')); - $message->addMessage( - Message::rawError( - $GLOBALS['dbi']->getError($GLOBALS['userlink']) - ), - '

' - ); - $error = true; - break; - } - } - return array( - 'queryError' => $error, 'message' => $message - ); -} - -/** - * build html for 3NF step 1 to find the transitive dependencies - * - * @param string $db current database - * @param array $tables tables formed after 2NF and need to process for 3NF - * - * @return string - */ -function PMA_getHtmlFor3NFstep1($db, $tables) -{ - $legendText = __('Step 3.') . "1 " . __('Find transitive dependencies'); - $extra = ""; - $headText = __( - 'Please answer the following question(s) ' - . 'carefully to obtain a correct normalization.' - ); - $subText = __( - 'For each column below, ' - . 'please select the minimal set of columns among given set ' - . 'whose values combined together are sufficient' - . ' to determine the value of the column.
' - . 'Note: A column may have no transitive dependency, ' - . 'in that case you don\'t have to select any.' - ); - $cnt = 0; - foreach ($tables as $table) { - $primary = PhpMyAdmin\Index::getPrimary($table, $db); - $primarycols = $primary->getColumns(); - $selectTdForm = ""; - $pk = array(); - foreach ($primarycols as $col) { - $pk[] = $col->getName(); - } - $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); - $columns = (array) $GLOBALS['dbi']->getColumnNames( - $db, $table, $GLOBALS['userlink'] - ); - if (count($columns) - count($pk) <= 1) { - continue; - } - foreach ($columns as $column) { - if (!in_array($column, $pk)) { - $selectTdForm .= '' - . '' . htmlspecialchars($column) . ''; - } - } - foreach ($columns as $column) { - if (!in_array($column, $pk)) { - $cnt++; - $extra .= "" . sprintf( - __('\'%1$s\' depends on:'), htmlspecialchars($column) - ) - . "
"; - $extra .= '
' - . $selectTdForm - . '


'; - } - } - } - if ($extra == "") { - $headText = __( - "No Transitive dependencies possible as the table " - . "doesn't have any non primary key columns" - ); - $subText = ""; - $extra = "

" . __("Table is already in Third normal form!") . "

"; - } - $res = array( - 'legendText' => $legendText, - 'headText' => $headText, - 'subText' => $subText, - 'extra' => $extra - ); - return $res; -} -/** - * get html for options to normalize table - * - * @return string HTML - */ -function PMA_getHtmlForNormalizetable() -{ - $html_output = '
' - . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) - . ''; - $html_output .= '
'; - $html_output .= '' - . __('Improve table structure (Normalization):') . ''; - $html_output .= '

' . __('Select up to what step you want to normalize') - . '

'; - $choices = array( - '1nf' => __('First step of normalization (1NF)'), - '2nf' => __('Second step of normalization (1NF+2NF)'), - '3nf' => __('Third step of normalization (1NF+2NF+3NF)')); - - $html_output .= Util::getRadioFields( - 'normalizeTo', $choices, '1nf', true - ); - $html_output .= '
' - . "" . __( - 'Hint: Please follow the procedure carefully in order ' - . 'to obtain correct normalization' - ) . "" - . '' - . '
' - . '
' - . ''; - - return $html_output; -} - -/** - * find all the possible partial dependencies based on data in the table. - * - * @param string $table current table - * @param string $db current database - * - * @return string HTML containing the list of all the possible partial dependencies - */ -function PMA_findPartialDependencies($table, $db) -{ - $dependencyList = array(); - $GLOBALS['dbi']->selectDb($db, $GLOBALS['userlink']); - $columns = (array) $GLOBALS['dbi']->getColumnNames( - $db, $table, $GLOBALS['userlink'] - ); - $columns = (array)Util::backquote($columns); - $totalRowsRes = $GLOBALS['dbi']->fetchResult( - 'SELECT COUNT(*) FROM (SELECT * FROM ' - . Util::backquote($table) . ' LIMIT 500) as dt;' - ); - $totalRows = $totalRowsRes[0]; - $primary = PhpMyAdmin\Index::getPrimary($table, $db); - $primarycols = $primary->getColumns(); - $pk = array(); - foreach ($primarycols as $col) { - $pk[] = Util::backquote($col->getName()); - } - $partialKeys = PMA_getAllCombinationPartialKeys($pk); - $distinctValCount = PMA_findDistinctValuesCount( - array_unique( - array_merge($columns, $partialKeys) - ), $table - ); - foreach ($columns as $column) { - if (!in_array($column, $pk)) { - foreach ($partialKeys as $partialKey) { - if ($partialKey - && PMA_checkPartialDependency( - $partialKey, $column, $table, - $distinctValCount[$partialKey], - $distinctValCount[$column], $totalRows - ) - ) { - $dependencyList[$partialKey][] = $column; - } - } - } - } - - $html = __( - 'This list is based on a subset of the table\'s data ' - . 'and is not necessarily accurate. ' - ) - . '
'; - foreach ($dependencyList as $dependon=>$colList) { - $html .= '' - . '' - . '' - . htmlspecialchars(str_replace('`', '', $dependon)) . ' -> ' - . '' - . htmlspecialchars(str_replace('`', '', implode(', ', $colList))) - . '' - . ''; - } - if (empty($dependencyList)) { - $html .= '

' - . __('No partial dependencies found!') . '

'; - } - $html .= '
'; - return $html; -} -/** - * check whether a particular column is dependent on given subset of primary key - * - * @param string $partialKey the partial key, subset of primary key, - * each column in key supposed to be backquoted - * @param string $column backquoted column on whose dependency being checked - * @param string $table current table - * @param integer $pkCnt distinct value count for given partial key - * @param integer $colCnt distinct value count for given column - * @param integer $totalRows total distinct rows count of the table - * - * @return boolean TRUE if $column is dependent on $partialKey, False otherwise - */ -function PMA_checkPartialDependency( - $partialKey, $column, $table, $pkCnt, $colCnt, $totalRows -) { - $query = 'SELECT ' - . 'COUNT(DISTINCT ' . $partialKey . ',' . $column . ') as pkColCnt ' - . 'FROM (SELECT * FROM ' . Util::backquote($table) - . ' LIMIT 500) as dt' . ';'; - $res = $GLOBALS['dbi']->fetchResult($query, null, null, $GLOBALS['userlink']); - $pkColCnt = $res[0]; - if ($pkCnt && $pkCnt == $colCnt && $colCnt == $pkColCnt) { - return true; - } - if ($totalRows && $totalRows == $pkCnt) { - return true; - } - return false; -} - -/** - * function to get distinct values count of all the column in the array $columns - * - * @param array $columns array of backquoted columns whose distinct values - * need to be counted. - * @param string $table table to which these columns belong - * - * @return array associative array containing the count - */ -function PMA_findDistinctValuesCount($columns, $table) -{ - $result = array(); - $query = 'SELECT '; - foreach ($columns as $column) { - if ($column) { //each column is already backquoted - $query .= 'COUNT(DISTINCT ' . $column . ') as \'' - . $column . '_cnt\', '; - } - } - $query = trim($query, ', '); - $query .= ' FROM (SELECT * FROM ' . Util::backquote($table) - . ' LIMIT 500) as dt' . ';'; - $res = $GLOBALS['dbi']->fetchResult($query, null, null, $GLOBALS['userlink']); - foreach ($columns as $column) { - if ($column) { - $result[$column] = $res[0][$column . '_cnt']; - } - } - return $result; -} - -/** - * find all the possible partial keys - * - * @param array $primaryKey array containing all the column present in primary key - * - * @return array containing all the possible partial keys(subset of primary key) - */ -function PMA_getAllCombinationPartialKeys($primaryKey) -{ - $results = array(''); - foreach ($primaryKey as $element) { - foreach ($results as $combination) { - array_push( - $results, trim($element . ',' . $combination, ',') - ); - } - } - array_pop($results); //remove key which consist of all primary key columns - return $results; -} diff --git a/normalization.php b/normalization.php index 14d2137120..ed902137fa 100644 --- a/normalization.php +++ b/normalization.php @@ -7,20 +7,20 @@ */ use PhpMyAdmin\Core; -use PhpMyAdmin\Url; +use PhpMyAdmin\Normalization; use PhpMyAdmin\Response; +use PhpMyAdmin\Url; /** * */ require_once 'libraries/common.inc.php'; -require_once 'libraries/normalization.lib.php'; if (isset($_REQUEST['getColumns'])) { $html = '' . ''; //get column whose datatype falls under string category - $html .= PMA_getHtmlForColumnsList( + $html .= Normalization::getHtmlForColumnsList( $db, $table, _pgettext('string types', 'String') @@ -30,7 +30,7 @@ if (isset($_REQUEST['getColumns'])) { } if (isset($_REQUEST['splitColumn'])) { $num_fields = min(4096, intval($_REQUEST['numFields'])); - $html = PMA_getHtmlForCreateNewColumn($num_fields, $db, $table); + $html = Normalization::getHtmlForCreateNewColumn($num_fields, $db, $table); $html .= Url::getHiddenInputs($db, $table); echo $html; exit; @@ -38,7 +38,7 @@ if (isset($_REQUEST['splitColumn'])) { if (isset($_REQUEST['addNewPrimary'])) { $num_fields = 1; $columnMeta = array('Field'=>$table . "_id", 'Extra'=>'auto_increment'); - $html = PMA_getHtmlForCreateNewColumn( + $html = Normalization::getHtmlForCreateNewColumn( $num_fields, $db, $table, $columnMeta ); $html .= Url::getHiddenInputs($db, $table); @@ -46,14 +46,14 @@ if (isset($_REQUEST['addNewPrimary'])) { exit; } if (isset($_REQUEST['findPdl'])) { - $html = PMA_findPartialDependencies($table, $db); + $html = Normalization::findPartialDependencies($table, $db); echo $html; exit; } if (isset($_REQUEST['getNewTables2NF'])) { $partialDependencies = json_decode($_REQUEST['pd']); - $html = PMA_getHtmlForNewTables2NF($partialDependencies, $table); + $html = Normalization::getHtmlForNewTables2NF($partialDependencies, $table); echo $html; exit; } @@ -63,7 +63,7 @@ $response = Response::getInstance(); if (isset($_REQUEST['getNewTables3NF'])) { $dependencies = json_decode($_REQUEST['pd']); $tables = json_decode($_REQUEST['tables']); - $newTables = PMA_getHtmlForNewTables3NF($dependencies, $tables, $db); + $newTables = Normalization::getHtmlForNewTables3NF($dependencies, $tables, $db); $response->disable(); Core::headerJSON(); echo json_encode($newTables); @@ -81,13 +81,13 @@ if (Core::isValid($_REQUEST['normalizeTo'], array('1nf', '2nf', '3nf'))) { if (isset($_REQUEST['createNewTables2NF'])) { $partialDependencies = json_decode($_REQUEST['pd']); $tablesName = json_decode($_REQUEST['newTablesName']); - $res = PMA_createNewTablesFor2NF($partialDependencies, $tablesName, $table, $db); + $res = Normalization::createNewTablesFor2NF($partialDependencies, $tablesName, $table, $db); $response->addJSON($res); exit; } if (isset($_REQUEST['createNewTables3NF'])) { $newtables = json_decode($_REQUEST['newTables']); - $res = PMA_createNewTablesFor3NF($newtables, $db); + $res = Normalization::createNewTablesFor3NF($newtables, $db); $response->addJSON($res); exit; } @@ -96,31 +96,31 @@ if (isset($_POST['repeatingColumns'])) { $newTable = $_POST['newTable']; $newColumn = $_POST['newColumn']; $primary_columns = $_POST['primary_columns']; - $res = PMA_moveRepeatingGroup( + $res = Normalization::moveRepeatingGroup( $repeatingColumns, $primary_columns, $newTable, $newColumn, $table, $db ); $response->addJSON($res); exit; } if (isset($_REQUEST['step1'])) { - $html = PMA_getHtmlFor1NFStep1($db, $table, $normalForm); + $html = Normalization::getHtmlFor1NFStep1($db, $table, $normalForm); $response->addHTML($html); } else if (isset($_REQUEST['step2'])) { - $res = PMA_getHtmlContentsFor1NFStep2($db, $table); + $res = Normalization::getHtmlContentsFor1NFStep2($db, $table); $response->addJSON($res); } else if (isset($_REQUEST['step3'])) { - $res = PMA_getHtmlContentsFor1NFStep3($db, $table); + $res = Normalization::getHtmlContentsFor1NFStep3($db, $table); $response->addJSON($res); } else if (isset($_REQUEST['step4'])) { - $res = PMA_getHtmlContentsFor1NFStep4($db, $table); + $res = Normalization::getHtmlContentsFor1NFStep4($db, $table); $response->addJSON($res); } else if (isset($_REQUEST['step']) && $_REQUEST['step'] == '2.1') { - $res = PMA_getHtmlFor2NFstep1($db, $table); + $res = Normalization::getHtmlFor2NFstep1($db, $table); $response->addJSON($res); } else if (isset($_REQUEST['step']) && $_REQUEST['step'] == '3.1') { $tables = $_REQUEST['tables']; - $res = PMA_getHtmlFor3NFstep1($db, $tables); + $res = Normalization::getHtmlFor3NFstep1($db, $tables); $response->addJSON($res); } else { - $response->addHTML(PMA_getHtmlForNormalizetable()); + $response->addHTML(Normalization::getHtmlForNormalizetable()); } diff --git a/test/libraries/PMA_normalization_test.php b/test/classes/NormalizationTest.php similarity index 82% rename from test/libraries/PMA_normalization_test.php rename to test/classes/NormalizationTest.php index ca983b64e6..5bf5960c3f 100644 --- a/test/libraries/PMA_normalization_test.php +++ b/test/classes/NormalizationTest.php @@ -1,28 +1,28 @@ assertContains( '', - PMA_getHtmlForColumnsList($table, $db) + Normalization::getHtmlForColumnsList($table, $db) ); $this->assertEquals( 'col1 [ varchar(100) ]
', - PMA_getHtmlForColumnsList($table, $db, 'String', 'checkbox') + Normalization::getHtmlForColumnsList($table, $db, 'String', 'checkbox') ); } /** - * Test for PMA_getHtmlForCreateNewColumn + * Test for Normalization::getHtmlForCreateNewColumn * * @return void */ @@ -121,7 +121,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase $db = "PMA_db"; $table= "PMA_table"; $num_fields = 1; - $result = PMA_getHtmlForCreateNewColumn($num_fields, $db, $table); + $result = Normalization::getHtmlForCreateNewColumn($num_fields, $db, $table); $this->assertContains( 'assertContains( "

" . __('First step of normalization (1NF)') . "

", @@ -166,7 +166,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase ); $this->assertContains( - PMA_getHtmlForColumnsList( + Normalization::getHtmlForColumnsList( $db, $table, _pgettext('string types', 'String') ), $result ); @@ -174,7 +174,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase } /** - * Test for PMA_getHtmlContentsFor1NFStep2 + * Test for Normalization::getHtmlContentsFor1NFStep2 * * @return void */ @@ -182,7 +182,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase { $db = "PMA_db"; $table= "PMA_table1"; - $result = PMA_getHtmlContentsFor1NFStep2($db, $table); + $result = Normalization::getHtmlContentsFor1NFStep2($db, $table); $this->assertInternalType('array', $result); $this->assertArrayHasKey('legendText', $result); $this->assertArrayHasKey('headText', $result); @@ -199,12 +199,12 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase ); $this->assertEquals('0', $result['hasPrimaryKey']); $this->assertContains(__('Step 1.') . 2, $result['legendText']); - $result1 = PMA_getHtmlContentsFor1NFStep2($db, 'PMA_table'); + $result1 = Normalization::getHtmlContentsFor1NFStep2($db, 'PMA_table'); $this->assertEquals('1', $result1['hasPrimaryKey']); } /** - * Test for PMA_getHtmlContentsFor1NFStep4 + * Test for Normalization::getHtmlContentsFor1NFStep4 * * @return void */ @@ -212,7 +212,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase { $db = "PMA_db"; $table= "PMA_table"; - $result = PMA_getHtmlContentsFor1NFStep4($db, $table); + $result = Normalization::getHtmlContentsFor1NFStep4($db, $table); $this->assertInternalType('array', $result); $this->assertArrayHasKey('legendText', $result); $this->assertArrayHasKey('headText', $result); @@ -220,7 +220,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('extra', $result); $this->assertContains(__('Step 1.') . 4, $result['legendText']); $this->assertContains( - PMA_getHtmlForColumnsList($db, $table, 'all', "checkbox"), + Normalization::getHtmlForColumnsList($db, $table, 'all', "checkbox"), $result['extra'] ); $this->assertContains( @@ -230,7 +230,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase } /** - * Test for PMA_getHtmlContentsFor1NFStep3 + * Test for Normalization::getHtmlContentsFor1NFStep3 * * @return void */ @@ -238,7 +238,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase { $db = "PMA_db"; $table= "PMA_table"; - $result = PMA_getHtmlContentsFor1NFStep3($db, $table); + $result = Normalization::getHtmlContentsFor1NFStep3($db, $table); $this->assertInternalType('array', $result); $this->assertArrayHasKey('legendText', $result); $this->assertArrayHasKey('headText', $result); @@ -247,7 +247,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('primary_key', $result); $this->assertContains(__('Step 1.') . 3, $result['legendText']); $this->assertContains( - PMA_getHtmlForColumnsList($db, $table, 'all', "checkbox"), + Normalization::getHtmlForColumnsList($db, $table, 'all', "checkbox"), $result['extra'] ); $this->assertContains( @@ -258,7 +258,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase } /** - * Test for PMA_getHtmlFor2NFstep1 + * Test for Normalization::getHtmlFor2NFstep1 * * @return void */ @@ -266,7 +266,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase { $db = "PMA_db"; $table= "PMA_table"; - $result = PMA_getHtmlFor2NFstep1($db, $table); + $result = Normalization::getHtmlFor2NFstep1($db, $table); $this->assertInternalType('array', $result); $this->assertArrayHasKey('legendText', $result); $this->assertArrayHasKey('headText', $result); @@ -275,7 +275,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('primary_key', $result); $this->assertContains(__('Step 2.') . 1, $result['legendText']); $this->assertEquals('id', $result['primary_key']); - $result1 = PMA_getHtmlFor2NFstep1($db, "PMA_table2"); + $result1 = Normalization::getHtmlFor2NFstep1($db, "PMA_table2"); $this->assertEquals('id, col1', $result1['primary_key']); $this->assertContains( 'array('col2')); - $result = PMA_getHtmlForNewTables2NF($partialDependencies, $table); + $result = Normalization::getHtmlForNewTables2NF($partialDependencies, $table); $this->assertContains( 'id = 'PMA_table'; $tablesName->col1 = 'PMA_table1'; $partialDependencies = array('id'=>array('col2')); - $result = PMA_createNewTablesFor2NF( + $result = Normalization::createNewTablesFor2NF( $partialDependencies, $tablesName, $table, $db ); $this->assertInternalType('array', $result); @@ -324,7 +324,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('headText', $result); $this->assertArrayHasKey('queryError', $result); $partialDependencies = array('id'=>array('col2'), 'col1'=>array('col2')); - $result1 = PMA_createNewTablesFor2NF( + $result1 = Normalization::createNewTablesFor2NF( $partialDependencies, $tablesName, $table, $db ); $this->assertArrayHasKey('extra', $result1); @@ -333,7 +333,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase } /** - * Test for PMA_getHtmlForNewTables3NF + * Test for Normalization::getHtmlForNewTables3NF * * @return void */ @@ -343,7 +343,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase $db = 'PMA_db'; $dependencies = new stdClass(); $dependencies->col1 = array('col2'); - $result = PMA_getHtmlForNewTables3NF($dependencies, $tables, $db); + $result = Normalization::getHtmlForNewTables3NF($dependencies, $tables, $db); $this->assertEquals( array( 'html' => '', @@ -353,7 +353,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase ); $tables= array("PMA_table"=>array('col1', 'PMA_table')); $dependencies->PMA_table = array('col4', 'col5'); - $result1 = PMA_getHtmlForNewTables3NF($dependencies, $tables, $db); + $result1 = Normalization::getHtmlForNewTables3NF($dependencies, $tables, $db); $this->assertInternalType('array', $result1); $this->assertContains( 'pk = 'col2'; $cols1->nonpk = 'col3, col4'; $newTables = array('PMA_table'=>array('PMA_table'=>$cols, 'table1'=>$cols1)); - $result = PMA_createNewTablesFor3NF( + $result = Normalization::createNewTablesFor3NF( $newTables, $db ); $this->assertInternalType('array', $result); @@ -398,7 +398,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('headText', $result); $this->assertArrayHasKey('queryError', $result); $newTables1 = array(); - $result1 = PMA_createNewTablesFor3NF( + $result1 = Normalization::createNewTablesFor3NF( $newTables1, $db ); $this->assertArrayHasKey('queryError', $result1); @@ -407,7 +407,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase } /** - * Test for PMA_moveRepeatingGroup + * Test for Normalization::moveRepeatingGroup * * @return void */ @@ -419,7 +419,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase $newColumn = 'PMA_newCol'; $table= "PMA_table"; $db = 'PMA_db'; - $result = PMA_moveRepeatingGroup( + $result = Normalization::moveRepeatingGroup( $repeatingColumns, $primary_columns, $newTable, $newColumn, $table, $db ); $this->assertInternalType('array', $result); @@ -431,7 +431,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase } /** - * Test for PMA_getHtmlFor3NFstep1 + * Test for Normalization::getHtmlFor3NFstep1 * * @return void */ @@ -439,7 +439,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase { $db = "PMA_db"; $tables= array("PMA_table"); - $result = PMA_getHtmlFor3NFstep1($db, $tables); + $result = Normalization::getHtmlFor3NFstep1($db, $tables); $this->assertInternalType('array', $result); $this->assertArrayHasKey('legendText', $result); $this->assertArrayHasKey('headText', $result); @@ -454,20 +454,20 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase 'assertEquals( '', $result1['subText'] ); } /** - * Test for PMA_getHtmlForNormalizetable + * Test for Normalization::getHtmlForNormalizetable * * @return void */ public function testPMAGetHtmlForNormalizetable() { - $result = PMA_getHtmlForNormalizetable(); + $result = Normalization::getHtmlForNormalizetable(); $this->assertContains( '
__('Second step of normalization (1NF+2NF)'), '3nf' => __('Third step of normalization (1NF+2NF+3NF)')); - $html_tmp = PhpMyAdmin\Util::getRadioFields( + $html_tmp = Util::getRadioFields( 'normalizeTo', $choices, '1nf', true ); $this->assertContains($html_tmp, $result); } /** - * Test for PMA_findPartialDependencies + * Test for Normalization::findPartialDependencies * * @return void */ @@ -496,7 +496,7 @@ class PMA_Normalization_Test extends PHPUnit_Framework_TestCase { $table= "PMA_table2"; $db = 'PMA_db'; - $result = PMA_findPartialDependencies($table, $db); + $result = Normalization::findPartialDependencies($table, $db); $this->assertContains( '
assertEquals( array('', 'id', 'col1', 'col1,id', 'col2', 'col2,id', 'col2,col1'), $result