Merge pull request #472 from scnakandala/gsoc_2013

Refactoring SQL executor
This commit is contained in:
Marc Delisle 2013-07-01 09:32:35 -07:00
commit a3980f549d
3 changed files with 248 additions and 184 deletions

View File

@ -76,6 +76,7 @@ $is_select = isset($analyzed_sql[0]['queryflags']['select_from']);
// aggregates all the results into one array
$analyzed_sql_results = array(
"parsed_sql" => $parsed_sql,
"analyzed_sql" => $analyzed_sql,
"reload" => $reload,
"drop_database" => $drop_database,

View File

@ -1113,23 +1113,13 @@ function PMA_getDefaultSqlQueryForBrowse($db, $table)
* Responds an error when an error happens when executing the query
*
* @param boolean $is_gotofile whether goto file or not
* @param String $goto goto page url
* @param String $table current table
* @param String $active_page active page url
* @param String $error error after executing the query
*
* @return void
*/
function PMA_handleQueryExecuteError($is_gotofile, $goto, $table, $active_page,
$error
) {
function PMA_handleQueryExecuteError($is_gotofile, $error) {
if ($is_gotofile) {
if (strpos($goto, 'db_') === 0 && strlen($table)) {
$table = '';
}
$active_page = $goto;
$message = PMA_Message::rawError($error);
$response = PMA_Response::getInstance();
$response->isSuccess(false);
$response->addJSON('message', $message);
@ -1144,7 +1134,7 @@ function PMA_handleQueryExecuteError($is_gotofile, $goto, $table, $active_page,
* @param String $bkm_user the bookmarking user
* @param String $import_text import text
* @param String $bkm_label bookmark label
* @param boolean $bkm_replace whether to rep;ace existing bookmarks
* @param boolean $bkm_replace whether to replace existing bookmarks
*
* @return void
*/
@ -1247,4 +1237,229 @@ function PMA_hasCurrentDbChanged($db)
return $reload;
}
/**
* If a table, database or column gets dropped, clean comments.
*
* @param String $db current database
* @param String $table current table
* @param String $dropped_column dropped column if any
* @param bool $purge
* @param array $extra_data
*
* @return array $extra_data
*/
function PMA_cleanupRelations($db, $table, $dropped_column, $purge, $extra_data)
{
include_once 'libraries/relation_cleanup.lib.php';
if (isset($purge) && $purge == 1) {
if (strlen($table) && strlen($db)) {
PMA_relationsCleanupTable($db, $table);
} elseif (strlen($db)) {
PMA_relationsCleanupDatabase($db);
}
}
if (isset($dropped_column)
&& !empty($dropped_column)
&& strlen($db)
&& strlen($table)
) {
PMA_relationsCleanupColumn($db, $table, $dropped_column);
if(isset($extra_data)) {
// to refresh the list of indexes (Ajax mode)
$extra_data['indexes_list'] = PMA_Index::getView($table, $db);
}
}
return $extra_data;
}
/**
* Function to count the total number of rows for the same 'SELECT' query without
* the 'LIMIT' clause that may have been programatically added
*
* @param int $num_rows number of rows affected/changed by the query
* @param bool $is_select whether the query is SELECT or not
* @param bool $justBrowsing whether just browsing or not
* @param string $db the current database
* @param string $table the current table
* @param array $parsed_sql parsed sql
* @param array $analyzed_sql_results the analyzed query and other varibles set
* after analyzing the query
*
* @return int $unlim_num_rows unlimited number of rows
*/
function PMA_countQueryResults($num_rows, $is_select, $justBrowsing,
$db, $table, $parsed_sql, $analyzed_sql_results
) {
if (!PMA_isAppendLimitClause($analyzed_sql_results))
{
// if we did not append a limit, set this to get a correct
// "Showing rows..." message
// $_SESSION['tmp_user_values']['max_rows'] = 'all';
$unlim_num_rows = $num_rows;
} elseif ($is_select) {
// c o u n t q u e r y
// If we are "just browsing", there is only one table,
// and no WHERE clause (or just 'WHERE 1 '),
// we do a quick count (which uses MaxExactCount) because
// SQL_CALC_FOUND_ROWS is not quick on large InnoDB tables
// However, do not count again if we did it previously
// due to $find_real_end == true
if ($justBrowsing) {
$unlim_num_rows = PMA_Table::countRecords(
$db,
$table,
$force_exact = true
);
} else {
// add select expression after the SQL_CALC_FOUND_ROWS
// for UNION, just adding SQL_CALC_FOUND_ROWS
// after the first SELECT works.
// take the left part, could be:
// SELECT
// (SELECT
$analyzed_sql = $analyzed_sql_results['analyzed_sql'];
$count_query = PMA_SQP_format(
$parsed_sql,
'query_only',
0,
$analyzed_sql[0]['position_of_first_select'] + 1
);
$count_query .= ' SQL_CALC_FOUND_ROWS ';
// add everything that was after the first SELECT
$count_query .= PMA_SQP_format(
$parsed_sql,
'query_only',
$analyzed_sql[0]['position_of_first_select'] + 1
);
// ensure there is no semicolon at the end of the
// count query because we'll probably add
// a LIMIT 1 clause after it
$count_query = rtrim($count_query);
$count_query = rtrim($count_query, ';');
// if using SQL_CALC_FOUND_ROWS, add a LIMIT to avoid
// long delays. Returned count will be complete anyway.
// (but a LIMIT would disrupt results in an UNION)
if (! isset($analyzed_sql[0]['queryflags']['union'])) {
$count_query .= ' LIMIT 1';
}
// run the count query
$GLOBALS['dbi']->tryQuery($count_query);
// if (mysql_error()) {
// void.
// I tried the case
// (SELECT `User`, `Host`, `Db`, `Select_priv` FROM `db`)
// UNION (SELECT `User`, `Host`, "%" AS "Db",
// `Select_priv`
// FROM `user`) ORDER BY `User`, `Host`, `Db`;
// and although the generated count_query is wrong
// the SELECT FOUND_ROWS() work! (maybe it gets the
// count from the latest query that worked)
//
// another case where the count_query is wrong:
// SELECT COUNT(*), f1 from t1 group by f1
// and you click to sort on count(*)
// }
$unlim_num_rows = $GLOBALS['dbi']->fetchValue('SELECT FOUND_ROWS()');
} // end else "just browsing"
} else {// not $is_select
$unlim_num_rows = 0;
}
return $unlim_num_rows;
}
/**
* Function to handle all aspects relating to executing the query
*
* @param array $analyzed_sql_results
* @param String $full_sql_query full sql query
* @param boolean $is_gotofile whether to go to a file
* @param String $db current database
* @param String $table current table
* @param boolean $find_real_end whether to find the real end
* @param String $import_text sql command
* @param String $bkm_user bookmarking user
*
* @return mixed
*/
function PMA_executeTheQuery($analyzed_sql_results, $full_sql_query, $is_gotofile,
$db, $table, $find_real_end, $import_text, $bkm_user, $extra_data
) {
// Only if we ask to see the php code
if (isset($GLOBALS['show_as_php']) || ! empty($GLOBALS['validatequery'])) {
$result = null;
$num_rows = 0;
$unlim_num_rows = 0;
} else { // If we don't ask to see the php code
if (isset($_SESSION['profiling']) && PMA_Util::profilingSupported()) {
$GLOBALS['dbi']->query('SET PROFILING=1;');
}
$result = PMA_executeQueryAndStoreResults($full_sql_query);
// Displays an error message if required and stop parsing the script
$error = $GLOBALS['dbi']->getError();
if ($error) {
PMA_handleQueryExecuteError($is_gotofile, $error);
}
// If there are no errors and bookmarklabel was given,
// store the query as a bookmark
if (! empty($_POST['bkm_label']) && ! empty($import_text)) {
PMA_storeTheQueryAsBookmark($db, $bkm_user,
$import_text, $_POST['bkm_label'],
isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null
);
} // end store bookmarks
// Gets the number of rows affected/returned
// (This must be done immediately after the query because
// mysql_affected_rows() reports about the last query done)
$num_rows = PMA_getNumberOfRowsAffectedOrChanged(
$analyzed_sql_results['is_affected'], $result,
isset($num_rows) ? $num_rows : null
);
// Grabs the profiling results
if (isset($_SESSION['profiling']) && PMA_Util::profilingSupported()) {
$profiling_results = $GLOBALS['dbi']->fetchResult('SHOW PROFILE;');
}
$justBrowsing = PMA_isJustBrowsing(
$analyzed_sql_results,isset($find_real_end) ? $find_real_end : null
);
$unlim_num_rows = PMA_countQueryResults($num_rows,
$analyzed_sql_results['is_select'], $justBrowsing, $db,
$table, $analyzed_sql_results['parsed_sql'], $analyzed_sql_results
);
$extra_data = PMA_cleanupRelations(
isset($db) ? $db : '', isset($table) ? $table : '',
isset($_REQUEST['dropped_column']) ? $_REQUEST['dropped_column'] : null,
isset($_REQUEST['purge']) ? $_REQUEST['purge'] : null,
isset($extra_data) ? $extra_data : null
);
}
return array($result, $num_rows, $unlim_num_rows,
isset($profiling_results) ? $profiling_results : null,
isset($justBrowsing) ? $justBrowsing : null, $extra_data
);
}
?>

192
sql.php
View File

@ -196,181 +196,29 @@ if (PMA_isAppendLimitClause($analyzed_sql_results)) {
);
}
// Since multiple query execution is anyway handled,
// ignore the WHERE clause of the first sql statement
// which might contain a phrase like 'call '
if (preg_match("/\bcall\b/i", $full_sql_query)
&& empty($analyzed_sql[0]['where_clause'])
) {
$is_procedure = true;
} else {
$is_procedure = false;
}
$reload = PMA_hasCurrentDbChanged($db);
// E x e c u t e t h e q u e r y
// Execute the query
list($result, $num_rows, $unlim_num_rows, $profiling_results,
$justBrowsing, $extra_data
) = PMA_executeTheQuery(
$analyzed_sql_results, $full_sql_query, $is_gotofile, $db, $table,
isset($find_real_end) ? $find_real_end : null,
isset($import_text) ? $import_text : null, $cfg['Bookmark']['user'],
isset($extra_data) ? $extra_data : null
);
// Only if we didn't ask to see the php code
if (isset($GLOBALS['show_as_php']) || ! empty($GLOBALS['validatequery'])) {
unset($result);
$num_rows = 0;
$unlim_num_rows = 0;
} else {
if (isset($_SESSION['profiling']) && PMA_Util::profilingSupported()) {
$GLOBALS['dbi']->query('SET PROFILING=1;');
}
$result = PMA_executeQueryAndStoreResults($full_sql_query);
$is_procedure = false;
// Since multiple query execution is anyway handled,
// ignore the WHERE clause of the first sql statement
// which might contain a phrase like 'call '
if (preg_match("/\bcall\b/i", $full_sql_query)
&& empty($analyzed_sql[0]['where_clause'])
) {
$is_procedure = true;
}
// Displays an error message if required and stop parsing the script
$error = $GLOBALS['dbi']->getError();
if ($error) {
PMA_handleQueryExecuteError($is_gotofile, $goto, $table, $active_page,
$error
);
}
unset($error);
// If there are no errors and bookmarklabel was given,
// store the query as a bookmark
if (! empty($bkm_label) && ! empty($import_text)) {
PMA_storeTheQueryAsBookmark($db, $cfg['Bookmark']['user'],
$import_text, $bkm_label, isset($bkm_replace) ? $bkm_replace : null
);
$bookmark_created = true;
} // end store bookmarks
// Gets the number of rows affected/returned
// (This must be done immediately after the query because
// mysql_affected_rows() reports about the last query done)
$num_rows = PMA_getNumberOfRowsAffectedOrChanged($is_affected, $result,
isset($num_rows) ? $num_rows : null
);
// Grabs the profiling results
if (isset($_SESSION['profiling']) && PMA_Util::profilingSupported()) {
$profiling_results = $GLOBALS['dbi']->fetchResult('SHOW PROFILE;');
}
// Counts the total number of rows for the same 'SELECT' query without the
// 'LIMIT' clause that may have been programatically added
$justBrowsing = false;
if (empty($sql_limit_to_append)) {
$unlim_num_rows = $num_rows;
// if we did not append a limit, set this to get a correct
// "Showing rows..." message
// $_SESSION['tmp_user_values']['max_rows'] = 'all';
} elseif ($is_select) {
// c o u n t q u e r y
// If we are "just browsing", there is only one table,
// and no WHERE clause (or just 'WHERE 1 '),
// we do a quick count (which uses MaxExactCount) because
// SQL_CALC_FOUND_ROWS is not quick on large InnoDB tables
// However, do not count again if we did it previously
// due to $find_real_end == true
if (PMA_isJustBrowsing(
$analyzed_sql_results,isset($find_real_end) ? $find_real_end : null)
) {
$justBrowsing = true;
$unlim_num_rows = PMA_Table::countRecords(
$db,
$table,
$force_exact = true
);
} else {
// add select expression after the SQL_CALC_FOUND_ROWS
// for UNION, just adding SQL_CALC_FOUND_ROWS
// after the first SELECT works.
// take the left part, could be:
// SELECT
// (SELECT
$count_query = PMA_SQP_format(
$parsed_sql,
'query_only',
0,
$analyzed_sql[0]['position_of_first_select'] + 1
);
$count_query .= ' SQL_CALC_FOUND_ROWS ';
// add everything that was after the first SELECT
$count_query .= PMA_SQP_format(
$parsed_sql,
'query_only',
$analyzed_sql[0]['position_of_first_select'] + 1
);
// ensure there is no semicolon at the end of the
// count query because we'll probably add
// a LIMIT 1 clause after it
$count_query = rtrim($count_query);
$count_query = rtrim($count_query, ';');
// if using SQL_CALC_FOUND_ROWS, add a LIMIT to avoid
// long delays. Returned count will be complete anyway.
// (but a LIMIT would disrupt results in an UNION)
if (! isset($analyzed_sql[0]['queryflags']['union'])) {
$count_query .= ' LIMIT 1';
}
// run the count query
$GLOBALS['dbi']->tryQuery($count_query);
// if (mysql_error()) {
// void.
// I tried the case
// (SELECT `User`, `Host`, `Db`, `Select_priv` FROM `db`)
// UNION (SELECT `User`, `Host`, "%" AS "Db",
// `Select_priv`
// FROM `user`) ORDER BY `User`, `Host`, `Db`;
// and although the generated count_query is wrong
// the SELECT FOUND_ROWS() work! (maybe it gets the
// count from the latest query that worked)
//
// another case where the count_query is wrong:
// SELECT COUNT(*), f1 from t1 group by f1
// and you click to sort on count(*)
// }
$unlim_num_rows = $GLOBALS['dbi']->fetchValue('SELECT FOUND_ROWS()');
} // end else "just browsing"
} else { // not $is_select
$unlim_num_rows = 0;
} // end rows total count
// if a table or database gets dropped, check column comments.
if (isset($purge) && $purge == '1') {
/**
* Cleanup relations.
*/
include_once 'libraries/relation_cleanup.lib.php';
if (strlen($table) && strlen($db)) {
PMA_relationsCleanupTable($db, $table);
} elseif (strlen($db)) {
PMA_relationsCleanupDatabase($db);
} else {
// VOID. No DB/Table gets deleted.
} // end if relation-stuff
} // end if ($purge)
// If a column gets dropped, do relation magic.
if (isset($dropped_column)
&& strlen($db)
&& strlen($table)
&& ! empty($dropped_column)
) {
include_once 'libraries/relation_cleanup.lib.php';
PMA_relationsCleanupColumn($db, $table, $dropped_column);
// to refresh the list of indexes (Ajax mode)
$extra_data['indexes_list'] = PMA_Index::getView($table, $db);
} // end if column was dropped
} // end else "didn't ask to see php code"
// No rows returned -> move back to the calling page
if ((0 == $num_rows && 0 == $unlim_num_rows) || $is_affected) {