Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
cd9d383cd1
@ -58,6 +58,7 @@ phpMyAdmin - ChangeLog
|
||||
+ rfe #1594 Use plain English descriptors instead of script names for icon link destinations
|
||||
+ rfe #1541 Disable foreign key checks for some operations
|
||||
- bug #4957 "With selected" links doesn't work in table browse
|
||||
- bug #4795 Query builder: missing joint for the intermediary table
|
||||
|
||||
4.4.10.0 (not yet released)
|
||||
- bug #4950 Issues in database selection for replication
|
||||
|
||||
@ -1304,23 +1304,23 @@ class PMA_DbQbe
|
||||
/**
|
||||
* Provides UNIQUE columns and INDEX columns present in criteria tables
|
||||
*
|
||||
* @param array $all_tables Tables involved in the search
|
||||
* @param array $all_columns Columns involved in the search
|
||||
* @param array $search_tables Tables involved in the search
|
||||
* @param array $search_columns Columns involved in the search
|
||||
* @param array $where_clause_columns Columns having criteria where clause
|
||||
*
|
||||
* @return array having UNIQUE and INDEX columns
|
||||
*/
|
||||
private function _getIndexes($all_tables, $all_columns,
|
||||
private function _getIndexes($search_tables, $search_columns,
|
||||
$where_clause_columns
|
||||
) {
|
||||
$unique_columns = array();
|
||||
$index_columns = array();
|
||||
|
||||
foreach ($all_tables as $table) {
|
||||
foreach ($search_tables as $table) {
|
||||
$indexes = $GLOBALS['dbi']->getTableIndexes($this->_db, $table);
|
||||
foreach ($indexes as $index) {
|
||||
$column = $table . '.' . $index['Column_name'];
|
||||
if (isset($all_columns[$column])) {
|
||||
if (isset($search_columns[$column])) {
|
||||
if ($index['Non_unique'] == 0) {
|
||||
if (isset($where_clause_columns[$column])) {
|
||||
$unique_columns[$column] = 'Y';
|
||||
@ -1347,27 +1347,27 @@ class PMA_DbQbe
|
||||
/**
|
||||
* Provides UNIQUE columns and INDEX columns present in criteria tables
|
||||
*
|
||||
* @param array $all_tables Tables involved in the search
|
||||
* @param array $all_columns Columns involved in the search
|
||||
* @param array $search_tables Tables involved in the search
|
||||
* @param array $search_columns Columns involved in the search
|
||||
* @param array $where_clause_columns Columns having criteria where clause
|
||||
*
|
||||
* @return array having UNIQUE and INDEX columns
|
||||
*/
|
||||
private function _getLeftJoinColumnCandidates($all_tables, $all_columns,
|
||||
private function _getLeftJoinColumnCandidates($search_tables, $search_columns,
|
||||
$where_clause_columns
|
||||
) {
|
||||
$GLOBALS['dbi']->selectDb($this->_db);
|
||||
|
||||
// Get unique columns and index columns
|
||||
$indexes = $this->_getIndexes(
|
||||
$all_tables, $all_columns, $where_clause_columns
|
||||
$search_tables, $search_columns, $where_clause_columns
|
||||
);
|
||||
$unique_columns = $indexes['unique'];
|
||||
$index_columns = $indexes['index'];
|
||||
|
||||
list($candidate_columns, $needsort)
|
||||
= $this->_getLeftJoinColumnCandidatesBest(
|
||||
$all_tables, $where_clause_columns, $unique_columns, $index_columns
|
||||
$search_tables, $where_clause_columns, $unique_columns, $index_columns
|
||||
);
|
||||
|
||||
// If we came up with $unique_columns (very good) or $index_columns (still
|
||||
@ -1403,14 +1403,14 @@ class PMA_DbQbe
|
||||
/**
|
||||
* Provides the main table to form the LEFT JOIN clause
|
||||
*
|
||||
* @param array $all_tables Tables involved in the search
|
||||
* @param array $all_columns Columns involved in the search
|
||||
* @param array $search_tables Tables involved in the search
|
||||
* @param array $search_columns Columns involved in the search
|
||||
* @param array $where_clause_columns Columns having criteria where clause
|
||||
* @param array $where_clause_tables Tables having criteria where clause
|
||||
*
|
||||
* @return string table name
|
||||
*/
|
||||
private function _getMasterTable($all_tables, $all_columns,
|
||||
private function _getMasterTable($search_tables, $search_columns,
|
||||
$where_clause_columns, $where_clause_tables
|
||||
) {
|
||||
if (count($where_clause_tables) == 1) {
|
||||
@ -1425,7 +1425,7 @@ class PMA_DbQbe
|
||||
// because he is using one of his databases as pmadb,
|
||||
// the last db selected is not always the one where we need to work)
|
||||
$candidate_columns = $this->_getLeftJoinColumnCandidates(
|
||||
$all_tables, $all_columns, $where_clause_columns
|
||||
$search_tables, $search_columns, $where_clause_columns
|
||||
);
|
||||
|
||||
// Generally, we need to display all the rows of foreign (referenced)
|
||||
@ -1537,27 +1537,27 @@ class PMA_DbQbe
|
||||
$from_clause = '';
|
||||
if (isset($_POST['criteriaColumn']) && count($_POST['criteriaColumn']) > 0) {
|
||||
// Initialize some variables
|
||||
$all_tables = $all_columns = array();
|
||||
$search_tables = $search_columns = array();
|
||||
|
||||
// We only start this if we have fields, otherwise it would be dumb
|
||||
foreach ($_POST['criteriaColumn'] as $value) {
|
||||
$parts = explode('.', $value);
|
||||
if (! empty($parts[0]) && ! empty($parts[1])) {
|
||||
$table = str_replace('`', '', $parts[0]);
|
||||
$all_tables[$table] = $table;
|
||||
$all_columns[] = $table . '.' . str_replace('`', '', $parts[1]);
|
||||
$search_tables[$table] = $table;
|
||||
$search_columns[] = $table . '.' . str_replace('`', '', $parts[1]);
|
||||
}
|
||||
} // end while
|
||||
|
||||
// Create LEFT JOINS out of Relations
|
||||
$from_clause = $this->_getJoinForFromClause($all_tables, $all_columns);
|
||||
$from_clause = $this->_getJoinForFromClause($search_tables, $search_columns);
|
||||
|
||||
// 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)
|
||||
", ", array_map('PMA_Util::backquote', $search_tables)
|
||||
);
|
||||
}
|
||||
} // end count($_POST['criteriaColumn']) > 0
|
||||
@ -1568,50 +1568,19 @@ class PMA_DbQbe
|
||||
/**
|
||||
* Formulates the WHERE clause by JOINing tables
|
||||
*
|
||||
* @param array $allTables Tables involved in the search
|
||||
* @param array $allColumns Columns involved in the search
|
||||
* @param array $searchTables Tables involved in the search
|
||||
* @param array $searchColumns Columns involved in the search
|
||||
*
|
||||
* @return string table name
|
||||
*/
|
||||
private function _getJoinForFromClause($allTables, $allColumns)
|
||||
private function _getJoinForFromClause($searchTables, $searchColumns)
|
||||
{
|
||||
|
||||
// $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']);
|
||||
}
|
||||
}
|
||||
foreach ($searchTables as $oneTable) {
|
||||
$this->_loadRelationsForTable($relations, $oneTable);
|
||||
}
|
||||
|
||||
// Get tables and columns with valid where clauses
|
||||
@ -1621,7 +1590,7 @@ class PMA_DbQbe
|
||||
|
||||
// Get master table
|
||||
$master = $this->_getMasterTable(
|
||||
$allTables, $allColumns,
|
||||
$searchTables, $searchColumns,
|
||||
$whereClauseColumns, $whereClauseTables
|
||||
);
|
||||
|
||||
@ -1632,47 +1601,72 @@ class PMA_DbQbe
|
||||
// Add master tables
|
||||
$finalized[$master] = '';
|
||||
}
|
||||
// Fill the $finalized array with JOIN clauses for each table
|
||||
$this->_fillJoinClauses($finalized, $relations, $searchTables);
|
||||
|
||||
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;
|
||||
// JOIN clause
|
||||
$join = '';
|
||||
|
||||
// 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;
|
||||
// Tables that can not be combined with the table cluster
|
||||
// which includes master table
|
||||
$unfinalized = array_diff($searchTables, array_keys($finalized));
|
||||
if (count($unfinalized) > 0) {
|
||||
|
||||
// We are done if all tables are in $finalized
|
||||
if (count($finalized) == count($allTables)) {
|
||||
break 3;
|
||||
// We need to look for intermediary tables to JOIN unfinalized tables
|
||||
// Heuristic to chose intermediary tables is to look for tables
|
||||
// having relationships with unfinalized tables
|
||||
foreach ($unfinalized as $oneTable) {
|
||||
|
||||
$references = PMA_getChildReferences($this->_db, $oneTable);
|
||||
foreach ($references as $column => $columnReferences) {
|
||||
foreach ($columnReferences as $reference) {
|
||||
|
||||
// Only from this schema
|
||||
if ($reference['table_schema'] == $this->_db) {
|
||||
$table = $reference['table_name'];
|
||||
|
||||
$this->_loadRelationsForTable($relations, $table);
|
||||
|
||||
// Make copies
|
||||
$tempFinalized = $finalized;
|
||||
$tempSearchTables = $searchTables;
|
||||
$tempSearchTables[] = $table;
|
||||
|
||||
// Try joining with the added table
|
||||
$this->_fillJoinClauses(
|
||||
$tempFinalized, $relations, $tempSearchTables
|
||||
);
|
||||
|
||||
$tempUnfinalized = array_diff(
|
||||
$tempSearchTables, array_keys($tempFinalized)
|
||||
);
|
||||
// Take greedy approach.
|
||||
// If the unfinalized count drops we keep the new table
|
||||
// and switch temporary varibles with the original ones
|
||||
if (count($tempUnfinalized) < count($unfinalized)) {
|
||||
$finalized = $tempFinalized;
|
||||
$searchTables = $tempSearchTables;
|
||||
}
|
||||
|
||||
// We are done if no unfinalized tables anymore
|
||||
if (count($tempUnfinalized) == 0) {
|
||||
break 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no new tables were added during this iteration, break;
|
||||
if (! $added) {
|
||||
break;
|
||||
|
||||
$unfinalized = array_diff($searchTables, array_keys($finalized));
|
||||
// If there are still unfinalized tables
|
||||
if (count($unfinalized) > 0) {
|
||||
// Add these tables as cartesian product before joined tables
|
||||
$join .= implode(
|
||||
', ', array_map('PMA_Util::backquote', $unfinalized)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@ -1692,6 +1686,89 @@ class PMA_DbQbe
|
||||
return $join;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads relations for a given table into the $relations array
|
||||
*
|
||||
* @param array $relations array of relations
|
||||
* @param string $oneTable the table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _loadRelationsForTable(&$relations, $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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the $finalized arrays with JOIN clauses for each of the tables
|
||||
*
|
||||
* @param array $finalized JOIN clauses for each table
|
||||
* @param array $relations Relations among tables
|
||||
* @param array $searchTables Tables involved in the search
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _fillJoinClauses(&$finalized, $relations, $searchTables)
|
||||
{
|
||||
while (true) {
|
||||
$added = false;
|
||||
foreach ($searchTables as $masterTable) {
|
||||
$foreignData = $relations[$masterTable];
|
||||
foreach ($foreignData as $foreignTable => $clause) {
|
||||
if (! isset($finalized[$masterTable])
|
||||
&& isset($finalized[$foreignTable])
|
||||
) {
|
||||
$finalized[$masterTable] = $clause;
|
||||
$added = true;
|
||||
} elseif (! isset($finalized[$foreignTable])
|
||||
&& isset($finalized[$masterTable])
|
||||
&& in_array($foreignTable, $searchTables)
|
||||
) {
|
||||
$finalized[$foreignTable] = $clause;
|
||||
$added = true;
|
||||
}
|
||||
if ($added) {
|
||||
// We are done if all tables are in $finalized
|
||||
if (count($finalized) == count($searchTables)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no new tables were added during this iteration, break;
|
||||
if (! $added) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the generated SQL query
|
||||
*
|
||||
@ -1859,7 +1936,7 @@ class PMA_DbQbe
|
||||
/**
|
||||
* Get best
|
||||
*
|
||||
* @param array $all_tables All tables
|
||||
* @param array $search_tables Tables involved in the search
|
||||
* @param array $where_clause_columns Columns with where clause
|
||||
* @param array $unique_columns Unique columns
|
||||
* @param array $index_columns Indexed columns
|
||||
@ -1867,7 +1944,7 @@ class PMA_DbQbe
|
||||
* @return array
|
||||
*/
|
||||
private function _getLeftJoinColumnCandidatesBest(
|
||||
$all_tables, $where_clause_columns, $unique_columns, $index_columns
|
||||
$search_tables, $where_clause_columns, $unique_columns, $index_columns
|
||||
) {
|
||||
// now we want to find the best.
|
||||
if (isset($unique_columns) && count($unique_columns) > 0) {
|
||||
@ -1883,7 +1960,7 @@ class PMA_DbQbe
|
||||
$needsort = 0;
|
||||
return array($candidate_columns, $needsort);
|
||||
} else {
|
||||
$candidate_columns = $all_tables;
|
||||
$candidate_columns = $search_tables;
|
||||
$needsort = 0;
|
||||
return array($candidate_columns, $needsort);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user