diff options
author | Christian Foellmann <foellmann@foe-services.de> | 2014-12-23 14:48:13 +0300 |
---|---|---|
committer | Christian Foellmann <foellmann@foe-services.de> | 2014-12-23 14:48:13 +0300 |
commit | 2bfb20e57418ebf396149782be9f98e868fe8608 (patch) | |
tree | a5ec4011410970603d3f462a535a5a359057fa77 /libraries/DatabaseInterface.class.php | |
parent | 20f1bf77c8281efc675a14e0f6bf52f657dabd9a (diff) |
UPDATE 4.3.34.3.3
Diffstat (limited to 'libraries/DatabaseInterface.class.php')
-rw-r--r-- | libraries/DatabaseInterface.class.php | 1474 |
1 files changed, 994 insertions, 480 deletions
diff --git a/libraries/DatabaseInterface.class.php b/libraries/DatabaseInterface.class.php index c7c5171420..507c63e349 100644 --- a/libraries/DatabaseInterface.class.php +++ b/libraries/DatabaseInterface.class.php @@ -9,6 +9,9 @@ if (! defined('PHPMYADMIN')) { exit; } +require_once './libraries/logging.lib.php'; +require_once './libraries/Index.class.php'; + /** * Main interface for database interactions * @@ -100,7 +103,7 @@ class PMA_DatabaseInterface // entry for Comment when changing the storage engine in Operations) // Note 2: Instead of array_merge(), simply use the + operator because // array_merge() renumbers numeric keys starting with 0, therefore - // we would lose a db name thats consists only of numbers + // we would lose a db name that consists only of numbers foreach ($tables as $one_database => $its_tables) { if (isset(PMA_Table::$cache[$one_database])) { @@ -122,10 +125,10 @@ class PMA_DatabaseInterface /** * Stores query data into session data for debugging purposes * - * @param string $query Query text - * @param resource $link database link - * @param resource $result Query result - * @param integer $time Time to execute query + * @param string $query Query text + * @param object $link database link + * @param object $result Query result + * @param integer $time Time to execute query * * @return void */ @@ -137,58 +140,40 @@ class PMA_DatabaseInterface $_SESSION['debug']['queries'][$hash]['count']++; } else { $_SESSION['debug']['queries'][$hash] = array(); - if ($result == false) { + $error_message = $this->getError($link); + if ($result == false && is_string($error_message)) { $_SESSION['debug']['queries'][$hash]['error'] = '<b style="color:red">' - . htmlspecialchars(mysqli_error($link)) . '</b>'; + . htmlspecialchars($error_message) . '</b>'; } $_SESSION['debug']['queries'][$hash]['count'] = 1; $_SESSION['debug']['queries'][$hash]['query'] = htmlspecialchars($query); $_SESSION['debug']['queries'][$hash]['time'] = $time; } - $trace = array(); - foreach (debug_backtrace() as $trace_step) { - $trace[] - = (isset($trace_step['file']) - ? PMA_Error::relPath($trace_step['file']) - : '') - . (isset($trace_step['line']) - ? '#' . $trace_step['line'] . ': ' - : '') - . (isset($trace_step['class']) ? $trace_step['class'] : '') - . (isset($trace_step['type']) ? $trace_step['type'] : '') - . (isset($trace_step['function']) ? $trace_step['function'] : '') - . '(' - . (isset($trace_step['params']) - ? implode(', ', $trace_step['params']) - : '' - ) - . ')' - ; - } - $_SESSION['debug']['queries'][$hash]['trace'][] = $trace; + $_SESSION['debug']['queries'][$hash]['trace'][] = PMA_Error::formatBacktrace( + debug_backtrace(), + " ", + "\n" + ); } /** * runs a query and returns the result * - * @param string $query query to run - * @param resource $link mysql link resource - * @param integer $options query options - * @param bool $cache_affected_rows whether to cache affected row + * @param string $query query to run + * @param object $link mysql link resource + * @param integer $options query options + * @param bool $cache_affected_rows whether to cache affected row * * @return mixed */ public function tryQuery($query, $link = null, $options = 0, $cache_affected_rows = true ) { - if (empty($link)) { - if (isset($GLOBALS['userlink'])) { - $link = $GLOBALS['userlink']; - } else { - return false; - } + $link = $this->getLink($link); + if ($link === false) { + return false; } if ($GLOBALS['cfg']['DBG']['sql']) { @@ -222,12 +207,9 @@ class PMA_DatabaseInterface */ public function tryMultiQuery($multi_query = '', $link = null) { - if (empty($link)) { - if (isset($GLOBALS['userlink'])) { - $link = $GLOBALS['userlink']; - } else { - return false; - } + $link = $this->getLink($link); + if ($link === false) { + return false; } return $this->_extension->realMultiQuery($link, $multi_query); @@ -324,14 +306,14 @@ class PMA_DatabaseInterface * * @param string|bool $table table or false * @param boolean $tbl_is_group $table is a table group - * @param string $tble_type whether table or view + * @param string $table_type whether table or view * * @return string a segment of the WHERE clause */ - private function _getTableCondition($table, $tbl_is_group, $tble_type) + private function _getTableCondition($table, $tbl_is_group, $table_type) { // get table information from information_schema - if ($table) { + if ($table && is_string($table)) { if (true === $tbl_is_group) { $sql_where_table = 'AND t.`TABLE_NAME` LIKE \'' . PMA_Util::escapeMysqlWildcards( @@ -346,14 +328,14 @@ class PMA_DatabaseInterface $sql_where_table = ''; } - if ($tble_type) { - if ($tble_type == 'view') { + if ($table_type) { + if ($table_type == 'view') { if (PMA_DRIZZLE) { $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE'"; } else { $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE TABLE'"; } - } else if ($tble_type == 'table') { + } else if ($table_type == 'table') { if (PMA_DRIZZLE) { $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE'"; } else { @@ -367,23 +349,15 @@ class PMA_DatabaseInterface /** * returns the beginning of the SQL statement to fetch the list of tables * - * @param array $this_databases databases to list - * @param string $sql_where_table additional condition + * @param string[] $this_databases databases to list + * @param string $sql_where_table additional condition * * @return string the SQL statement */ private function _getSqlForTablesFull($this_databases, $sql_where_table) { if (PMA_DRIZZLE) { - $engine_info = PMA_Util::cacheGet('drizzle_engines', null); - $stats_join = "LEFT JOIN (SELECT 0 NUM_ROWS) AS stat ON false"; - if (isset($engine_info['InnoDB']) - && $engine_info['InnoDB']['module_library'] == 'innobase' - ) { - $stats_join = "LEFT JOIN data_dictionary.INNODB_SYS_TABLESTATS" - . " stat ON (t.ENGINE = 'InnoDB' AND stat.NAME" - . " = (t.TABLE_SCHEMA || '/') || t.TABLE_NAME)"; - } + $stats_join = $this->_getDrizzeStatsJoin(); // data_dictionary.table_cache may not contain any data // for some tables, it's just a table cache @@ -478,7 +452,7 @@ class PMA_DatabaseInterface * @param boolean|integer $limit_count number of tables to return * @param string $sort_by table attribute to sort by * @param string $sort_order direction to sort (ASC or DESC) - * @param string $tble_type whether table or view + * @param string $table_type whether table or view * * @todo move into PMA_Table * @@ -487,7 +461,7 @@ class PMA_DatabaseInterface public function getTablesFull($database, $table = false, $tbl_is_group = false, $link = null, $limit_offset = 0, $limit_count = false, $sort_by = 'Name', $sort_order = 'ASC', - $tble_type = null + $table_type = null ) { if (true === $limit_count) { $limit_count = $GLOBALS['cfg']['MaxTableList']; @@ -501,84 +475,83 @@ class PMA_DatabaseInterface $tables = array(); - $sql_where_table = $this->_getTableCondition( - $table, $tbl_is_group, $tble_type - ); + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $sql_where_table = $this->_getTableCondition( + $table, $tbl_is_group, $table_type + ); - // for PMA bc: - // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME` - // - // on non-Windows servers, - // added BINARY in the WHERE clause to force a case sensitive - // comparison (if we are looking for the db Aa we don't want - // to find the db aa) - $this_databases = array_map('PMA_Util::sqlAddSlashes', $databases); + // for PMA bc: + // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME` + // + // on non-Windows servers, + // added BINARY in the WHERE clause to force a case sensitive + // comparison (if we are looking for the db Aa we don't want + // to find the db aa) + $this_databases = array_map('PMA_Util::sqlAddSlashes', $databases); - $sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table); - unset($sql_where_table); + $sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table); - // Sort the tables - $sql .= " ORDER BY $sort_by $sort_order"; + // Sort the tables + $sql .= " ORDER BY $sort_by $sort_order"; - if ($limit_count) { - $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; - } + if ($limit_count) { + $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; + } - $tables = $this->fetchResult( - $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link - ); - unset($sql); + $tables = $this->fetchResult( + $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link + ); - if (PMA_DRIZZLE) { - // correct I_S and D_D names returned by D_D.TABLES - - // Drizzle generally uses lower case for them, - // but TABLES returns uppercase - foreach ((array)$database as $db) { - $db_upper = strtoupper($db); - if (!isset($tables[$db]) && isset($tables[$db_upper])) { - $tables[$db] = $tables[$db_upper]; - unset($tables[$db_upper]); + if (PMA_DRIZZLE) { + // correct I_S and D_D names returned by D_D.TABLES - + // Drizzle generally uses lower case for them, + // but TABLES returns uppercase + foreach ((array)$database as $db) { + $db_upper = /*overload*/mb_strtoupper($db); + if (!isset($tables[$db]) && isset($tables[$db_upper])) { + $tables[$db] = $tables[$db_upper]; + unset($tables[$db_upper]); + } } } - } - if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) { - // here, the array's first key is by schema name - foreach ($tables as $one_database_name => $one_database_tables) { - uksort($one_database_tables, 'strnatcasecmp'); + if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) { + // here, the array's first key is by schema name + foreach ($tables as $one_database_name => $one_database_tables) { + uksort($one_database_tables, 'strnatcasecmp'); - if ($sort_order == 'DESC') { - $one_database_tables = array_reverse($one_database_tables); - } - $tables[$one_database_name] = $one_database_tables; - } - } else if ($sort_by == 'Data_length') { // Size = Data_length + Index_length - foreach ($tables as $one_database_name => $one_database_tables) { - uasort( - $one_database_tables, - function ($a, $b) { - $aLength = $a['Data_length'] + $a['Index_length']; - $bLength = $b['Data_length'] + $b['Index_length']; - return ($aLength == $bLength) - ? 0 - : ($aLength < $bLength) ? -1 : 1; + if ($sort_order == 'DESC') { + $one_database_tables = array_reverse($one_database_tables); } - ); + $tables[$one_database_name] = $one_database_tables; + } + } else if ($sort_by == 'Data_length') { // Size = Data_length + Index_length + foreach ($tables as $one_database_name => $one_database_tables) { + uasort( + $one_database_tables, + function ($a, $b) { + $aLength = $a['Data_length'] + $a['Index_length']; + $bLength = $b['Data_length'] + $b['Index_length']; + return ($aLength == $bLength) + ? 0 + : ($aLength < $bLength) ? -1 : 1; + } + ); - if ($sort_order == 'DESC') { - $one_database_tables = array_reverse($one_database_tables); + if ($sort_order == 'DESC') { + $one_database_tables = array_reverse($one_database_tables); + } + $tables[$one_database_name] = $one_database_tables; } - $tables[$one_database_name] = $one_database_tables; } - } - // end (get information from table schema) + } // end (get information from table schema) // If permissions are wrong on even one database directory, // information_schema does not return any table info for any database // this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002 if (empty($tables) && !PMA_DRIZZLE) { foreach ($databases as $each_database) { - if ($table || (true === $tbl_is_group) || $tble_type) { + if ($table || (true === $tbl_is_group) || $table_type) { $sql = 'SHOW TABLE STATUS FROM ' . PMA_Util::backquote($each_database) . ' WHERE'; @@ -591,13 +564,13 @@ class PMA_DatabaseInterface . "%'"; $needAnd = true; } - if ($tble_type) { + if ($table_type) { if ($needAnd) { $sql .= " AND"; } - if ($tble_type == 'view') { + if ($table_type == 'view') { $sql .= " `Comment` = 'VIEW'"; - } else if ($tble_type == 'table') { + } else if ($table_type == 'table') { $sql .= " `Comment` != 'VIEW'"; } } @@ -655,8 +628,7 @@ class PMA_DatabaseInterface if ($sort_by == 'Data_length') { foreach ($each_tables as $table_name => $table_data) { ${$sort_by}[$table_name] = strtolower( - $table_data['Data_length'] - + $table_data['Index_length'] + $table_data['Data_length'] + $table_data['Index_length'] ); } } else { @@ -682,40 +654,118 @@ class PMA_DatabaseInterface ); } - $tables[$each_database] = $this->copyTableProperties( - $each_tables, $each_database - ); + foreach ($each_tables as $table_name => $each_table) { + if (! isset($each_tables[$table_name]['Type']) + && isset($each_tables[$table_name]['Engine']) + ) { + // pma BC, same parts of PMA still uses 'Type' + $each_tables[$table_name]['Type'] + =& $each_tables[$table_name]['Engine']; + } elseif (! isset($each_tables[$table_name]['Engine']) + && isset($each_tables[$table_name]['Type']) + ) { + // old MySQL reports Type, newer MySQL reports Engine + $each_tables[$table_name]['Engine'] + =& $each_tables[$table_name]['Type']; + } + + // MySQL forward compatibility + // so pma could use this array as if every server + // is of version >5.0 + // todo : remove and check usage in the rest of the code, + // MySQL 5.0 is required by current PMA version + $each_tables[$table_name]['TABLE_SCHEMA'] + = $each_database; + $each_tables[$table_name]['TABLE_NAME'] + =& $each_tables[$table_name]['Name']; + $each_tables[$table_name]['ENGINE'] + =& $each_tables[$table_name]['Engine']; + $each_tables[$table_name]['VERSION'] + =& $each_tables[$table_name]['Version']; + $each_tables[$table_name]['ROW_FORMAT'] + =& $each_tables[$table_name]['Row_format']; + $each_tables[$table_name]['TABLE_ROWS'] + =& $each_tables[$table_name]['Rows']; + $each_tables[$table_name]['AVG_ROW_LENGTH'] + =& $each_tables[$table_name]['Avg_row_length']; + $each_tables[$table_name]['DATA_LENGTH'] + =& $each_tables[$table_name]['Data_length']; + $each_tables[$table_name]['MAX_DATA_LENGTH'] + =& $each_tables[$table_name]['Max_data_length']; + $each_tables[$table_name]['INDEX_LENGTH'] + =& $each_tables[$table_name]['Index_length']; + $each_tables[$table_name]['DATA_FREE'] + =& $each_tables[$table_name]['Data_free']; + $each_tables[$table_name]['AUTO_INCREMENT'] + =& $each_tables[$table_name]['Auto_increment']; + $each_tables[$table_name]['CREATE_TIME'] + =& $each_tables[$table_name]['Create_time']; + $each_tables[$table_name]['UPDATE_TIME'] + =& $each_tables[$table_name]['Update_time']; + $each_tables[$table_name]['CHECK_TIME'] + =& $each_tables[$table_name]['Check_time']; + $each_tables[$table_name]['TABLE_COLLATION'] + =& $each_tables[$table_name]['Collation']; + $each_tables[$table_name]['CHECKSUM'] + =& $each_tables[$table_name]['Checksum']; + $each_tables[$table_name]['CREATE_OPTIONS'] + =& $each_tables[$table_name]['Create_options']; + $each_tables[$table_name]['TABLE_COMMENT'] + =& $each_tables[$table_name]['Comment']; + + if (strtoupper($each_tables[$table_name]['Comment']) === 'VIEW' + && $each_tables[$table_name]['Engine'] == null + ) { + $each_tables[$table_name]['TABLE_TYPE'] = 'VIEW'; + } elseif ($each_database == 'information_schema') { + $each_tables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW'; + } else { + /** + * @todo difference between 'TEMPORARY' and 'BASE TABLE' + * but how to detect? + */ + $each_tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE'; + } + } + + $tables[$each_database] = $each_tables; } } + // cache table data + // so PMA_Table does not require to issue SHOW TABLE STATUS again $this->_cacheTableData($tables, $table); - if (! is_array($database)) { - if (isset($tables[$database])) { - return $tables[$database]; - } elseif (isset($tables[strtolower($database)])) { - // on windows with lower_case_table_names = 1 - // MySQL returns - // with SHOW DATABASES or information_schema.SCHEMATA: `Test` - // but information_schema.TABLES gives `test` - // bug #2036 - // https://sourceforge.net/p/phpmyadmin/bugs/2036/ - return $tables[strtolower($database)]; - } else { - // one database but inexact letter case match - // as Drizzle is always case insensitive, - // we can safely return the only result - if (PMA_DRIZZLE && count($tables) == 1) { - $keys = array_keys($tables); - if (strlen(array_pop($keys)) == strlen($database)) { - return array_pop($tables); - } - } - return $tables; - } - } else { + if (is_array($database)) { + return $tables; + } + + if (isset($tables[$database])) { + return $tables[$database]; + } + + if (isset($tables[/*overload*/mb_strtolower($database)])) { + // on windows with lower_case_table_names = 1 + // MySQL returns + // with SHOW DATABASES or information_schema.SCHEMATA: `Test` + // but information_schema.TABLES gives `test` + // bug #2036 + // https://sourceforge.net/p/phpmyadmin/bugs/2036/ + return $tables[/*overload*/mb_strtolower($database)]; + } + + // one database but inexact letter case match + // as Drizzle is always case insensitive, + // we can safely return the only result + if (!PMA_DRIZZLE || !count($tables) == 1) { return $tables; } + + $keys = array_keys($tables); + if (/*overload*/mb_strlen(array_pop($keys)) == /*overload*/mb_strlen($database)) { + return array_pop($tables); + } + return $tables; } /** @@ -787,7 +837,10 @@ class PMA_DatabaseInterface $tables[$table_name]['TABLE_COMMENT'] =& $tables[$table_name]['Comment']; - if (strtoupper($tables[$table_name]['Comment']) === 'VIEW' + $commentUpper = /*overload*/mb_strtoupper( + $tables[$table_name]['Comment'] + ); + if ($commentUpper === 'VIEW' && $tables[$table_name]['Engine'] == null ) { $tables[$table_name]['TABLE_TYPE'] = 'VIEW'; @@ -833,7 +886,7 @@ class PMA_DatabaseInterface * * @param string $database database * @param boolean $force_stats retrieve stats also for MySQL < 5 - * @param resource $link mysql link + * @param object $link mysql link * @param string $sort_by column to order by * @param string $sort_order ASC or DESC * @param integer $limit_offset starting offset for LIMIT @@ -856,112 +909,154 @@ class PMA_DatabaseInterface $apply_limit_and_order_manual = true; - /** - * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT - * cause MySQL does not support natural ordering, - * we have to do it afterward - */ - $limit = ''; - if (! $GLOBALS['cfg']['NaturalOrder']) { - if ($limit_count) { - $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; - } - - $apply_limit_and_order_manual = false; - } - - // get table information from information_schema - if ($database) { - $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \'' - . PMA_Util::sqlAddSlashes($database) . '\''; - } else { - $sql_where_schema = ''; - } - - if (PMA_DRIZZLE) { - // data_dictionary.table_cache may not contain any data for some - // tables, it's just a table cache - $sql = 'SELECT - s.SCHEMA_NAME, - s.DEFAULT_COLLATION_NAME'; - if ($force_stats) { - // no TABLE_CACHE data, stable results are better than - // constantly changing - $sql .= ', - COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES, - SUM(stat.NUM_ROWS) AS SCHEMA_TABLE_ROWS'; - } - $sql .= ' - FROM data_dictionary.SCHEMAS s'; - if ($force_stats) { - $engine_info = PMA_Util::cacheGet('drizzle_engines', null); - $stats_join = "LEFT JOIN (SELECT 0 NUM_ROWS) AS stat ON false"; - if (isset($engine_info['InnoDB']) - && $engine_info['InnoDB']['module_library'] == 'innobase' - ) { - $stats_join - = "LEFT JOIN data_dictionary.INNODB_SYS_TABLESTATS stat" - . " ON (t.ENGINE = 'InnoDB' AND stat.NAME" - . " = (t.TABLE_SCHEMA || '/') || t.TABLE_NAME)"; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + /** + * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT + * cause MySQL does not support natural ordering, + * we have to do it afterward + */ + $limit = ''; + if (! $GLOBALS['cfg']['NaturalOrder']) { + if ($limit_count) { + $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; } - $sql .= " - LEFT JOIN data_dictionary.TABLES t - ON t.TABLE_SCHEMA = s.SCHEMA_NAME - $stats_join"; + $apply_limit_and_order_manual = false; } - $sql .= $sql_where_schema . ' - GROUP BY s.SCHEMA_NAME - ORDER BY ' . PMA_Util::backquote($sort_by) . ' ' . $sort_order - . $limit; - } else { - $sql = 'SELECT - s.SCHEMA_NAME, - s.DEFAULT_COLLATION_NAME'; - if ($force_stats) { - $sql .= ', - COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES, - SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS, - SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH, - SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH, - SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH, - SUM(t.DATA_LENGTH + t.INDEX_LENGTH) - AS SCHEMA_LENGTH, - SUM(t.DATA_FREE) AS SCHEMA_DATA_FREE'; + + // get table information from information_schema + if ($database) { + $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \'' + . PMA_Util::sqlAddSlashes($database) . '\''; + } else { + $sql_where_schema = ''; } - $sql .= ' - FROM `information_schema`.SCHEMATA s'; - if ($force_stats) { + + if (PMA_DRIZZLE) { + // data_dictionary.table_cache may not contain any data for some + // tables, it's just a table cache + $sql = 'SELECT + s.SCHEMA_NAME, + s.DEFAULT_COLLATION_NAME'; + if ($force_stats) { + // no TABLE_CACHE data, stable results are better than + // constantly changing + $sql .= ', + COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES, + SUM(stat.NUM_ROWS) AS SCHEMA_TABLE_ROWS'; + } $sql .= ' - LEFT JOIN `information_schema`.TABLES t - ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME'; + FROM data_dictionary.SCHEMAS s'; + if ($force_stats) { + $stats_join = $this->_getDrizzeStatsJoin(); + + $sql .= " + LEFT JOIN data_dictionary.TABLES t + ON t.TABLE_SCHEMA = s.SCHEMA_NAME + $stats_join"; + } + $sql .= $sql_where_schema . ' + GROUP BY s.SCHEMA_NAME + ORDER BY ' . PMA_Util::backquote($sort_by) . ' ' . $sort_order + . $limit; + } else { + $sql = 'SELECT + s.SCHEMA_NAME, + s.DEFAULT_COLLATION_NAME'; + if ($force_stats) { + $sql .= ', + COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES, + SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS, + SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH, + SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH, + SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH, + SUM(t.DATA_LENGTH + t.INDEX_LENGTH) + AS SCHEMA_LENGTH, + SUM(t.DATA_FREE) AS SCHEMA_DATA_FREE'; + } + $sql .= ' + FROM `information_schema`.SCHEMATA s'; + if ($force_stats) { + $sql .= ' + LEFT JOIN `information_schema`.TABLES t + ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME'; + } + $sql .= $sql_where_schema . ' + GROUP BY BINARY s.SCHEMA_NAME + ORDER BY BINARY ' . PMA_Util::backquote($sort_by) + . ' ' . $sort_order + . $limit; } - $sql .= $sql_where_schema . ' - GROUP BY BINARY s.SCHEMA_NAME - ORDER BY BINARY ' . PMA_Util::backquote($sort_by) - . ' ' . $sort_order - . $limit; - } - $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link); + $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link); - $mysql_error = $this->getError($link); - if (! count($databases) && $GLOBALS['errno']) { - PMA_Util::mysqlDie($mysql_error, $sql); - } + $mysql_error = $this->getError($link); + if (! count($databases) && $GLOBALS['errno']) { + PMA_Util::mysqlDie($mysql_error, $sql); + } - // display only databases also in official database list - // f.e. to apply hide_db and only_db - $drops = array_diff( - array_keys($databases), (array) $GLOBALS['pma']->databases - ); - if (count($drops)) { + // display only databases also in official database list + // f.e. to apply hide_db and only_db + $drops = array_diff( + array_keys($databases), (array) $GLOBALS['pma']->databases + ); foreach ($drops as $drop) { unset($databases[$drop]); } - unset($drop); + } else { + $databases = array(); + foreach ($GLOBALS['pma']->databases as $database_name) { + // MySQL forward compatibility + // so pma could use this array as if every server is of version >5.0 + // todo : remove and check the rest of the code for usage, + // MySQL 5.0 or higher is required for current PMA version + $databases[$database_name]['SCHEMA_NAME'] = $database_name; + + if ($force_stats) { + include_once './libraries/mysql_charsets.inc.php'; + + $databases[$database_name]['DEFAULT_COLLATION_NAME'] + = PMA_getDbCollation($database_name); + + // get additional info about tables + $databases[$database_name]['SCHEMA_TABLES'] = 0; + $databases[$database_name]['SCHEMA_TABLE_ROWS'] = 0; + $databases[$database_name]['SCHEMA_DATA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_INDEX_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_DATA_FREE'] = 0; + + $res = $this->query( + 'SHOW TABLE STATUS FROM ' + . PMA_Util::backquote($database_name) . ';' + ); + + while ($row = $this->fetchAssoc($res)) { + $databases[$database_name]['SCHEMA_TABLES']++; + $databases[$database_name]['SCHEMA_TABLE_ROWS'] + += $row['Rows']; + $databases[$database_name]['SCHEMA_DATA_LENGTH'] + += $row['Data_length']; + $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] + += $row['Max_data_length']; + $databases[$database_name]['SCHEMA_INDEX_LENGTH'] + += $row['Index_length']; + + // for InnoDB, this does not contain the number of + // overhead bytes but the total free space + if ('InnoDB' != $row['Engine']) { + $databases[$database_name]['SCHEMA_DATA_FREE'] + += $row['Data_free']; + } + $databases[$database_name]['SCHEMA_LENGTH'] + += $row['Data_length'] + $row['Index_length']; + } + $this->freeResult($res); + unset($res); + } + } } - unset($sql_where_schema, $sql, $drops); /** * apply limit and order manually now @@ -987,6 +1082,28 @@ class PMA_DatabaseInterface return $databases; } + + /** + * Generates JOIN part for the Drizzle query to get database/table stats. + * + * @return string + */ + private function _getDrizzeStatsJoin() + { + $engine_info = PMA_Util::cacheGet('drizzle_engines'); + $stats_join = "LEFT JOIN (SELECT 0 NUM_ROWS) AS stat ON false"; + if (isset($engine_info['InnoDB']) + && $engine_info['InnoDB']['module_library'] == 'innobase' + ) { + $stats_join + = "LEFT JOIN data_dictionary.INNODB_SYS_TABLESTATS stat" + . " ON (t.ENGINE = 'InnoDB' AND stat.NAME" + . " = (t.TABLE_SCHEMA || '/') || t.TABLE_NAME)"; + } + return $stats_join; + } + + /** * usort comparison callback * @@ -1032,80 +1149,167 @@ class PMA_DatabaseInterface public function getColumnsFull($database = null, $table = null, $column = null, $link = null ) { - $columns = array(); - - $sql_wheres = array(); - $array_keys = array(); + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $sql_wheres = array(); + $array_keys = array(); + + // get columns information from information_schema + if (null !== $database) { + $sql_wheres[] = '`TABLE_SCHEMA` = \'' + . PMA_Util::sqlAddSlashes($database) . '\' '; + } else { + $array_keys[] = 'TABLE_SCHEMA'; + } + if (null !== $table) { + $sql_wheres[] = '`TABLE_NAME` = \'' + . PMA_Util::sqlAddSlashes($table) . '\' '; + } else { + $array_keys[] = 'TABLE_NAME'; + } + if (null !== $column) { + $sql_wheres[] = '`COLUMN_NAME` = \'' + . PMA_Util::sqlAddSlashes($column) . '\' '; + } else { + $array_keys[] = 'COLUMN_NAME'; + } - // get columns information from information_schema - if (null !== $database) { - $sql_wheres[] = '`TABLE_SCHEMA` = \'' - . PMA_Util::sqlAddSlashes($database) . '\' '; - } else { - $array_keys[] = 'TABLE_SCHEMA'; - } - if (null !== $table) { - $sql_wheres[] = '`TABLE_NAME` = \'' - . PMA_Util::sqlAddSlashes($table) . '\' '; - } else { - $array_keys[] = 'TABLE_NAME'; - } - if (null !== $column) { - $sql_wheres[] = '`COLUMN_NAME` = \'' - . PMA_Util::sqlAddSlashes($column) . '\' '; + // for PMA bc: + // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]` + if (PMA_DRIZZLE) { + $sql = "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, + column_name AS `Field`, + (CASE + WHEN character_maximum_length > 0 + THEN concat(lower(data_type), '(', character_maximum_length, ')') + WHEN numeric_precision > 0 OR numeric_scale > 0 + THEN concat(lower(data_type), '(', numeric_precision, + ',', numeric_scale, ')') + WHEN enum_values IS NOT NULL + THEN concat(lower(data_type), '(', enum_values, ')') + ELSE lower(data_type) END) + AS `Type`, + collation_name AS `Collation`, + (CASE is_nullable + WHEN 1 THEN 'YES' + ELSE 'NO' END) AS `Null`, + (CASE + WHEN is_used_in_primary THEN 'PRI' + ELSE '' END) AS `Key`, + column_default AS `Default`, + (CASE + WHEN is_auto_increment THEN 'auto_increment' + WHEN column_default_update + THEN 'on update ' || column_default_update + ELSE '' END) AS `Extra`, + NULL AS `Privileges`, + column_comment AS `Comment` + FROM data_dictionary.columns"; + } else { + $sql = ' + SELECT *, + `COLUMN_NAME` AS `Field`, + `COLUMN_TYPE` AS `Type`, + `COLLATION_NAME` AS `Collation`, + `IS_NULLABLE` AS `Null`, + `COLUMN_KEY` AS `Key`, + `COLUMN_DEFAULT` AS `Default`, + `EXTRA` AS `Extra`, + `PRIVILEGES` AS `Privileges`, + `COLUMN_COMMENT` AS `Comment` + FROM `information_schema`.`COLUMNS`'; + } + if (count($sql_wheres)) { + $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres); + } + return $this->fetchResult($sql, $array_keys, null, $link); } else { - $array_keys[] = 'COLUMN_NAME'; - } + $columns = array(); + if (null === $database) { + foreach ($GLOBALS['pma']->databases as $database) { + $columns[$database] = $this->getColumnsFull( + $database, null, null, $link + ); + } + return $columns; + } elseif (null === $table) { + $tables = $this->getTables($database); + foreach ($tables as $table) { + $columns[$table] = $this->getColumnsFull( + $database, $table, null, $link + ); + } + return $columns; + } + $sql = 'SHOW FULL COLUMNS FROM ' + . PMA_Util::backquote($database) . '.' . PMA_Util::backquote($table); + if (null !== $column) { + $sql .= " LIKE '" . PMA_Util::sqlAddSlashes($column, true) . "'"; + } - // for PMA bc: - // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]` - if (PMA_DRIZZLE) { - $sql = "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, - column_name AS `Field`, - (CASE - WHEN character_maximum_length > 0 - THEN concat(lower(data_type), '(', character_maximum_length, ')') - WHEN numeric_precision > 0 OR numeric_scale > 0 - THEN concat(lower(data_type), '(', numeric_precision, - ',', numeric_scale, ')') - WHEN enum_values IS NOT NULL - THEN concat(lower(data_type), '(', enum_values, ')') - ELSE lower(data_type) END) - AS `Type`, - collation_name AS `Collation`, - (CASE is_nullable - WHEN 1 THEN 'YES' - ELSE 'NO' END) AS `Null`, - (CASE - WHEN is_used_in_primary THEN 'PRI' - ELSE '' END) AS `Key`, - column_default AS `Default`, - (CASE - WHEN is_auto_increment THEN 'auto_increment' - WHEN column_default_update - THEN 'on update ' || column_default_update - ELSE '' END) AS `Extra`, - NULL AS `Privileges`, - column_comment AS `Comment` - FROM data_dictionary.columns"; - } else { - $sql = ' - SELECT *, - `COLUMN_NAME` AS `Field`, - `COLUMN_TYPE` AS `Type`, - `COLLATION_NAME` AS `Collation`, - `IS_NULLABLE` AS `Null`, - `COLUMN_KEY` AS `Key`, - `COLUMN_DEFAULT` AS `Default`, - `EXTRA` AS `Extra`, - `PRIVILEGES` AS `Privileges`, - `COLUMN_COMMENT` AS `Comment` - FROM `information_schema`.`COLUMNS`'; - } - if (count($sql_wheres)) { - $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres); + $columns = $this->fetchResult($sql, 'Field', null, $link); + $ordinal_position = 1; + foreach ($columns as $column_name => $each_column) { + + // MySQL forward compatibility + // so pma could use this array as if every server is of version >5.0 + // todo : remove and check the rest of the code for usage, + // MySQL 5.0 or higher is required for current PMA version + $columns[$column_name]['COLUMN_NAME'] + =& $columns[$column_name]['Field']; + $columns[$column_name]['COLUMN_TYPE'] + =& $columns[$column_name]['Type']; + $columns[$column_name]['COLLATION_NAME'] + =& $columns[$column_name]['Collation']; + $columns[$column_name]['IS_NULLABLE'] + =& $columns[$column_name]['Null']; + $columns[$column_name]['COLUMN_KEY'] + =& $columns[$column_name]['Key']; + $columns[$column_name]['COLUMN_DEFAULT'] + =& $columns[$column_name]['Default']; + $columns[$column_name]['EXTRA'] + =& $columns[$column_name]['Extra']; + $columns[$column_name]['PRIVILEGES'] + =& $columns[$column_name]['Privileges']; + $columns[$column_name]['COLUMN_COMMENT'] + =& $columns[$column_name]['Comment']; + + $columns[$column_name]['TABLE_CATALOG'] = null; + $columns[$column_name]['TABLE_SCHEMA'] = $database; + $columns[$column_name]['TABLE_NAME'] = $table; + $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position; + $columns[$column_name]['DATA_TYPE'] + = substr( + $columns[$column_name]['COLUMN_TYPE'], + 0, + strpos($columns[$column_name]['COLUMN_TYPE'], '(') + ); + /** + * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE + */ + $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null; + /** + * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH + */ + $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null; + $columns[$column_name]['NUMERIC_PRECISION'] = null; + $columns[$column_name]['NUMERIC_SCALE'] = null; + $columns[$column_name]['CHARACTER_SET_NAME'] + = substr( + $columns[$column_name]['COLLATION_NAME'], + 0, + strpos($columns[$column_name]['COLLATION_NAME'], '_') + ); + + $ordinal_position++; + } + + if (null !== $column) { + reset($columns); + $columns = current($columns); + } + + return $columns; } - return $this->fetchResult($sql, $array_keys, null, $link); } /** @@ -1130,7 +1334,7 @@ class PMA_DatabaseInterface // * used in primary key => PRI // * unique one-column => UNI // * indexed, one-column or first in multi-column => MUL - // Promotion of UNI to PRI in case no promary index exists + // Promotion of UNI to PRI in case no primary index exists // is done after query is executed $sql = "SELECT column_name AS `Field`, @@ -1205,6 +1409,29 @@ class PMA_DatabaseInterface if (! is_array($fields) || count($fields) == 0) { return null; } + // Check if column is a part of multiple-column index and set its 'Key'. + $indexes = PMA_Index::getFromTable($table, $database); + foreach ($fields as $field => $field_data) { + if (!empty($field_data['Key'])) { + continue; + } + + foreach ($indexes as $index) { + /** @var PMA_Index $index */ + if (!$index->hasColumn($field)) { + continue; + } + + $index_columns = $index->getColumns(); + if ($index_columns[$field]->getSeqInIndex() > 1) { + if ($index->isUnique()) { + $fields[$field]['Key'] = 'UNI'; + } else { + $fields[$field]['Key'] = 'MUL'; + } + } + } + } if (PMA_DRIZZLE) { // fix Key column, it's much simpler in PHP than in SQL $has_pk = false; @@ -1268,10 +1495,10 @@ class PMA_DatabaseInterface * Returns SQL for fetching information on table indexes (SHOW INDEXES) * * @param string $database name of database - * @param string $table name of the table whose indexes are to be retreived + * @param string $table name of the table whose indexes are to be retrieved * @param string $where additional conditions for WHERE * - * @return array $indexes + * @return string SQL for getting indexes */ public function getTableIndexesSql($database, $table, $where = null) { @@ -1344,12 +1571,9 @@ class PMA_DatabaseInterface public function getVariable( $var, $type = self::GETVAR_SESSION, $link = null ) { - if ($link === null) { - if (isset($GLOBALS['userlink'])) { - $link = $GLOBALS['userlink']; - } else { - return false; - } + $link = $this->getLink($link); + if ($link === false) { + return false; } switch ($type) { @@ -1372,41 +1596,33 @@ class PMA_DatabaseInterface * been established. It sets the connection collation, and determines the * version of MySQL which is running. * - * @param mixed $link mysql link resource|object - * @param boolean $is_controluser whether link is for control user + * @param mixed $link mysql link resource|object * * @return void */ - public function postConnect($link, $is_controluser = false) + public function postConnect($link) { - if ($is_controluser) { - /* - * FIXME: Not sure if this is right approach, but we can not - * define constants multiple time. - */ - return; - } if (! defined('PMA_MYSQL_INT_VERSION')) { - if (PMA_Util::cacheExists('PMA_MYSQL_INT_VERSION', null)) { + if (PMA_Util::cacheExists('PMA_MYSQL_INT_VERSION')) { define( 'PMA_MYSQL_INT_VERSION', - PMA_Util::cacheGet('PMA_MYSQL_INT_VERSION', null) + PMA_Util::cacheGet('PMA_MYSQL_INT_VERSION') ); define( 'PMA_MYSQL_MAJOR_VERSION', - PMA_Util::cacheGet('PMA_MYSQL_MAJOR_VERSION', null) + PMA_Util::cacheGet('PMA_MYSQL_MAJOR_VERSION') ); define( 'PMA_MYSQL_STR_VERSION', - PMA_Util::cacheGet('PMA_MYSQL_STR_VERSION', null) + PMA_Util::cacheGet('PMA_MYSQL_STR_VERSION') ); define( 'PMA_MYSQL_VERSION_COMMENT', - PMA_Util::cacheGet('PMA_MYSQL_VERSION_COMMENT', null) + PMA_Util::cacheGet('PMA_MYSQL_VERSION_COMMENT') ); define( 'PMA_DRIZZLE', - PMA_Util::cacheGet('PMA_DRIZZLE', null) + PMA_Util::cacheGet('PMA_DRIZZLE') ); } else { $version = $this->fetchSingleRow( @@ -1430,30 +1646,26 @@ class PMA_DatabaseInterface $version['@@version_comment'] ); } else { - define('PMA_MYSQL_INT_VERSION', 50015); + define('PMA_MYSQL_INT_VERSION', 50501); define('PMA_MYSQL_MAJOR_VERSION', 5); - define('PMA_MYSQL_STR_VERSION', '5.00.15'); + define('PMA_MYSQL_STR_VERSION', '5.05.01'); define('PMA_MYSQL_VERSION_COMMENT', ''); } PMA_Util::cacheSet( 'PMA_MYSQL_INT_VERSION', - PMA_MYSQL_INT_VERSION, - null + PMA_MYSQL_INT_VERSION ); PMA_Util::cacheSet( 'PMA_MYSQL_MAJOR_VERSION', - PMA_MYSQL_MAJOR_VERSION, - null + PMA_MYSQL_MAJOR_VERSION ); PMA_Util::cacheSet( 'PMA_MYSQL_STR_VERSION', - PMA_MYSQL_STR_VERSION, - null + PMA_MYSQL_STR_VERSION ); PMA_Util::cacheSet( 'PMA_MYSQL_VERSION_COMMENT', - PMA_MYSQL_VERSION_COMMENT, - null + PMA_MYSQL_VERSION_COMMENT ); /* Detect Drizzle - it does not support charsets */ @@ -1470,8 +1682,7 @@ class PMA_DatabaseInterface PMA_Util::cacheSet( 'PMA_DRIZZLE', - PMA_DRIZZLE, - null + PMA_DRIZZLE ); } } @@ -1517,7 +1728,7 @@ class PMA_DatabaseInterface } // Cache plugin list for Drizzle - if (PMA_DRIZZLE && !PMA_Util::cacheExists('drizzle_engines', null)) { + if (PMA_DRIZZLE && !PMA_Util::cacheExists('drizzle_engines')) { $sql = "SELECT p.plugin_name, m.module_library FROM data_dictionary.plugins p JOIN data_dictionary.modules m USING (module_name) @@ -1525,7 +1736,7 @@ class PMA_DatabaseInterface AND p.plugin_name NOT IN ('FunctionEngine', 'schema') AND p.is_active = 'YES'"; $engines = $this->fetchResult($sql, 'plugin_name', null, $link); - PMA_Util::cacheSet('drizzle_engines', $engines, null); + PMA_Util::cacheSet('drizzle_engines', $engines); } } @@ -1541,27 +1752,28 @@ class PMA_DatabaseInterface * // $user_name = 'John Doe' * </code> * - * @param string|mysql_result $result query or mysql result - * @param integer $row_number row to fetch the value from, - * starting at 0, with 0 being default - * @param integer|string $field field to fetch the value from, - * starting at 0, with 0 being default - * @param resource $link mysql link + * @param string $query The query to execute + * @param integer $row_number row to fetch the value from, + * starting at 0, with 0 being default + * @param integer|string $field field to fetch the value from, + * starting at 0, with 0 being default + * @param object $link mysql link * * @return mixed value of first field in first row from result * or false if not found */ - public function fetchValue($result, $row_number = 0, $field = 0, $link = null) + public function fetchValue($query, $row_number = 0, $field = 0, $link = null) { $value = false; - if (is_string($result)) { - $result = $this->tryQuery( - $result, - $link, - self::QUERY_STORE, - false - ); + $result = $this->tryQuery( + $query, + $link, + self::QUERY_STORE, + false + ); + if ($result === false) { + return false; } // return false if result is empty or false @@ -1587,7 +1799,6 @@ class PMA_DatabaseInterface if (isset($row[$field])) { $value = $row[$field]; } - unset($row); return $value; } @@ -1602,27 +1813,27 @@ class PMA_DatabaseInterface * // $user = array('id' => 123, 'name' => 'John Doe') * </code> * - * @param string|mysql_result $result query or mysql result - * @param string $type NUM|ASSOC|BOTH - * returned array should either numeric - * associativ or booth - * @param resource $link mysql link + * @param string $query The query to execute + * @param string $type NUM|ASSOC|BOTH returned array should either + * numeric associative or both + * @param object $link mysql link * * @return array|boolean first row from result * or false if result is empty */ - public function fetchSingleRow($result, $type = 'ASSOC', $link = null) + public function fetchSingleRow($query, $type = 'ASSOC', $link = null) { - if (is_string($result)) { - $result = $this->tryQuery( - $result, - $link, - self::QUERY_STORE, - false - ); + $result = $this->tryQuery( + $query, + $link, + self::QUERY_STORE, + false + ); + if ($result === false) { + return false; } - // return null if result is empty or false + // return false if result is empty or false if (! $this->numRows($result)) { return false; } @@ -1646,6 +1857,23 @@ class PMA_DatabaseInterface } /** + * Returns row or element of a row + * + * @param array $row Row to process + * @param string|null $value Which column to return + * + * @return mixed + */ + private function _fetchValue($row, $value) + { + if (is_null($value)) { + return $row; + } else { + return $row[$value]; + } + } + + /** * returns all rows in the resultset in one array * * <code> @@ -1687,27 +1915,26 @@ class PMA_DatabaseInterface * // $users['admin']['John Doe'] = '123' * </code> * - * @param string|mysql_result $result query or mysql result + * @param string $query query to execute * @param string|integer|array $key field-name or offset * used as key for array + * or array of those * @param string|integer $value value-name or offset * used as value for array - * @param resource $link mysql link - * @param mixed $options query options + * @param object $link mysql link + * @param integer $options query options * * @return array resultrows or values indexed by $key */ - public function fetchResult($result, $key = null, $value = null, + public function fetchResult($query, $key = null, $value = null, $link = null, $options = 0 ) { $resultrows = array(); - if (is_string($result)) { - $result = $this->tryQuery($result, $link, $options, false); - } + $result = $this->tryQuery($query, $link, $options, false); // return empty array if result is empty or false - if (! $result) { + if ($result === false) { return $resultrows; } @@ -1724,35 +1951,9 @@ class PMA_DatabaseInterface $fetch_function = 'fetchRow'; } - if (null === $key && null === $value) { - while ($row = $this->$fetch_function($result)) { - $resultrows[] = $row; - } - } elseif (null === $key) { + if (null === $key) { while ($row = $this->$fetch_function($result)) { - $resultrows[] = $row[$value]; - } - } elseif (null === $value) { - if (is_array($key)) { - while ($row = $this->$fetch_function($result)) { - $result_target =& $resultrows; - foreach ($key as $key_index) { - if (null === $key_index) { - $result_target =& $result_target[]; - continue; - } - - if (! isset($result_target[$row[$key_index]])) { - $result_target[$row[$key_index]] = array(); - } - $result_target =& $result_target[$row[$key_index]]; - } - $result_target = $row; - } - } else { - while ($row = $this->$fetch_function($result)) { - $resultrows[$row[$key]] = $row; - } + $resultrows[] = $this->_fetchValue($row, $value); } } else { if (is_array($key)) { @@ -1769,11 +1970,11 @@ class PMA_DatabaseInterface } $result_target =& $result_target[$row[$key_index]]; } - $result_target = $row[$value]; + $result_target = $this->_fetchValue($row, $value); } } else { while ($row = $this->$fetch_function($result)) { - $resultrows[$row[$key]] = $row[$value]; + $resultrows[$row[$key]] = $this->_fetchValue($row, $value); } } } @@ -1813,18 +2014,15 @@ class PMA_DatabaseInterface /** * returns warnings for last query * - * @param resource $link mysql link resource + * @param object $link mysql link resource * * @return array warnings */ public function getWarnings($link = null) { - if (empty($link)) { - if (isset($GLOBALS['userlink'])) { - $link = $GLOBALS['userlink']; - } else { - return array(); - } + $link = $this->getLink($link); + if ($link === false) { + return false; } return $this->fetchResult('SHOW WARNINGS', null, null, $link); @@ -1833,9 +2031,9 @@ class PMA_DatabaseInterface /** * returns an array of PROCEDURE or FUNCTION names for a db * - * @param string $db db name - * @param string $which PROCEDURE | FUNCTION - * @param resource $link mysql link + * @param string $db db name + * @param string $which PROCEDURE | FUNCTION + * @param object $link mysql link * * @return array the procedure names or function names */ @@ -1897,22 +2095,38 @@ class PMA_DatabaseInterface } $result = array(); - // Note: in http://dev.mysql.com/doc/refman/5.0/en/faqs-triggers.html - // their example uses WHERE TRIGGER_SCHEMA='dbname' so let's use this - // instead of WHERE EVENT_OBJECT_SCHEMA='dbname' - $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION' - . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT' - . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER' - . ' FROM information_schema.TRIGGERS' - . ' WHERE TRIGGER_SCHEMA= \'' . PMA_Util::sqlAddSlashes($db) . '\''; - - if (! empty($table)) { - $query .= " AND EVENT_OBJECT_TABLE = '" - . PMA_Util::sqlAddSlashes($table) . "';"; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + // Note: in http://dev.mysql.com/doc/refman/5.0/en/faqs-triggers.html + // their example uses WHERE TRIGGER_SCHEMA='dbname' so let's use this + // instead of WHERE EVENT_OBJECT_SCHEMA='dbname' + $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION' + . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT' + . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER' + . ' FROM information_schema.TRIGGERS' + . ' WHERE TRIGGER_SCHEMA ' . PMA_Util::getCollateForIS() . '=' + . ' \'' . PMA_Util::sqlAddSlashes($db) . '\''; + + if (! empty($table)) { + $query .= " AND EVENT_OBJECT_TABLE = '" + . PMA_Util::sqlAddSlashes($table) . "';"; + } + } else { + $query = "SHOW TRIGGERS FROM " . PMA_Util::backquote($db); + if (! empty($table)) { + $query .= " LIKE '" . PMA_Util::sqlAddSlashes($table, true) . "';"; + } } if ($triggers = $this->fetchResult($query)) { foreach ($triggers as $trigger) { + if ($GLOBALS['cfg']['Server']['DisableIS']) { + $trigger['TRIGGER_NAME'] = $trigger['Trigger']; + $trigger['ACTION_TIMING'] = $trigger['Timing']; + $trigger['EVENT_MANIPULATION'] = $trigger['Event']; + $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table']; + $trigger['ACTION_STATEMENT'] = $trigger['Statement']; + $trigger['DEFINER'] = $trigger['Definer']; + } $one_result = array(); $one_result['name'] = $trigger['TRIGGER_NAME']; $one_result['table'] = $trigger['EVENT_OBJECT_TABLE']; @@ -1986,7 +2200,7 @@ class PMA_DatabaseInterface $error .= '<br />' . __('Please check privileges of directory containing database.'); } else { - /* InnoDB contraints, see + /* InnoDB constraints, see * http://dev.mysql.com/doc/refman/5.0/en/ * innodb-foreign-key-constraints.html */ @@ -2004,40 +2218,163 @@ class PMA_DatabaseInterface } /** - * returns true (int > 0) if current user is superuser - * otherwise 0 + * gets the current user with host * - * @return bool Whether use is a superuser + * @return string the current user i.e. user@host + */ + public function getCurrentUser() + { + if (PMA_Util::cacheExists('mysql_cur_user')) { + return PMA_Util::cacheGet('mysql_cur_user'); + } + $user = $GLOBALS['dbi']->fetchValue('SELECT USER();'); + if ($user !== false) { + PMA_Util::cacheSet('mysql_cur_user', $user); + return PMA_Util::cacheGet('mysql_cur_user'); + } + return ''; + } + + /** + * Checks if current user is superuser + * + * @return bool Whether user is a superuser */ public function isSuperuser() { - if (PMA_Util::cacheExists('is_superuser', null)) { - return PMA_Util::cacheGet('is_superuser', null); + return self::isUserType('super'); + } + + /** + * Checks if current user has global create user/grant privilege + * or is a superuser (i.e. SELECT on mysql.users) + * while caching the result in session. + * + * @param string $type type of user to check for + * i.e. 'create', 'grant', 'super' + * + * @return bool Whether user is a given type of user + */ + public function isUserType($type) + { + if (PMA_Util::cacheExists('is_' . $type . 'user')) { + return PMA_Util::cacheGet('is_' . $type . 'user'); } // when connection failed we don't have a $userlink - if (isset($GLOBALS['userlink'])) { - if (PMA_DRIZZLE) { - // Drizzle has no authorization by default, so when no plugin is - // enabled everyone is a superuser - // Known authorization libraries: regex_policy, simple_user_policy - // Plugins limit object visibility (dbs, tables, processes), we can - // safely assume we always deal with superuser - $result = true; - } else { - // check access to mysql.user table - $result = (bool) $GLOBALS['dbi']->tryQuery( - 'SELECT COUNT(*) FROM mysql.user', - $GLOBALS['userlink'], - self::QUERY_STORE - ); + if (! isset($GLOBALS['userlink'])) { + PMA_Util::cacheSet('is_' . $type . 'user', false); + return PMA_Util::cacheGet('is_' . $type . 'user'); + } + + if (PMA_DRIZZLE) { + // Drizzle has no authorization by default, so when no plugin is + // enabled everyone is a superuser + // Known authorization libraries: regex_policy, simple_user_policy + // Plugins limit object visibility (dbs, tables, processes), we can + // safely assume we always deal with superuser + PMA_Util::cacheSet('is_' . $type . 'user', true); + return PMA_Util::cacheGet('is_' . $type . 'user'); + } + + if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') { + // Prepare query for each user type check + $query = ''; + if ($type === 'super') { + $query = 'SELECT 1 FROM mysql.user LIMIT 1'; + } elseif ($type === 'create') { + list($user, $host) = $this->_getCurrentUserAndHost(); + $query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` " + . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND " + . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; + } elseif ($type === 'grant') { + list($user, $host) = $this->_getCurrentUserAndHost(); + $query = "SELECT 1 FROM (" + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t " + . "WHERE `IS_GRANTABLE` = 'YES' AND " + . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; } - PMA_Util::cacheSet('is_superuser', $result, null); + + $is = false; + $result = $GLOBALS['dbi']->tryQuery( + $query, + $GLOBALS['userlink'], + self::QUERY_STORE + ); + if ($result) { + $is = (bool) $GLOBALS['dbi']->numRows($result); + } + $GLOBALS['dbi']->freeResult($result); + + PMA_Util::cacheSet('is_' . $type . 'user', $is); } else { - PMA_Util::cacheSet('is_superuser', false, null); + $is = false; + $grants = $GLOBALS['dbi']->fetchResult( + "SHOW GRANTS FOR CURRENT_USER();", + null, + null, + $GLOBALS['userlink'], + self::QUERY_STORE + ); + if ($grants) { + foreach ($grants as $grant) { + if ($type === 'create') { + if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false + || strpos($grant, "CREATE USER") !== false + ) { + $is = true; + break; + } + } elseif ($type === 'grant') { + if (strpos($grant, "WITH GRANT OPTION") !== false) { + $is = true; + break; + } + } + } + } + + PMA_Util::cacheSet('is_' . $type . 'user', $is); } - return PMA_Util::cacheGet('is_superuser', null); + return PMA_Util::cacheGet('is_' . $type . 'user'); + } + + /** + * Get the current user and host + * + * @return array array of username and hostname + */ + private function _getCurrentUserAndHost() + { + $user = $GLOBALS['dbi']->fetchValue("SELECT CURRENT_USER();"); + return explode("@", $user); + } + + /** + * Get the list of system schemas + * + * @return array list of system schemas + */ + public function getSystemSchemas() + { + $schemas = array( + 'information_schema', 'performance_schema', 'data_dictionary', 'mysql' + ); + $systemSchemas = array(); + foreach ($schemas as $schema) { + if ($this->isSystemSchema($schema, true)) { + $systemSchemas[] = $schema; + } + } + return $systemSchemas; } /** @@ -2053,8 +2390,10 @@ class PMA_DatabaseInterface public function isSystemSchema($schema_name, $testForMysqlSchema = false) { return strtolower($schema_name) == 'information_schema' - || (!PMA_DRIZZLE && strtolower($schema_name) == 'performance_schema') - || (PMA_DRIZZLE && strtolower($schema_name) == 'data_dictionary') + || (!PMA_DRIZZLE + && strtolower($schema_name) == 'performance_schema') + || (PMA_DRIZZLE + && strtolower($schema_name) == 'data_dictionary') || ($testForMysqlSchema && !PMA_DRIZZLE && $schema_name == 'mysql'); } @@ -2074,9 +2413,39 @@ class PMA_DatabaseInterface $user, $password, $is_controluser = false, $server = null, $auxiliary_connection = false ) { - return $this->_extension->connect( + $result = $this->_extension->connect( $user, $password, $is_controluser, $server, $auxiliary_connection ); + + if ($result) { + if (! $auxiliary_connection && ! $is_controluser) { + $GLOBALS['dbi']->postConnect($result); + } + return $result; + } + + if ($is_controluser) { + trigger_error( + __( + 'Connection for controluser as defined in your ' + . 'configuration failed.' + ), + E_USER_WARNING + ); + return false; + } + + // we could be calling $GLOBALS['dbi']->connect() to connect to another + // server, for example in the Synchronize feature, so do not + // go back to main login if it fails + if ($auxiliary_connection) { + return false; + } + + PMA_logUser($user, 'mysql-denied'); + $GLOBALS['auth_plugin']->authFails(); + + return $result; } /** @@ -2089,6 +2458,10 @@ class PMA_DatabaseInterface */ public function selectDb($dbname, $link = null) { + $link = $this->getLink($link); + if ($link === false) { + return false; + } return $this->_extension->selectDb($dbname, $link); } @@ -2162,7 +2535,11 @@ class PMA_DatabaseInterface */ public function moreResults($link = null) { - return $this->_extension->moreResults($link = null); + $link = $this->getLink($link); + if ($link === false) { + return false; + } + return $this->_extension->moreResults($link); } /** @@ -2174,17 +2551,27 @@ class PMA_DatabaseInterface */ public function nextResult($link = null) { - return $this->_extension->nextResult($link = null); + $link = $this->getLink($link); + if ($link === false) { + return false; + } + return $this->_extension->nextResult($link); } /** * Store the result returned from multi query * + * @param object $link the connection object + * * @return mixed false when empty results / result set when not empty */ - public function storeResult() + public function storeResult($link = null) { - return $this->_extension->storeResult(); + $link = $this->getLink($link); + if ($link === false) { + return false; + } + return $this->_extension->storeResult($link); } /** @@ -2196,6 +2583,10 @@ class PMA_DatabaseInterface */ public function getHostInfo($link = null) { + $link = $this->getLink($link); + if ($link === false) { + return false; + } return $this->_extension->getHostInfo($link); } @@ -2208,6 +2599,10 @@ class PMA_DatabaseInterface */ public function getProtoInfo($link = null) { + $link = $this->getLink($link); + if ($link === false) { + return false; + } return $this->_extension->getProtoInfo($link); } @@ -2230,6 +2625,10 @@ class PMA_DatabaseInterface */ public function getError($link = null) { + $link = $this->getLink($link); + if ($link === false) { + return false; + } return $this->_extension->getError($link); } @@ -2255,7 +2654,19 @@ class PMA_DatabaseInterface */ public function insertId($link = null) { - return $this->_extension->insertId($link); + $link = $this->getLink($link); + if ($link === false) { + return false; + } + // If the primary key is BIGINT we get an incorrect result + // (sometimes negative, sometimes positive) + // and in the present function we don't know if the PK is BIGINT + // so better play safe and use LAST_INSERT_ID() + // + // When no controluser is defined, using mysqli_insert_id($link) + // does not always return the last insert id due to a mixup with + // the tracking mechanism, but this works: + return $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link); } /** @@ -2264,11 +2675,20 @@ class PMA_DatabaseInterface * @param object $link the connection object * @param bool $get_from_cache whether to retrieve from cache * - * @return string|int + * @return int */ public function affectedRows($link = null, $get_from_cache = true) { - return $this->_extension->affectedRows($link, $get_from_cache); + $link = $this->getLink($link); + if ($link === false) { + return false; + } + + if ($get_from_cache) { + return $GLOBALS['cached_affected_rows']; + } else { + return $this->_extension->affectedRows($link); + } } /** @@ -2333,5 +2753,99 @@ class PMA_DatabaseInterface { return $this->_extension->fieldFlags($result, $i); } + + /** + * Gets server connection port + * + * @param array|null $server host/port/socket/persistent + * + * @return null|integer + */ + public function getServerPort($server = null) + { + if (is_null($server)) { + $server = &$GLOBALS['cfg']['Server']; + } + + if (empty($server['port'])) { + return null; + } else { + return intval($server['port']); + } + } + + /** + * Gets server connection socket + * + * @param array|null $server host/port/socket/persistent + * + * @return null|string + */ + public function getServerSocket($server = null) + { + if (is_null($server)) { + $server = &$GLOBALS['cfg']['Server']; + } + + if (empty($server['socket'])) { + return null; + } else { + return $server['socket']; + } + } + + /** + * Gets correct link object. + * + * @param mixed $link optional database link to use + * + * @return object + */ + public function getLink($link = null) + { + if ( ! is_null($link) && $link !== false) { + return $link; + } + + if (isset($GLOBALS['userlink']) && !is_null($GLOBALS['userlink'])) { + return $GLOBALS['userlink']; + } else { + return false; + } + } + + /** + * Checks if this database server is running on Amazon RDS. + * + * @return boolean + */ + public function isAmazonRds() + { + if (PMA_Util::cacheExists('is_amazon_rds')) { + return PMA_Util::cacheGet('is_amazon_rds'); + } + $sql = 'SELECT @@basedir'; + $result = $this->fetchResult($sql); + $rds = ($result[0] == '/rdsdbbin/mysql/'); + PMA_Util::cacheSet('is_amazon_rds', $rds); + + return $rds; + } + + /** + * Gets SQL for killing a process. + * + * @param int $process Process ID + * + * @return string + */ + public function getKillQuery($process) + { + if ($this->isAmazonRds()) { + return 'CALL mysql.rds_kill(' . $process . ');'; + } else { + return 'KILL ' . $process . ';'; + } + } } ?> |