diff --git a/ChangeLog b/ChangeLog index 4f1bcdb10f..0f3759dd9c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -54,6 +54,7 @@ phpMyAdmin - ChangeLog + rfe #1634 Don't group tables in tree if the result has only one group - bug #4946 When hide table structure actions is false, action should be in a row + rfe #726 Batch changing the collation of each column in a table +- bug #4510 QBE generates wrong query + rfe #1594 Use plain English descriptors instead of script names for icon link destinations + rfe #1541 Disable foreign key checks for some operations diff --git a/libraries/DBQbe.class.php b/libraries/DBQbe.class.php index 3873817920..8efe43b66c 100644 --- a/libraries/DBQbe.class.php +++ b/libraries/DBQbe.class.php @@ -128,6 +128,13 @@ class PMA_DbQbe * @var array */ private $_curField; + /** + * Current alias + * + * @access private + * @var array + */ + private $_curAlias; /** * Current criteria Sort options * @@ -135,6 +142,13 @@ class PMA_DbQbe * @var array */ private $_curSort; + /** + * Current criteria sort order + * + * @access private + * @var array + */ + private $_curSortOrder; /** * Current criteria Show options * @@ -398,6 +412,50 @@ class PMA_DbQbe return $html_output; } + /** + * Provides select options list containing sort order + * + * @param integer $columnNumber Column Number (0,1,2) or more + * @param integer $sortOrder Sort order + * + * @return string HTML for select options + */ + private function _getSortOrderSelectCell($columnNumber, $sortOrder) + { + $totalColumnCount = $this->_getNewColumnCount(); + $html_output = ''; + $html_output .= ''; + $html_output .= ''; + return $html_output; + } + + /** + * Returns the new column count after adding and removing columns as instructed + * + * @return int new column count + */ + private function _getNewColumnCount() + { + $totalColumnCount = $this->_criteria_column_count; + if (! empty($this->_criteriaColumnInsert)) { + $totalColumnCount += count($this->_criteriaColumnInsert); + } + if (! empty($this->_criteriaColumnDelete)) { + $totalColumnCount -= count($this->_criteriaColumnDelete); + } + return $totalColumnCount; + } + /** * Provides search form's row containing column select options * @@ -444,6 +502,59 @@ class PMA_DbQbe return $html_output; } + /** + * Provides search form's row containing column aliases + * + * @return string HTML for search table's row + */ + private function _getColumnAliasRow() + { + $html_output = ''; + $html_output .= '' . __('Alias:') . ''; + $new_column_count = 0; + + for ( + $colInd = 0; + $colInd < $this->_criteria_column_count; + $colInd++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$colInd]) + && $this->_criteriaColumnInsert[$colInd] == 'on' + ) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$colInd]) + && $this->_criteriaColumnDelete[$colInd] == 'on' + ) { + continue; + } + + $tmp_alias = ''; + if (! empty($_REQUEST['criteriaAlias'][$colInd])) { + $tmp_alias + = $this->_curAlias[$new_column_count] + = $_REQUEST['criteriaAlias'][$colInd]; + }// end if + + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + /** * Provides search form's row containing sort(ASC/DESC) select options * @@ -482,26 +593,23 @@ class PMA_DbQbe ) { $_REQUEST['criteriaSort'][$colInd] = ''; } //end if - // Set asc_selected - if (isset($_REQUEST['criteriaSort'][$colInd]) - && $_REQUEST['criteriaSort'][$colInd] == 'ASC' - ) { + + $asc_selected = ''; $desc_selected = ''; + if (isset($_REQUEST['criteriaSort'][$colInd])) { $this->_curSort[$new_column_count] = $_REQUEST['criteriaSort'][$colInd]; - $asc_selected = ' selected="selected"'; + // Set asc_selected + if ($_REQUEST['criteriaSort'][$colInd] == 'ASC') { + $asc_selected = ' selected="selected"'; + } // end if + // Set desc selected + if ($_REQUEST['criteriaSort'][$colInd] == 'DESC') { + $desc_selected = ' selected="selected"'; + } // end if } else { - $asc_selected = ''; - } // end if - // Set desc selected - if (isset($_REQUEST['criteriaSort'][$colInd]) - && $_REQUEST['criteriaSort'][$colInd] == 'DESC' - ) { - $this->_curSort[$new_column_count] - = $_REQUEST['criteriaSort'][$colInd]; - $desc_selected = ' selected="selected"'; - } else { - $desc_selected = ''; - } // end if + $this->_curSort[$new_column_count] = ''; + } + $html_output .= $this->_getSortSelectCell( $new_column_count, $asc_selected, $desc_selected ); @@ -511,6 +619,55 @@ class PMA_DbQbe return $html_output; } + /** + * Provides search form's row containing sort order + * + * @return string HTML for search table's row + */ + private function _getSortOrder() + { + $html_output = ''; + $html_output .= '' . __('Sort order:') . ''; + $new_column_count = 0; + + for ( + $colInd = 0; + $colInd < $this->_criteria_column_count; + $colInd++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$colInd]) + && $this->_criteriaColumnInsert[$colInd] == 'on' + ) { + $html_output .= $this->_getSortOrderSelectCell( + $new_column_count, null + ); + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$colInd]) + && $this->_criteriaColumnDelete[$colInd] == 'on' + ) { + continue; + } + + $sortOrder = null; + if (! empty($_REQUEST['criteriaSortOrder'][$colInd])) { + $sortOrder + = $this->_curSortOrder[$new_column_count] + = $_REQUEST['criteriaSortOrder'][$colInd]; + } + + $html_output .= $this->_getSortOrderSelectCell( + $new_column_count, $sortOrder + ); + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + /** * Provides search form's row containing SHOW checkboxes * @@ -708,20 +865,24 @@ class PMA_DbQbe * * @param integer $column_number Column Number (0,1,2) or more * @param array $selected Selected criteria column name + * @param bool $last_column Whether this is the last column * * @return string HTML for modification cell */ - private function _getAndOrColCell($column_number, $selected = null) - { + private function _getAndOrColCell( + $column_number, $selected = null, $last_column = false + ) { $html_output = ''; - $html_output .= '' . __('Or:') . ''; - $html_output .= ''; - $html_output .= '  ' . __('And:') . ''; - $html_output .= ''; + if (! $last_column) { + $html_output .= '' . __('Or:') . ''; + $html_output .= ''; + $html_output .= '  ' . __('And:') . ''; + $html_output .= ''; + } $html_output .= '
' . __('Ins'); $html_output .= ''; @@ -779,7 +940,8 @@ class PMA_DbQbe } $html_output .= $this->_getAndOrColCell( $new_column_count, - $checked_options + $checked_options, + ($column_index + 1 == $this->_criteria_column_count) ); $new_column_count++; } // end for @@ -978,7 +1140,12 @@ class PMA_DbQbe && isset($this->_curShow[$column_index]) && $this->_curShow[$column_index] == 'on' ) { - $select_clauses[] = $this->_curField[$column_index]; + $select = $this->_curField[$column_index]; + if (! empty($this->_curAlias[$column_index])) { + $select .= " AS " + . PMA_Util::backquote($this->_curAlias[$column_index]); + } + $select_clauses[] = $select; } } // end for if ($select_clauses) { @@ -1092,6 +1259,18 @@ class PMA_DbQbe $orderby_clause = ''; $orderby_clauses = array(); + // Create copy of instance variables + $field = $this->_curField; + $sort = $this->_curSort; + $sortOrder = $this->_curSortOrder; + if ($sortOrder + && count($sortOrder) == count($sort) + && count($sortOrder) == count($field) + ) { + // Sort all three arrays based on sort order + array_multisort($sortOrder, $sort, $field); + } + for ( $column_index = 0; $column_index < $this->_criteria_column_count; @@ -1100,19 +1279,19 @@ class PMA_DbQbe // if all columns are chosen with * selector, // then sorting isn't available // Fix for Bug #570698 - if (empty($this->_curField[$column_index]) - && empty($this->_curSort[$column_index]) + if (empty($field[$column_index]) + && empty($sort[$column_index]) ) { continue; } - if (/*overload*/mb_substr($this->_curField[$column_index], -2) == '.*') { + if (/*overload*/mb_substr($field[$column_index], -2) == '.*') { continue; } - if (! empty($this->_curSort[$column_index])) { - $orderby_clauses[] = $this->_curField[$column_index] . ' ' - . $this->_curSort[$column_index]; + if (! empty($sort[$column_index])) { + $orderby_clauses[] = $field[$column_index] . ' ' + . $sort[$column_index]; } } // end for if ($orderby_clauses) { @@ -1371,30 +1550,139 @@ class PMA_DbQbe } // end while // Create LEFT JOINS out of Relations - if (count($all_tables) > 0) { - // Get tables and columns with valid where clauses - $valid_where_clauses = $this->_getWhereClauseTablesAndColumns(); - $where_clause_tables = $valid_where_clauses['where_clause_tables']; - $where_clause_columns = $valid_where_clauses['where_clause_columns']; - // Get master table - $master = $this->_getMasterTable( - $all_tables, $all_columns, - $where_clause_columns, $where_clause_tables - ); - $from_clause = PMA_Util::backquote($master) - . PMA_getRelatives($all_tables, $master); + $from_clause = $this->_getJoinForFromClause($all_tables, $all_columns); - } // end if (count($all_tables) > 0) + // In case relations are not defined, just generate the FROM clause + // from the list of tables, however we don't generate any JOIN + if (empty($from_clause)) { + // Create cartesian product + $from_clause = implode(", ", array_map('PMA_Util::backquote', $all_tables)); + } } // end count($_POST['criteriaColumn']) > 0 - // In case relations are not defined, just generate the FROM clause - // from the list of tables, however we don't generate any JOIN - if (empty($from_clause) && isset($all_tables)) { - $from_clause = implode(', ', $all_tables); - } return $from_clause; } + /** + * Formulates the WHERE clause by JOINing tables + * + * @param array $allTables Tables involved in the search + * @param array $allColumns Columns involved in the search + * + * @return string table name + */ + private function _getJoinForFromClause($allTables, $allColumns) { + + // $relations[master_table][foreign_table] => clause + $relations = array(); + + // Fill $relations with inter table relationship data + foreach ($allTables as $oneTable) { + $relations[$oneTable] = array(); + + $foreigners = PMA_getForeigners($GLOBALS['db'], $oneTable); + foreach ($foreigners as $field => $foreigner) { + // Foreign keys data + if ($field == 'foreign_keys_data') { + foreach ($foreigner as $oneKey) { + $clauses = array(); + // There may be multiple column relations + foreach ($oneKey['index_list'] as $index => $oneField) { + $clauses[] = PMA_Util::backquote($oneTable) . "." + . PMA_Util::backquote($oneField) . " = " + . PMA_Util::backquote($oneKey['ref_table_name']) . "." + . PMA_Util::backquote($oneKey['ref_index_list'][$index]); + } + // Combine multiple column relations with AND + $relations[$oneTable][$oneKey['ref_table_name']] + = implode(" AND ", $clauses); + } + } else { // Internal relations + $relations[$oneTable][$foreigner['foreign_table']] + = PMA_Util::backquote($oneTable) . "." + . PMA_Util::backquote($field) . " = " + . PMA_Util::backquote($foreigner['foreign_table']) . "." + . PMA_Util::backquote($foreigner['foreign_field']); + } + } + } + + // Get tables and columns with valid where clauses + $validWhereClauses = $this->_getWhereClauseTablesAndColumns(); + $whereClauseTables = $validWhereClauses['where_clause_tables']; + $whereClauseColumns = $validWhereClauses['where_clause_columns']; + + // Get master table + $master = $this->_getMasterTable( + $allTables, $allColumns, + $whereClauseColumns, $whereClauseTables + ); + + // Will incldue master tables and all tables that can be combined into + // a cluster by their relation + $finalized = array(); + if (mb_strlen($master) > 0) { + // Add master tables + $finalized[$master] = ''; + } + + while (true) { + $added = false; + foreach ($relations as $masterTable => $foreignData) { + foreach ($foreignData as $foreignTable => $clause) { + if (! isset($finalized[$masterTable]) + && isset($finalized[$foreignTable]) + ) { + $finalized[$masterTable] = $clause; + $added = true; + + // We are done if all tables are in $finalized + if (count($finalized) == count($allTables)) { + break 3; + } + } else if (! isset($finalized[$foreignTable]) + && isset($finalized[$masterTable]) + && in_array($foreignTable, $allTables) + ) { + $finalized[$foreignTable] = $clause; + $added = true; + + // We are done if all tables are in $finalized + if (count($finalized) == count($allTables)) { + break 3; + } + } + } + } + // If no new tables were added during this iteration, break; + if (! $added) { + break; + } + } + + // Tables that can not be combined with the table cluster + // that includes master table + $unfinalized = array_diff($allTables, array_keys($finalized)); + // Add these tables as cartesian product before joined tables + $join = implode(', ', array_map('PMA_Util::backquote', $unfinalized)); + + $first = true; + // Add joined tables + foreach ($finalized as $table => $clause) { + if ($first) { + if (! empty($join)) { + $join .= ", "; + } + $join .= PMA_Util::backquote($table); + $first = false; + } else { + $join .= "\n LEFT JOIN " . PMA_Util::backquote($table) . " ON " . $clause; + } + } + + return $join; + } + /** * Provides the generated SQL query * @@ -1434,8 +1722,10 @@ class PMA_DbQbe $html_output .= ''; // Get table's elements $html_output .= $this->_getColumnNamesRow(); - $html_output .= $this->_getSortRow(); + $html_output .= $this->_getColumnAliasRow(); $html_output .= $this->_getShowRow(); + $html_output .= $this->_getSortRow(); + $html_output .= $this->_getSortOrder(); $html_output .= $this->_getCriteriaInputboxRow(); $html_output .= $this->_getInsDelAndOrCriteriaRows(); $html_output .= $this->_getModifyColumnsRow(); diff --git a/libraries/relation.lib.php b/libraries/relation.lib.php index e10f4a18ad..d160ee1cfe 100644 --- a/libraries/relation.lib.php +++ b/libraries/relation.lib.php @@ -1364,130 +1364,6 @@ function PMA_getForeignData( return $foreignData; } // end of 'PMA_getForeignData()' function -/** - * Finds all related tables - * - * @param array $all_tables All the involved tables - * @param string $master The master table to form the LEFT JOIN clause - * - * @return string LEFT JOIN - * @access private - */ -function PMA_getRelatives($all_tables, $master) -{ - $fromclause = ''; - $left_joins = array(); - $emerg = ''; - $ignore_internal_relations = false; - - // The list of tables that we still couldn't connect - $remaining_tables = $all_tables; - unset($remaining_tables[$master]); - // The list of already connected tables - $known_tables = array(); - $known_tables[$master] = $master; - $run = 0; - while ($GLOBALS['cfgRelation']['relwork'] && count($remaining_tables) > 0) { - // Whether to go from master to foreign or vice versa - if ($run % 2 == 0) { - $from = 'master'; - $to = 'foreign'; - } else { - $from = 'foreign'; - $to = 'master'; - } - $in_know = '(\'' . implode('\', \'', $known_tables) . '\')'; - $in_left = '(\'' . implode('\', \'', $remaining_tables) . '\')'; - $rel_query = 'SELECT *' - . ' FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) - . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['relation']) - . ' WHERE ' . $from . '_db = \'' - . PMA_Util::sqlAddSlashes($GLOBALS['db']) . '\'' - . ' AND ' . $to . '_db = \'' - . PMA_Util::sqlAddSlashes($GLOBALS['db']) . '\'' - . ' AND ' . $from . '_table IN ' . $in_know - . ' AND ' . $to . '_table IN ' . $in_left; - $relations = @$GLOBALS['dbi']->query($rel_query, $GLOBALS['controllink']); - while ($row = $GLOBALS['dbi']->fetchAssoc($relations)) { - $found_table = $row[$to . '_table']; - if (isset($remaining_tables[$found_table])) { - $left_join_with = PMA_Util::backquote($GLOBALS['db']) . '.' - . PMA_Util::backquote($row[$to . '_table']); - $on_condition = PMA_Util::backquote($row[$from . '_table']) . '.' - . PMA_Util::backquote($row[$from . '_field']) . ' = ' - . PMA_Util::backquote($row[$to . '_table']) . '.' - . PMA_Util::backquote($row[$to . '_field']); - - $left_joins[$left_join_with] = array( - 'left_join_with' => $left_join_with, - 'on_condition' => array($on_condition) - ); - - $known_tables[$found_table] = $found_table; - unset($remaining_tables[$found_table]); - } - } // end while - $run++; - if ($run > 5) { - foreach ($remaining_tables as $table) { - $emerg .= ', ' . PMA_Util::backquote($table); - unset($remaining_tables[$table]); - } - } - } // end while - - // Generate 'LEFT JOIN's for InnoDB foreign keys. - $remaining_tables = $all_tables; - foreach ($remaining_tables as $one_table) { - $foreigners = PMA_getForeigners($GLOBALS['db'], $one_table, '', 'foreign'); - foreach ($foreigners['foreign_keys_data'] as $one_key) { - if (in_array($one_key['ref_table_name'], $all_tables) - && ! isset($one_key['ref_db_name']) - && ($one_key['ref_table_name'] == $master - || $one_key['ref_table_name'] == $one_table) - ) { - $left_join_with = PMA_Util::backquote($GLOBALS['db']) . '.' - . PMA_Util::backquote($one_table); - if (! isset($left_joins[$left_join_with])) { - $left_joins[$left_join_with] = array( - 'left_join_with' =>$left_join_with, - 'on_condition' => array() - ); - } - - foreach ($one_key['ref_index_list'] as $key => $one_column) { - $on_condition = PMA_Util::backquote($one_key['ref_table_name']) - . '.' . PMA_Util::backquote($one_column) . ' = ' - . PMA_Util::backquote($one_table) . '.' - . PMA_Util::backquote($one_key['index_list'][$key]); - - if (! in_array($on_condition, $left_joins[$left_join_with]['on_condition'])) { - $left_joins[$left_join_with]['on_condition'][] - = $on_condition; - } - } - $ignore_internal_relations = true; - unset($remaining_tables[$one_table]); - } - } - } - - if ($ignore_internal_relations) { - $emerg = ''; - } - - // Build the 'FROM' clause. - foreach ($left_joins as $one_join) { - $fromclause .= "\n" . ' LEFT JOIN ' - . $one_join['left_join_with'] - . ' ON ' - . implode(' AND ', $one_join['on_condition']); - } - - $fromclause = $emerg . $fromclause; - return $fromclause; -} // end of the "PMA_getRelatives()" function - /** * Rename a field in relation tables * diff --git a/test/classes/PMA_DBQbe_test.php b/test/classes/PMA_DBQbe_test.php index c84f337c6e..c93e8fc906 100644 --- a/test/classes/PMA_DBQbe_test.php +++ b/test/classes/PMA_DBQbe_test.php @@ -272,10 +272,7 @@ class PMA_DBQbe_Test extends PHPUnit_Framework_TestCase . '
Ins  Del', $this->_callProtectedFunction(
Or:' - . '  And:
Ins

Ins  Del' . '