Merge pull request #1723 from madhuracj/qbe

QBE improvements including bug fixes
This commit is contained in:
Madhura Jayaratne 2015-06-14 07:46:57 +05:30
commit 734d0684e5
4 changed files with 346 additions and 182 deletions

View File

@ -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

View File

@ -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 = '<td class="center">';
$html_output .= '<select name="criteriaSortOrder[' . $columnNumber . ']">';
$html_output .= '<option value="1000">'
. '&nbsp;</option>';
for ($a = 1; $a <= $totalColumnCount; $a++) {
$html_output .= '<option value="' . $a . '"';
if ($a == $sortOrder) {
$html_output .= ' selected="selected"';
}
$html_output .= '>' . $a . '</option>';
}
$html_output .= '</select>';
$html_output .= '</td>';
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 = '<tr class="even noclick">';
$html_output .= '<th>' . __('Alias:') . '</th>';
$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 .= '<td class="center">';
$html_output .= '<input type="text"'
. ' name="criteriaAlias[' . $new_column_count . ']"'
. ' value="" />';
$html_output .= '</td>';
$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 .= '<td class="center">';
$html_output .= '<input type="text"'
. ' name="criteriaAlias[' . $new_column_count . ']"'
. ' value="' . htmlspecialchars($tmp_alias) . '" />';
$html_output .= '</td>';
$new_column_count++;
} // end for
$html_output .= '</tr>';
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 = '<tr class="even noclick">';
$html_output .= '<th>' . __('Sort order:') . '</th>';
$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 .= '</tr>';
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 = '<td class="center">';
$html_output .= '<strong>' . __('Or:') . '</strong>';
$html_output .= '<input type="radio"'
. ' name="criteriaAndOrColumn[' . $column_number . ']"'
. ' value="or"' . $selected['or'] . ' />';
$html_output .= '&nbsp;&nbsp;<strong>' . __('And:') . '</strong>';
$html_output .= '<input type="radio"'
. ' name="criteriaAndOrColumn[' . $column_number . ']"'
. ' value="and"' . $selected['and'] . ' />';
if (! $last_column) {
$html_output .= '<strong>' . __('Or:') . '</strong>';
$html_output .= '<input type="radio"'
. ' name="criteriaAndOrColumn[' . $column_number . ']"'
. ' value="or"' . $selected['or'] . ' />';
$html_output .= '&nbsp;&nbsp;<strong>' . __('And:') . '</strong>';
$html_output .= '<input type="radio"'
. ' name="criteriaAndOrColumn[' . $column_number . ']"'
. ' value="and"' . $selected['and'] . ' />';
}
$html_output .= '<br />' . __('Ins');
$html_output .= '<input type="checkbox"'
. ' name="criteriaColumnInsert[' . $column_number . ']" />';
@ -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 .= '<table class="data" style="width: 100%;">';
// Get table's <tr> 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();

View File

@ -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
*

View File

@ -272,10 +272,7 @@ class PMA_DBQbe_Test extends PHPUnit_Framework_TestCase
. '</strong><input type="radio" name="criteriaAndOrColumn[1]" value='
. '"and" checked="checked" /><br />Ins<input type="checkbox" name='
. '"criteriaColumnInsert[1]" />&nbsp;&nbsp;Del<input type="checkbox" '
. 'name="criteriaColumnDelete[1]" /></td><td class="center"><strong>Or:'
. '</strong><input type="radio" name="criteriaAndOrColumn[2]" value="or"'
. ' />&nbsp;&nbsp;<strong>And:</strong><input type="radio" name="criter'
. 'iaAndOrColumn[2]" value="and" checked="checked" /><br />Ins<input '
. 'name="criteriaColumnDelete[1]" /></td><td class="center"><br />Ins<input '
. 'type="checkbox" name="criteriaColumnInsert[2]" />&nbsp;&nbsp;Del'
. '<input type="checkbox" name="criteriaColumnDelete[2]" /></td></tr>',
$this->_callProtectedFunction(