diff --git a/js/messages.php b/js/messages.php index 1d3e5228c8..b2a9a40df5 100644 --- a/js/messages.php +++ b/js/messages.php @@ -326,6 +326,10 @@ $js_messages['strMoveRepeatingGroup'] = '
    ' . __('The following actions wi . '
  1. ' . __('Create the following table') . '
  2. '; $js_messages['strNewTablePlaceholder'] = 'Enter new table name'; $js_messages['strNewColumnPlaceholder'] = 'Enter column name'; +$js_messages['str3NFNormalization'] = __('Third step of normalization (3NF)'); +$js_messages['strConfirmTd'] = __('Confirm transitive dependencies'); +$js_messages['strSelectedTd'] = __('Selected dependencies are as follows:'); +$js_messages['strNoTdSelected'] = __('No dependencies selected!'); /* For server_variables.js */ $js_messages['strSave'] = __('Save'); diff --git a/js/normalization.js b/js/normalization.js index e0b1a730f8..ebe7e289a8 100644 --- a/js/normalization.js +++ b/js/normalization.js @@ -31,7 +31,39 @@ function appendHtmlColumnsList() } ); } - +function goTo3NFStep1(newTables) +{ + if (Object.keys(newTables).length === 1) { + newTables = [PMA_commonParams.get('table')]; + } + $.post( + "normalization.php", + { + "token": PMA_commonParams.get('token'), + "ajax_request": true, + "db": PMA_commonParams.get('db'), + "tables": newTables, + "step": '3.1' + }, function(data) { + $("#page_content h3").html(PMA_messages.str3NFNormalization); + $("#mainContent legend").html(data.legendText); + $("#mainContent h4").html(data.headText); + $("#mainContent p").html(data.subText); + $("#mainContent #extra").html(data.extra); + $("#extra form").each(function() { + form_id = $(this).attr('id'); + colname = $(this).data('colname'); + $("#"+form_id+" input[value='"+colname+"']").next().remove(); + $("#"+form_id+" input[value='"+colname+"']").remove(); + }); + $("#mainContent #newCols").html(''); + $('.tblFooters').html(''); + if (data.subText !== "") { + $('.tblFooters').html(''); + } + } + ); +} function goTo2NFStep1() { $.post( "normalization.php", @@ -49,7 +81,14 @@ function goTo2NFStep1() { $("#mainContent #extra").html(data.extra); $("#mainContent #newCols").html(''); if (data.subText !== '') { - $('.tblFooters').html(''); + $('.tblFooters').html(''); + } else { + if (normalizeto === '3nf') { + $("#mainContent #newCols").html(PMA_messages.strToNextStep); + setTimeout(function() { + goTo3NFStep1([PMA_commonParams.get('table')]); + }, 3000); + } } }); } @@ -154,6 +193,7 @@ function goToStep2(extra) } ); } + function goTo2NFFinish(pd) { var tables = {}; @@ -167,6 +207,51 @@ function goTo2NFFinish(pd) "pd": JSON.stringify(pd), "newTablesName":JSON.stringify(tables), "createNewTables2NF":1}; + $.ajax({ + type: "GET", + url: "normalization.php", + data: datastring, + async:false, + success: function(data) { + if (data.success === true) { + if(data.queryError === false) { + if (normalizeto === '3nf') { + $("#pma_navigation_reload").click(); + goTo3NFStep1(tables); + return true; + } + $("#mainContent legend").html(data.legendText); + $("#mainContent h4").html(data.headText); + $("#mainContent p").html(''); + $("#mainContent #extra").html(''); + $('.tblFooters').html(''); + } else { + PMA_ajaxShowMessage(data.extra, false); + } + $("#pma_navigation_reload").click(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} + +function goTo3NFFinish(newTables) +{ + for (var table in newTables) { + for (var newtbl in newTables[table]) { + updatedname = $('#extra input[name="'+newtbl+'"]').val(); + newTables[table][updatedname] = newTables[table][newtbl]; + if (updatedname !== newtbl) { + delete newTables[table][newtbl]; + } + } + } + datastring = {"token": PMA_commonParams.get('token'), + "ajax_request": true, + "db": PMA_commonParams.get('db'), + "newTables":JSON.stringify(newTables), + "createNewTables3NF":1}; $.ajax({ type: "GET", url: "normalization.php", @@ -202,7 +287,7 @@ function goTo2NFStep2(pd, primary_key) for (var dependson in pd) { if (dependson !== primary_key) { pdFound = true; - extra += '

    '+dependson +" -> "+pd[dependson].toString()+'

    '; + extra += '

    '+escapeHtml(dependson) +" -> "+escapeHtml(pd[dependson].toString())+'

    '; } } if(!pdFound) { @@ -237,12 +322,74 @@ function goTo2NFStep2(pd, primary_key) }); } -function processPartialDependancies(primary_key) +function goTo3NFStep2(pd, tablesTds) +{ + $("#newCols").html(''); + $("#mainContent legend").html(PMA_messages.strStep+' 3.2 '+PMA_messages.strConfirmTd); + $("#mainContent h4").html(PMA_messages.strSelectedTd); + $("#mainContent p").html(PMA_messages.strPdHintNote); + var extra = '
    '; + var pdFound = false; + for (var table in tablesTds) { + for (var i in tablesTds[table]) { + dependson = tablesTds[table][i]; + if (dependson !== '' && dependson !== table) { + pdFound = true; + extra += '

    '+escapeHtml(dependson) +" -> "+escapeHtml(pd[dependson].toString())+'

    '; + } + } + } + if(!pdFound) { + extra += '

    '+PMA_messages.strNoTdSelected+'

    '; + extra += '
    '; + } else { + extra += ''; + datastring = {"token": PMA_commonParams.get('token'), + "ajax_request": true, + "db": PMA_commonParams.get('db'), + "tables": JSON.stringify(tablesTds), + "pd": JSON.stringify(pd), + "getNewTables3NF":1}; + $.ajax({ + type: "GET", + url: "normalization.php", + data: datastring, + async:false, + success: function(data) { + data_parsed = $.parseJSON(data.message); + if (data.success === true) { + extra += data_parsed.html; + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); + } + $("#mainContent #extra").html(extra); + $('.tblFooters').html(''); + $("#goTo3NFFinish").click(function(){ + if (!pdFound) { + goTo3NFFinish([]); + } else { + goTo3NFFinish(data_parsed.newTables); + } + }); +} +function processDependencies(primary_key, isTransitive) { var pd = {}; + var tablesTds = {}; var dependsOn; pd[primary_key] = []; $("#extra form").each(function() { + if (isTransitive === true) { + tblname = $(this).data('tablename'); + primary_key = tblname; + if (!(tblname in tablesTds)) { + tablesTds[tblname] = []; + } + tablesTds[tblname].push(primary_key); + } form_id = $(this).attr('id'); $('#'+form_id+' input[type=checkbox]:not(:checked)').removeAttr('checked'); dependsOn = ''; @@ -251,9 +398,7 @@ function processPartialDependancies(primary_key) $(this).attr("checked","checked"); }); if (dependsOn === '') { - $('#'+form_id+' input[type=checkbox]').each(function(){ - dependsOn = primary_key; - }); + dependsOn = primary_key; } else { dependsOn = dependsOn.slice(0, -2); } @@ -261,11 +406,24 @@ function processPartialDependancies(primary_key) pd[dependsOn] = []; } pd[dependsOn].push($(this).data('colname')); + if (isTransitive === true) { + if (!(tblname in tablesTds)) { + tablesTds[tblname] = []; + } + if ($.inArray(dependsOn, tablesTds[tblname]) === -1) { + tablesTds[tblname].push(dependsOn); + } + } }); backup = $("#mainContent").html(); - goTo2NFStep2(pd, primary_key); + if (isTransitive === true) { + goTo3NFStep2(pd, tablesTds); + } else { + goTo2NFStep2(pd, primary_key); + } return false; } + function moveRepeatingGroup(repeatingCols) { newTable = $("input[name=repeatGroupTable]").val(); newColumn = $("input[name=repeatGroupColumn]").val(); diff --git a/libraries/normalization.lib.php b/libraries/normalization.lib.php index 8bc15db29b..b657322fa4 100644 --- a/libraries/normalization.lib.php +++ b/libraries/normalization.lib.php @@ -337,8 +337,9 @@ function PMA_getHtmlFor2NFstep1($db, $table) foreach ($columns as $column) { if (!in_array($column, $pk)) { $cnt++; - $extra .= "'" . htmlspecialchars($column) - . "' depends on:
    "; + $extra .= "" . sprintf( + __('\'%1$s\' depends on:'), htmlspecialchars($column) + ) . "
    "; $extra .= '
    ' . $selectPkForm . '


    '; @@ -376,7 +377,7 @@ function PMA_getHtmlForNewTables2NF($partialDependencies,$table) 'As per above partial dependencies, in order to put the ' . 'original table \'%1$s\' into Second normal form we need ' . 'to create the following tables:' - ), $table + ), htmlspecialchars($table) ) . '

    '; $tableName = $table; $i=1; @@ -466,6 +467,148 @@ function PMA_createNewTablesFor2NF($partialDependencies, $tablesName, $table, $d ); } +/** + * build the html for showing the new tables to have in order + * to put given tables in 3NF + * + * @param array $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 = PMA_Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = array(); + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $html .= '

    ' . sprintf( + __( + 'As per above dependencies, 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); +} + +/** + * 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( + ', ', PMA_Util::backquote(explode(', ', $cols->pk)) + ); + $quotedNonpk = implode( + ', ', PMA_Util::backquote(explode(', ', $cols->nonpk)) + ); + $queries[] = 'CREATE TABLE ' . PMA_Util::backquote($table) + . ' SELECT DISTINCT ' . $quotedPk + . ', ' . $quotedNonpk + . ' FROM ' . PMA_Util::backquote($originalTable) . ';'; + $queries[] = 'ALTER TABLE ' . PMA_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 ' . PMA_Util::backquote($originalTable); + foreach ($columns as $col) { + if (!in_array($col, $colPresent)) { + $query .= ' DROP ' . PMA_Util::backquote($col) . ','; + } + } + $query = trim($query, ', '); + $query .= ';'; + $queries[] = $query; + } else { + $queries[] = 'DROP TABLE ' . PMA_Util::backquote($originalTable); + } + $dropCols = false; + } + foreach ($queries as $query) { + if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['userlink'])) { + $message = PMA_Message::error(__('Error in processing!')); + $message->addMessage('

    '); + $message->addMessage( + PMA_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 * @@ -528,6 +671,83 @@ function PMA_moveRepeatingGroup( '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 of the 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 $key=>$table) { + $primary = PMA_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 * diff --git a/normalization.php b/normalization.php index 86dd19c9d2..c7f7229013 100644 --- a/normalization.php +++ b/normalization.php @@ -57,6 +57,13 @@ if (isset($_REQUEST['getNewTables2NF'])) { exit; } +if (isset($_REQUEST['getNewTables3NF'])) { + $dependencies = json_decode($_REQUEST['pd']); + $tables = json_decode($_REQUEST['tables']); + $newTables = PMA_getHtmlForNewTables3NF($dependencies, $tables, $db); + echo json_encode($newTables); + exit; +} $response = PMA_Response::getInstance(); $header = $response->getHeader(); @@ -66,16 +73,6 @@ $scripts->addFile('jquery/jquery.uitablefilter.js'); $normalForm = '1nf'; if (isset($_REQUEST['normalizeTo'])) { $normalForm = $_REQUEST['normalizeTo']; - if ($normalForm == '3nf') { - $response->addHTML( - '

    ' - . __('Second/Third step of normalization') . '

    ' - . '
    ' - . 'Coming soon...' - . 'Wait is worth it :-)
    ' - ); - exit; - } } if (isset($_REQUEST['createNewTables2NF'])) { $partialDependencies = json_decode($_REQUEST['pd']); @@ -84,6 +81,12 @@ if (isset($_REQUEST['createNewTables2NF'])) { $response->addJSON($res); exit; } +if (isset($_REQUEST['createNewTables3NF'])) { + $newtables = json_decode($_REQUEST['newTables']); + $res = PMA_createNewTablesFor3NF($newtables, $db); + $response->addJSON($res); + exit; +} if (isset($_POST['repeatingColumns'])) { $repeatingColumns = $_POST['repeatingColumns']; $newTable = $_POST['newTable']; @@ -110,6 +113,10 @@ if (isset($_REQUEST['step1'])) { } else if (isset($_REQUEST['step']) && $_REQUEST['step'] == 2.1) { $res = PMA_getHtmlFor2NFstep1($db, $table); $response->addJSON($res); +} else if (isset($_REQUEST['step']) && $_REQUEST['step'] == 3.1) { + $tables = $_REQUEST['tables']; + $res = PMA_getHtmlFor3NFstep1($db, $tables); + $response->addJSON($res); } else { $response->addHTML(PMA_getHtmlForNormalizetable()); }