Translated using Weblate (Hungarian)
[phpmyadmin.git] / libraries / DatabaseInterface.class.php
blob507c63e3499d4ffb0eb6ce558addd133a25d8b60
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Main interface for database interactions
6 * @package PhpMyAdmin-DBI
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 require_once './libraries/logging.lib.php';
13 require_once './libraries/Index.class.php';
15 /**
16 * Main interface for database interactions
18 * @package PhpMyAdmin-DBI
20 class PMA_DatabaseInterface
22 /**
23 * Force STORE_RESULT method, ignored by classic MySQL.
25 const QUERY_STORE = 1;
26 /**
27 * Do not read whole query.
29 const QUERY_UNBUFFERED = 2;
30 /**
31 * Get session variable.
33 const GETVAR_SESSION = 1;
34 /**
35 * Get global variable.
37 const GETVAR_GLOBAL = 2;
39 /**
40 * @var PMA_DBI_Extension
42 private $_extension;
44 /**
45 * Constructor
47 * @param PMA_DBI_Extension $ext Object to be used for database queries
49 public function __construct(PMA_DBI_Extension $ext)
51 $this->_extension = $ext;
54 /**
55 * Checks whether database extension is loaded
57 * @param string $extension mysql extension to check
59 * @return bool
61 public static function checkDbExtension($extension = 'mysql')
63 if ($extension == 'drizzle' && function_exists('drizzle_create')) {
64 return true;
65 } else if (function_exists($extension . '_connect')) {
66 return true;
68 return false;
71 /**
72 * runs a query
74 * @param string $query SQL query to execute
75 * @param mixed $link optional database link to use
76 * @param int $options optional query options
77 * @param bool $cache_affected_rows whether to cache affected rows
79 * @return mixed
81 public function query($query, $link = null, $options = 0,
82 $cache_affected_rows = true
83 ) {
84 $res = $this->tryQuery($query, $link, $options, $cache_affected_rows)
85 or PMA_Util::mysqlDie($this->getError($link), $query);
86 return $res;
90 /**
91 * Caches table data so PMA_Table does not require to issue
92 * SHOW TABLE STATUS again
94 * @param array $tables information for tables of some databases
95 * @param string|bool $table table name or false
97 * @return void
99 private function _cacheTableData($tables, $table)
101 // Note: I don't see why we would need array_merge_recursive() here,
102 // as it creates double entries for the same table (for example a double
103 // entry for Comment when changing the storage engine in Operations)
104 // Note 2: Instead of array_merge(), simply use the + operator because
105 // array_merge() renumbers numeric keys starting with 0, therefore
106 // we would lose a db name that consists only of numbers
108 foreach ($tables as $one_database => $its_tables) {
109 if (isset(PMA_Table::$cache[$one_database])) {
110 // the + operator does not do the intended effect
111 // when the cache for one table already exists
112 if ($table
113 && isset(PMA_Table::$cache[$one_database][$table])
115 unset(PMA_Table::$cache[$one_database][$table]);
117 PMA_Table::$cache[$one_database]
118 = PMA_Table::$cache[$one_database] + $tables[$one_database];
119 } else {
120 PMA_Table::$cache[$one_database] = $tables[$one_database];
126 * Stores query data into session data for debugging purposes
128 * @param string $query Query text
129 * @param object $link database link
130 * @param object $result Query result
131 * @param integer $time Time to execute query
133 * @return void
135 private function _dbgQuery($query, $link, $result, $time)
137 $hash = md5($query);
139 if (isset($_SESSION['debug']['queries'][$hash])) {
140 $_SESSION['debug']['queries'][$hash]['count']++;
141 } else {
142 $_SESSION['debug']['queries'][$hash] = array();
143 $error_message = $this->getError($link);
144 if ($result == false && is_string($error_message)) {
145 $_SESSION['debug']['queries'][$hash]['error']
146 = '<b style="color:red">'
147 . htmlspecialchars($error_message) . '</b>';
149 $_SESSION['debug']['queries'][$hash]['count'] = 1;
150 $_SESSION['debug']['queries'][$hash]['query'] = htmlspecialchars($query);
151 $_SESSION['debug']['queries'][$hash]['time'] = $time;
154 $_SESSION['debug']['queries'][$hash]['trace'][] = PMA_Error::formatBacktrace(
155 debug_backtrace(),
156 " ",
157 "\n"
162 * runs a query and returns the result
164 * @param string $query query to run
165 * @param object $link mysql link resource
166 * @param integer $options query options
167 * @param bool $cache_affected_rows whether to cache affected row
169 * @return mixed
171 public function tryQuery($query, $link = null, $options = 0,
172 $cache_affected_rows = true
174 $link = $this->getLink($link);
175 if ($link === false) {
176 return false;
179 if ($GLOBALS['cfg']['DBG']['sql']) {
180 $time = microtime(true);
183 $result = $this->_extension->realQuery($query, $link, $options);
185 if ($cache_affected_rows) {
186 $GLOBALS['cached_affected_rows'] = $this->affectedRows($link, false);
189 if ($GLOBALS['cfg']['DBG']['sql']) {
190 $time = microtime(true) - $time;
191 $this->_dbgQuery($query, $link, $result, $time);
193 if ($result != false && PMA_Tracker::isActive() == true ) {
194 PMA_Tracker::handleQuery($query);
197 return $result;
201 * Run multi query statement and return results
203 * @param string $multi_query multi query statement to execute
204 * @param mysqli $link mysqli object
206 * @return mysqli_result collection | boolean(false)
208 public function tryMultiQuery($multi_query = '', $link = null)
210 $link = $this->getLink($link);
211 if ($link === false) {
212 return false;
215 return $this->_extension->realMultiQuery($link, $multi_query);
219 * converts charset of a mysql message, usually coming from mysql_error(),
220 * into PMA charset, usually UTF-8
221 * uses language to charset mapping from mysql/share/errmsg.txt
222 * and charset names to ISO charset from information_schema.CHARACTER_SETS
224 * @param string $message the message
226 * @return string $message
228 public function convertMessage($message)
230 // latin always last!
231 $encodings = array(
232 'japanese' => 'EUC-JP', //'ujis',
233 'japanese-sjis' => 'Shift-JIS', //'sjis',
234 'korean' => 'EUC-KR', //'euckr',
235 'russian' => 'KOI8-R', //'koi8r',
236 'ukrainian' => 'KOI8-U', //'koi8u',
237 'greek' => 'ISO-8859-7', //'greek',
238 'serbian' => 'CP1250', //'cp1250',
239 'estonian' => 'ISO-8859-13', //'latin7',
240 'slovak' => 'ISO-8859-2', //'latin2',
241 'czech' => 'ISO-8859-2', //'latin2',
242 'hungarian' => 'ISO-8859-2', //'latin2',
243 'polish' => 'ISO-8859-2', //'latin2',
244 'romanian' => 'ISO-8859-2', //'latin2',
245 'spanish' => 'CP1252', //'latin1',
246 'swedish' => 'CP1252', //'latin1',
247 'italian' => 'CP1252', //'latin1',
248 'norwegian-ny' => 'CP1252', //'latin1',
249 'norwegian' => 'CP1252', //'latin1',
250 'portuguese' => 'CP1252', //'latin1',
251 'danish' => 'CP1252', //'latin1',
252 'dutch' => 'CP1252', //'latin1',
253 'english' => 'CP1252', //'latin1',
254 'french' => 'CP1252', //'latin1',
255 'german' => 'CP1252', //'latin1',
258 $server_language = $this->fetchValue(
259 'SHOW VARIABLES LIKE \'language\';',
263 if ($server_language) {
264 $found = array();
265 $match = preg_match(
266 '&(?:\\\|\\/)([^\\\\\/]*)(?:\\\|\\/)$&i',
267 $server_language,
268 $found
270 if ($match) {
271 $server_language = $found[1];
275 if (! empty($server_language) && isset($encodings[$server_language])) {
276 $encoding = $encodings[$server_language];
277 } else {
278 /* Fallback to CP1252 if we can not detect */
279 $encoding = 'CP1252';
282 return PMA_convertString($encoding, 'utf-8', $message);
286 * returns array with table names for given db
288 * @param string $database name of database
289 * @param mixed $link mysql link resource|object
291 * @return array tables names
293 public function getTables($database, $link = null)
295 return $this->fetchResult(
296 'SHOW TABLES FROM ' . PMA_Util::backquote($database) . ';',
297 null,
299 $link,
300 self::QUERY_STORE
305 * returns a segment of the SQL WHERE clause regarding table name and type
307 * @param string|bool $table table or false
308 * @param boolean $tbl_is_group $table is a table group
309 * @param string $table_type whether table or view
311 * @return string a segment of the WHERE clause
313 private function _getTableCondition($table, $tbl_is_group, $table_type)
315 // get table information from information_schema
316 if ($table && is_string($table)) {
317 if (true === $tbl_is_group) {
318 $sql_where_table = 'AND t.`TABLE_NAME` LIKE \''
319 . PMA_Util::escapeMysqlWildcards(
320 PMA_Util::sqlAddSlashes($table)
322 . '%\'';
323 } else {
324 $sql_where_table = 'AND t.`TABLE_NAME` = \''
325 . PMA_Util::sqlAddSlashes($table) . '\'';
327 } else {
328 $sql_where_table = '';
331 if ($table_type) {
332 if ($table_type == 'view') {
333 if (PMA_DRIZZLE) {
334 $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE'";
335 } else {
336 $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE TABLE'";
338 } else if ($table_type == 'table') {
339 if (PMA_DRIZZLE) {
340 $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE'";
341 } else {
342 $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE TABLE'";
346 return $sql_where_table;
350 * returns the beginning of the SQL statement to fetch the list of tables
352 * @param string[] $this_databases databases to list
353 * @param string $sql_where_table additional condition
355 * @return string the SQL statement
357 private function _getSqlForTablesFull($this_databases, $sql_where_table)
359 if (PMA_DRIZZLE) {
360 $stats_join = $this->_getDrizzeStatsJoin();
362 // data_dictionary.table_cache may not contain any data
363 // for some tables, it's just a table cache
364 // auto_increment == 0 is cast to NULL because currently
365 // (2011.03.13 GA)
366 // Drizzle doesn't provide correct value
367 $sql = "
368 SELECT t.*,
369 t.TABLE_SCHEMA AS `Db`,
370 t.TABLE_NAME AS `Name`,
371 t.TABLE_TYPE AS `TABLE_TYPE`,
372 t.ENGINE AS `Engine`,
373 t.ENGINE AS `Type`,
374 t.TABLE_VERSION AS `Version`,-- VERSION
375 t.ROW_FORMAT AS `Row_format`,
376 coalesce(tc.ROWS, stat.NUM_ROWS)
377 AS `Rows`,-- TABLE_ROWS,
378 coalesce(tc.ROWS, stat.NUM_ROWS)
379 AS `TABLE_ROWS`,
380 tc.AVG_ROW_LENGTH AS `Avg_row_length`, -- AVG_ROW_LENGTH
381 tc.TABLE_SIZE AS `Data_length`, -- DATA_LENGTH
382 NULL AS `Max_data_length`, -- MAX_DATA_LENGTH
383 NULL AS `Index_length`, -- INDEX_LENGTH
384 NULL AS `Data_free`, -- DATA_FREE
385 nullif(t.AUTO_INCREMENT, 0)
386 AS `Auto_increment`,
387 t.TABLE_CREATION_TIME AS `Create_time`, -- CREATE_TIME
388 t.TABLE_UPDATE_TIME AS `Update_time`, -- UPDATE_TIME
389 NULL AS `Check_time`, -- CHECK_TIME
390 t.TABLE_COLLATION AS `Collation`,
391 NULL AS `Checksum`, -- CHECKSUM
392 NULL AS `Create_options`, -- CREATE_OPTIONS
393 t.TABLE_COMMENT AS `Comment`
394 FROM data_dictionary.TABLES t
395 LEFT JOIN data_dictionary.TABLE_CACHE tc
396 ON tc.TABLE_SCHEMA = t.TABLE_SCHEMA AND tc.TABLE_NAME
397 = t.TABLE_NAME
398 $stats_join
399 WHERE t.TABLE_SCHEMA IN ('" . implode("', '", $this_databases) . "')
400 " . $sql_where_table;
401 } else {
402 $sql = '
403 SELECT *,
404 `TABLE_SCHEMA` AS `Db`,
405 `TABLE_NAME` AS `Name`,
406 `TABLE_TYPE` AS `TABLE_TYPE`,
407 `ENGINE` AS `Engine`,
408 `ENGINE` AS `Type`,
409 `VERSION` AS `Version`,
410 `ROW_FORMAT` AS `Row_format`,
411 `TABLE_ROWS` AS `Rows`,
412 `AVG_ROW_LENGTH` AS `Avg_row_length`,
413 `DATA_LENGTH` AS `Data_length`,
414 `MAX_DATA_LENGTH` AS `Max_data_length`,
415 `INDEX_LENGTH` AS `Index_length`,
416 `DATA_FREE` AS `Data_free`,
417 `AUTO_INCREMENT` AS `Auto_increment`,
418 `CREATE_TIME` AS `Create_time`,
419 `UPDATE_TIME` AS `Update_time`,
420 `CHECK_TIME` AS `Check_time`,
421 `TABLE_COLLATION` AS `Collation`,
422 `CHECKSUM` AS `Checksum`,
423 `CREATE_OPTIONS` AS `Create_options`,
424 `TABLE_COMMENT` AS `Comment`
425 FROM `information_schema`.`TABLES` t
426 WHERE ' . (PMA_IS_WINDOWS ? '' : 'BINARY') . ' `TABLE_SCHEMA`
427 IN (\'' . implode("', '", $this_databases) . '\')
428 ' . $sql_where_table;
430 return $sql;
434 * returns array of all tables in given db or dbs
435 * this function expects unquoted names:
436 * RIGHT: my_database
437 * WRONG: `my_database`
438 * WRONG: my\_database
439 * if $tbl_is_group is true, $table is used as filter for table names
441 * <code>
442 * $GLOBALS['dbi']->getTablesFull('my_database');
443 * $GLOBALS['dbi']->getTablesFull('my_database', 'my_table'));
444 * $GLOBALS['dbi']->getTablesFull('my_database', 'my_tables_', true));
445 * </code>
447 * @param string $database database
448 * @param string|bool $table table name or false
449 * @param boolean $tbl_is_group $table is a table group
450 * @param mixed $link mysql link
451 * @param integer $limit_offset zero-based offset for the count
452 * @param boolean|integer $limit_count number of tables to return
453 * @param string $sort_by table attribute to sort by
454 * @param string $sort_order direction to sort (ASC or DESC)
455 * @param string $table_type whether table or view
457 * @todo move into PMA_Table
459 * @return array list of tables in given db(s)
461 public function getTablesFull($database, $table = false,
462 $tbl_is_group = false, $link = null, $limit_offset = 0,
463 $limit_count = false, $sort_by = 'Name', $sort_order = 'ASC',
464 $table_type = null
466 if (true === $limit_count) {
467 $limit_count = $GLOBALS['cfg']['MaxTableList'];
469 // prepare and check parameters
470 if (! is_array($database)) {
471 $databases = array($database);
472 } else {
473 $databases = $database;
476 $tables = array();
478 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
479 $sql_where_table = $this->_getTableCondition(
480 $table, $tbl_is_group, $table_type
483 // for PMA bc:
484 // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME`
486 // on non-Windows servers,
487 // added BINARY in the WHERE clause to force a case sensitive
488 // comparison (if we are looking for the db Aa we don't want
489 // to find the db aa)
490 $this_databases = array_map('PMA_Util::sqlAddSlashes', $databases);
492 $sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table);
494 // Sort the tables
495 $sql .= " ORDER BY $sort_by $sort_order";
497 if ($limit_count) {
498 $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
501 $tables = $this->fetchResult(
502 $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link
505 if (PMA_DRIZZLE) {
506 // correct I_S and D_D names returned by D_D.TABLES -
507 // Drizzle generally uses lower case for them,
508 // but TABLES returns uppercase
509 foreach ((array)$database as $db) {
510 $db_upper = /*overload*/mb_strtoupper($db);
511 if (!isset($tables[$db]) && isset($tables[$db_upper])) {
512 $tables[$db] = $tables[$db_upper];
513 unset($tables[$db_upper]);
518 if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
519 // here, the array's first key is by schema name
520 foreach ($tables as $one_database_name => $one_database_tables) {
521 uksort($one_database_tables, 'strnatcasecmp');
523 if ($sort_order == 'DESC') {
524 $one_database_tables = array_reverse($one_database_tables);
526 $tables[$one_database_name] = $one_database_tables;
528 } else if ($sort_by == 'Data_length') { // Size = Data_length + Index_length
529 foreach ($tables as $one_database_name => $one_database_tables) {
530 uasort(
531 $one_database_tables,
532 function ($a, $b) {
533 $aLength = $a['Data_length'] + $a['Index_length'];
534 $bLength = $b['Data_length'] + $b['Index_length'];
535 return ($aLength == $bLength)
537 : ($aLength < $bLength) ? -1 : 1;
541 if ($sort_order == 'DESC') {
542 $one_database_tables = array_reverse($one_database_tables);
544 $tables[$one_database_name] = $one_database_tables;
547 } // end (get information from table schema)
549 // If permissions are wrong on even one database directory,
550 // information_schema does not return any table info for any database
551 // this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002
552 if (empty($tables) && !PMA_DRIZZLE) {
553 foreach ($databases as $each_database) {
554 if ($table || (true === $tbl_is_group) || $table_type) {
555 $sql = 'SHOW TABLE STATUS FROM '
556 . PMA_Util::backquote($each_database)
557 . ' WHERE';
558 $needAnd = false;
559 if ($table || (true === $tbl_is_group)) {
560 $sql .= " `Name` LIKE '"
561 . PMA_Util::escapeMysqlWildcards(
562 PMA_Util::sqlAddSlashes($table, true)
564 . "%'";
565 $needAnd = true;
567 if ($table_type) {
568 if ($needAnd) {
569 $sql .= " AND";
571 if ($table_type == 'view') {
572 $sql .= " `Comment` = 'VIEW'";
573 } else if ($table_type == 'table') {
574 $sql .= " `Comment` != 'VIEW'";
577 } else {
578 $sql = 'SHOW TABLE STATUS FROM '
579 . PMA_Util::backquote($each_database);
582 $useStatusCache = false;
584 if (extension_loaded('apc')
585 && isset($GLOBALS['cfg']['Server']['StatusCacheDatabases'])
586 && ! empty($GLOBALS['cfg']['Server']['StatusCacheLifetime'])
588 $statusCacheDatabases
589 = (array) $GLOBALS['cfg']['Server']['StatusCacheDatabases'];
590 if (in_array($each_database, $statusCacheDatabases)) {
591 $useStatusCache = true;
595 $each_tables = null;
597 if ($useStatusCache) {
598 $cacheKey = 'phpMyAdmin_tableStatus_'
599 . sha1($GLOBALS['cfg']['Server']['host'] . '_' . $sql);
601 $each_tables = apc_fetch($cacheKey);
604 if (! $each_tables) {
605 $each_tables = $this->fetchResult($sql, 'Name', null, $link);
608 if ($useStatusCache) {
609 apc_store(
610 $cacheKey, $each_tables,
611 $GLOBALS['cfg']['Server']['StatusCacheLifetime']
615 // Sort naturally if the config allows it and we're sorting
616 // the Name column.
617 if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
618 uksort($each_tables, 'strnatcasecmp');
620 if ($sort_order == 'DESC') {
621 $each_tables = array_reverse($each_tables);
623 } else {
624 // Prepare to sort by creating array of the selected sort
625 // value to pass to array_multisort
627 // Size = Data_length + Index_length
628 if ($sort_by == 'Data_length') {
629 foreach ($each_tables as $table_name => $table_data) {
630 ${$sort_by}[$table_name] = strtolower(
631 $table_data['Data_length'] + $table_data['Index_length']
634 } else {
635 foreach ($each_tables as $table_name => $table_data) {
636 ${$sort_by}[$table_name]
637 = strtolower($table_data[$sort_by]);
641 if ($sort_order == 'DESC') {
642 array_multisort($$sort_by, SORT_DESC, $each_tables);
643 } else {
644 array_multisort($$sort_by, SORT_ASC, $each_tables);
647 // cleanup the temporary sort array
648 unset($$sort_by);
651 if ($limit_count) {
652 $each_tables = array_slice(
653 $each_tables, $limit_offset, $limit_count
657 foreach ($each_tables as $table_name => $each_table) {
658 if (! isset($each_tables[$table_name]['Type'])
659 && isset($each_tables[$table_name]['Engine'])
661 // pma BC, same parts of PMA still uses 'Type'
662 $each_tables[$table_name]['Type']
663 =& $each_tables[$table_name]['Engine'];
664 } elseif (! isset($each_tables[$table_name]['Engine'])
665 && isset($each_tables[$table_name]['Type'])
667 // old MySQL reports Type, newer MySQL reports Engine
668 $each_tables[$table_name]['Engine']
669 =& $each_tables[$table_name]['Type'];
672 // MySQL forward compatibility
673 // so pma could use this array as if every server
674 // is of version >5.0
675 // todo : remove and check usage in the rest of the code,
676 // MySQL 5.0 is required by current PMA version
677 $each_tables[$table_name]['TABLE_SCHEMA']
678 = $each_database;
679 $each_tables[$table_name]['TABLE_NAME']
680 =& $each_tables[$table_name]['Name'];
681 $each_tables[$table_name]['ENGINE']
682 =& $each_tables[$table_name]['Engine'];
683 $each_tables[$table_name]['VERSION']
684 =& $each_tables[$table_name]['Version'];
685 $each_tables[$table_name]['ROW_FORMAT']
686 =& $each_tables[$table_name]['Row_format'];
687 $each_tables[$table_name]['TABLE_ROWS']
688 =& $each_tables[$table_name]['Rows'];
689 $each_tables[$table_name]['AVG_ROW_LENGTH']
690 =& $each_tables[$table_name]['Avg_row_length'];
691 $each_tables[$table_name]['DATA_LENGTH']
692 =& $each_tables[$table_name]['Data_length'];
693 $each_tables[$table_name]['MAX_DATA_LENGTH']
694 =& $each_tables[$table_name]['Max_data_length'];
695 $each_tables[$table_name]['INDEX_LENGTH']
696 =& $each_tables[$table_name]['Index_length'];
697 $each_tables[$table_name]['DATA_FREE']
698 =& $each_tables[$table_name]['Data_free'];
699 $each_tables[$table_name]['AUTO_INCREMENT']
700 =& $each_tables[$table_name]['Auto_increment'];
701 $each_tables[$table_name]['CREATE_TIME']
702 =& $each_tables[$table_name]['Create_time'];
703 $each_tables[$table_name]['UPDATE_TIME']
704 =& $each_tables[$table_name]['Update_time'];
705 $each_tables[$table_name]['CHECK_TIME']
706 =& $each_tables[$table_name]['Check_time'];
707 $each_tables[$table_name]['TABLE_COLLATION']
708 =& $each_tables[$table_name]['Collation'];
709 $each_tables[$table_name]['CHECKSUM']
710 =& $each_tables[$table_name]['Checksum'];
711 $each_tables[$table_name]['CREATE_OPTIONS']
712 =& $each_tables[$table_name]['Create_options'];
713 $each_tables[$table_name]['TABLE_COMMENT']
714 =& $each_tables[$table_name]['Comment'];
716 if (strtoupper($each_tables[$table_name]['Comment']) === 'VIEW'
717 && $each_tables[$table_name]['Engine'] == null
719 $each_tables[$table_name]['TABLE_TYPE'] = 'VIEW';
720 } elseif ($each_database == 'information_schema') {
721 $each_tables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW';
722 } else {
724 * @todo difference between 'TEMPORARY' and 'BASE TABLE'
725 * but how to detect?
727 $each_tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE';
731 $tables[$each_database] = $each_tables;
735 // cache table data
736 // so PMA_Table does not require to issue SHOW TABLE STATUS again
737 $this->_cacheTableData($tables, $table);
739 if (is_array($database)) {
740 return $tables;
743 if (isset($tables[$database])) {
744 return $tables[$database];
747 if (isset($tables[/*overload*/mb_strtolower($database)])) {
748 // on windows with lower_case_table_names = 1
749 // MySQL returns
750 // with SHOW DATABASES or information_schema.SCHEMATA: `Test`
751 // but information_schema.TABLES gives `test`
752 // bug #2036
753 // https://sourceforge.net/p/phpmyadmin/bugs/2036/
754 return $tables[/*overload*/mb_strtolower($database)];
757 // one database but inexact letter case match
758 // as Drizzle is always case insensitive,
759 // we can safely return the only result
760 if (!PMA_DRIZZLE || !count($tables) == 1) {
761 return $tables;
764 $keys = array_keys($tables);
765 if (/*overload*/mb_strlen(array_pop($keys)) == /*overload*/mb_strlen($database)) {
766 return array_pop($tables);
768 return $tables;
772 * Copies the table properties to the set of property names used by PMA.
774 * @param array $tables array of table properties
775 * @param string $database database name
777 * @return array array with added properties
779 public function copyTableProperties($tables, $database)
781 foreach ($tables as $table_name => $each_table) {
782 if (! isset($tables[$table_name]['Type'])
783 && isset($tables[$table_name]['Engine'])
785 // pma BC, same parts of PMA still uses 'Type'
786 $tables[$table_name]['Type']
787 =& $tables[$table_name]['Engine'];
788 } elseif (! isset($tables[$table_name]['Engine'])
789 && isset($tables[$table_name]['Type'])
791 // old MySQL reports Type, newer MySQL reports Engine
792 $tables[$table_name]['Engine']
793 =& $tables[$table_name]['Type'];
796 // MySQL forward compatibility
797 // so pma could use this array as if every server
798 // is of version >5.0
799 // todo : remove and check usage in the rest of the code,
800 // MySQL 5.0 is required by current PMA version
801 $tables[$table_name]['TABLE_SCHEMA']
802 = $database;
803 $tables[$table_name]['TABLE_NAME']
804 =& $tables[$table_name]['Name'];
805 $tables[$table_name]['ENGINE']
806 =& $tables[$table_name]['Engine'];
807 $tables[$table_name]['VERSION']
808 =& $tables[$table_name]['Version'];
809 $tables[$table_name]['ROW_FORMAT']
810 =& $tables[$table_name]['Row_format'];
811 $tables[$table_name]['TABLE_ROWS']
812 =& $tables[$table_name]['Rows'];
813 $tables[$table_name]['AVG_ROW_LENGTH']
814 =& $tables[$table_name]['Avg_row_length'];
815 $tables[$table_name]['DATA_LENGTH']
816 =& $tables[$table_name]['Data_length'];
817 $tables[$table_name]['MAX_DATA_LENGTH']
818 =& $tables[$table_name]['Max_data_length'];
819 $tables[$table_name]['INDEX_LENGTH']
820 =& $tables[$table_name]['Index_length'];
821 $tables[$table_name]['DATA_FREE']
822 =& $tables[$table_name]['Data_free'];
823 $tables[$table_name]['AUTO_INCREMENT']
824 =& $tables[$table_name]['Auto_increment'];
825 $tables[$table_name]['CREATE_TIME']
826 =& $tables[$table_name]['Create_time'];
827 $tables[$table_name]['UPDATE_TIME']
828 =& $tables[$table_name]['Update_time'];
829 $tables[$table_name]['CHECK_TIME']
830 =& $tables[$table_name]['Check_time'];
831 $tables[$table_name]['TABLE_COLLATION']
832 =& $tables[$table_name]['Collation'];
833 $tables[$table_name]['CHECKSUM']
834 =& $tables[$table_name]['Checksum'];
835 $tables[$table_name]['CREATE_OPTIONS']
836 =& $tables[$table_name]['Create_options'];
837 $tables[$table_name]['TABLE_COMMENT']
838 =& $tables[$table_name]['Comment'];
840 $commentUpper = /*overload*/mb_strtoupper(
841 $tables[$table_name]['Comment']
843 if ($commentUpper === 'VIEW'
844 && $tables[$table_name]['Engine'] == null
846 $tables[$table_name]['TABLE_TYPE'] = 'VIEW';
847 } else {
849 * @todo difference between 'TEMPORARY' and 'BASE TABLE'
850 * but how to detect?
852 $tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE';
855 return $tables;
859 * Get VIEWs in a particular database
861 * @param string $db Database name to look in
863 * @return array $views Set of VIEWs inside the database
865 public function getVirtualTables($db)
868 $tables_full = $this->getTablesFull($db);
869 $views = array();
871 foreach ($tables_full as $table=>$tmp) {
873 if (PMA_Table::isView($db, $table)) {
874 $views[] = $table;
879 return $views;
885 * returns array with databases containing extended infos about them
887 * @param string $database database
888 * @param boolean $force_stats retrieve stats also for MySQL < 5
889 * @param object $link mysql link
890 * @param string $sort_by column to order by
891 * @param string $sort_order ASC or DESC
892 * @param integer $limit_offset starting offset for LIMIT
893 * @param bool|int $limit_count row count for LIMIT or true
894 * for $GLOBALS['cfg']['MaxDbList']
896 * @todo move into PMA_List_Database?
898 * @return array $databases
900 public function getDatabasesFull($database = null, $force_stats = false,
901 $link = null, $sort_by = 'SCHEMA_NAME', $sort_order = 'ASC',
902 $limit_offset = 0, $limit_count = false
904 $sort_order = strtoupper($sort_order);
906 if (true === $limit_count) {
907 $limit_count = $GLOBALS['cfg']['MaxDbList'];
910 $apply_limit_and_order_manual = true;
912 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
914 * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT
915 * cause MySQL does not support natural ordering,
916 * we have to do it afterward
918 $limit = '';
919 if (! $GLOBALS['cfg']['NaturalOrder']) {
920 if ($limit_count) {
921 $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
924 $apply_limit_and_order_manual = false;
927 // get table information from information_schema
928 if ($database) {
929 $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \''
930 . PMA_Util::sqlAddSlashes($database) . '\'';
931 } else {
932 $sql_where_schema = '';
935 if (PMA_DRIZZLE) {
936 // data_dictionary.table_cache may not contain any data for some
937 // tables, it's just a table cache
938 $sql = 'SELECT
939 s.SCHEMA_NAME,
940 s.DEFAULT_COLLATION_NAME';
941 if ($force_stats) {
942 // no TABLE_CACHE data, stable results are better than
943 // constantly changing
944 $sql .= ',
945 COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,
946 SUM(stat.NUM_ROWS) AS SCHEMA_TABLE_ROWS';
948 $sql .= '
949 FROM data_dictionary.SCHEMAS s';
950 if ($force_stats) {
951 $stats_join = $this->_getDrizzeStatsJoin();
953 $sql .= "
954 LEFT JOIN data_dictionary.TABLES t
955 ON t.TABLE_SCHEMA = s.SCHEMA_NAME
956 $stats_join";
958 $sql .= $sql_where_schema . '
959 GROUP BY s.SCHEMA_NAME
960 ORDER BY ' . PMA_Util::backquote($sort_by) . ' ' . $sort_order
961 . $limit;
962 } else {
963 $sql = 'SELECT
964 s.SCHEMA_NAME,
965 s.DEFAULT_COLLATION_NAME';
966 if ($force_stats) {
967 $sql .= ',
968 COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,
969 SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS,
970 SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH,
971 SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH,
972 SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH,
973 SUM(t.DATA_LENGTH + t.INDEX_LENGTH)
974 AS SCHEMA_LENGTH,
975 SUM(t.DATA_FREE) AS SCHEMA_DATA_FREE';
977 $sql .= '
978 FROM `information_schema`.SCHEMATA s';
979 if ($force_stats) {
980 $sql .= '
981 LEFT JOIN `information_schema`.TABLES t
982 ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME';
984 $sql .= $sql_where_schema . '
985 GROUP BY BINARY s.SCHEMA_NAME
986 ORDER BY BINARY ' . PMA_Util::backquote($sort_by)
987 . ' ' . $sort_order
988 . $limit;
991 $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link);
993 $mysql_error = $this->getError($link);
994 if (! count($databases) && $GLOBALS['errno']) {
995 PMA_Util::mysqlDie($mysql_error, $sql);
998 // display only databases also in official database list
999 // f.e. to apply hide_db and only_db
1000 $drops = array_diff(
1001 array_keys($databases), (array) $GLOBALS['pma']->databases
1003 foreach ($drops as $drop) {
1004 unset($databases[$drop]);
1006 } else {
1007 $databases = array();
1008 foreach ($GLOBALS['pma']->databases as $database_name) {
1009 // MySQL forward compatibility
1010 // so pma could use this array as if every server is of version >5.0
1011 // todo : remove and check the rest of the code for usage,
1012 // MySQL 5.0 or higher is required for current PMA version
1013 $databases[$database_name]['SCHEMA_NAME'] = $database_name;
1015 if ($force_stats) {
1016 include_once './libraries/mysql_charsets.inc.php';
1018 $databases[$database_name]['DEFAULT_COLLATION_NAME']
1019 = PMA_getDbCollation($database_name);
1021 // get additional info about tables
1022 $databases[$database_name]['SCHEMA_TABLES'] = 0;
1023 $databases[$database_name]['SCHEMA_TABLE_ROWS'] = 0;
1024 $databases[$database_name]['SCHEMA_DATA_LENGTH'] = 0;
1025 $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] = 0;
1026 $databases[$database_name]['SCHEMA_INDEX_LENGTH'] = 0;
1027 $databases[$database_name]['SCHEMA_LENGTH'] = 0;
1028 $databases[$database_name]['SCHEMA_DATA_FREE'] = 0;
1030 $res = $this->query(
1031 'SHOW TABLE STATUS FROM '
1032 . PMA_Util::backquote($database_name) . ';'
1035 while ($row = $this->fetchAssoc($res)) {
1036 $databases[$database_name]['SCHEMA_TABLES']++;
1037 $databases[$database_name]['SCHEMA_TABLE_ROWS']
1038 += $row['Rows'];
1039 $databases[$database_name]['SCHEMA_DATA_LENGTH']
1040 += $row['Data_length'];
1041 $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH']
1042 += $row['Max_data_length'];
1043 $databases[$database_name]['SCHEMA_INDEX_LENGTH']
1044 += $row['Index_length'];
1046 // for InnoDB, this does not contain the number of
1047 // overhead bytes but the total free space
1048 if ('InnoDB' != $row['Engine']) {
1049 $databases[$database_name]['SCHEMA_DATA_FREE']
1050 += $row['Data_free'];
1052 $databases[$database_name]['SCHEMA_LENGTH']
1053 += $row['Data_length'] + $row['Index_length'];
1055 $this->freeResult($res);
1056 unset($res);
1062 * apply limit and order manually now
1063 * (caused by older MySQL < 5 or $GLOBALS['cfg']['NaturalOrder'])
1065 if ($apply_limit_and_order_manual) {
1066 $GLOBALS['callback_sort_order'] = $sort_order;
1067 $GLOBALS['callback_sort_by'] = $sort_by;
1068 usort(
1069 $databases,
1070 array('PMA_DatabaseInterface', '_usortComparisonCallback')
1072 unset($GLOBALS['callback_sort_order'], $GLOBALS['callback_sort_by']);
1075 * now apply limit
1077 if ($limit_count) {
1078 $databases = array_slice($databases, $limit_offset, $limit_count);
1082 return $databases;
1087 * Generates JOIN part for the Drizzle query to get database/table stats.
1089 * @return string
1091 private function _getDrizzeStatsJoin()
1093 $engine_info = PMA_Util::cacheGet('drizzle_engines');
1094 $stats_join = "LEFT JOIN (SELECT 0 NUM_ROWS) AS stat ON false";
1095 if (isset($engine_info['InnoDB'])
1096 && $engine_info['InnoDB']['module_library'] == 'innobase'
1098 $stats_join
1099 = "LEFT JOIN data_dictionary.INNODB_SYS_TABLESTATS stat"
1100 . " ON (t.ENGINE = 'InnoDB' AND stat.NAME"
1101 . " = (t.TABLE_SCHEMA || '/') || t.TABLE_NAME)";
1103 return $stats_join;
1108 * usort comparison callback
1110 * @param string $a first argument to sort
1111 * @param string $b second argument to sort
1113 * @return integer a value representing whether $a should be before $b in the
1114 * sorted array or not
1116 * @access private
1118 private static function _usortComparisonCallback($a, $b)
1120 if ($GLOBALS['cfg']['NaturalOrder']) {
1121 $sorter = 'strnatcasecmp';
1122 } else {
1123 $sorter = 'strcasecmp';
1125 /* No sorting when key is not present */
1126 if (! isset($a[$GLOBALS['callback_sort_by']])
1127 || ! isset($b[$GLOBALS['callback_sort_by']])
1129 return 0;
1131 // produces f.e.:
1132 // return -1 * strnatcasecmp($a["SCHEMA_TABLES"], $b["SCHEMA_TABLES"])
1133 return ($GLOBALS['callback_sort_order'] == 'ASC' ? 1 : -1) * $sorter(
1134 $a[$GLOBALS['callback_sort_by']], $b[$GLOBALS['callback_sort_by']]
1136 } // end of the '_usortComparisonCallback()' method
1139 * returns detailed array with all columns for given table in database,
1140 * or all tables/databases
1142 * @param string $database name of database
1143 * @param string $table name of table to retrieve columns from
1144 * @param string $column name of specific column
1145 * @param mixed $link mysql link resource
1147 * @return array
1149 public function getColumnsFull($database = null, $table = null,
1150 $column = null, $link = null
1152 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1153 $sql_wheres = array();
1154 $array_keys = array();
1156 // get columns information from information_schema
1157 if (null !== $database) {
1158 $sql_wheres[] = '`TABLE_SCHEMA` = \''
1159 . PMA_Util::sqlAddSlashes($database) . '\' ';
1160 } else {
1161 $array_keys[] = 'TABLE_SCHEMA';
1163 if (null !== $table) {
1164 $sql_wheres[] = '`TABLE_NAME` = \''
1165 . PMA_Util::sqlAddSlashes($table) . '\' ';
1166 } else {
1167 $array_keys[] = 'TABLE_NAME';
1169 if (null !== $column) {
1170 $sql_wheres[] = '`COLUMN_NAME` = \''
1171 . PMA_Util::sqlAddSlashes($column) . '\' ';
1172 } else {
1173 $array_keys[] = 'COLUMN_NAME';
1176 // for PMA bc:
1177 // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]`
1178 if (PMA_DRIZZLE) {
1179 $sql = "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME,
1180 column_name AS `Field`,
1181 (CASE
1182 WHEN character_maximum_length > 0
1183 THEN concat(lower(data_type), '(', character_maximum_length, ')')
1184 WHEN numeric_precision > 0 OR numeric_scale > 0
1185 THEN concat(lower(data_type), '(', numeric_precision,
1186 ',', numeric_scale, ')')
1187 WHEN enum_values IS NOT NULL
1188 THEN concat(lower(data_type), '(', enum_values, ')')
1189 ELSE lower(data_type) END)
1190 AS `Type`,
1191 collation_name AS `Collation`,
1192 (CASE is_nullable
1193 WHEN 1 THEN 'YES'
1194 ELSE 'NO' END) AS `Null`,
1195 (CASE
1196 WHEN is_used_in_primary THEN 'PRI'
1197 ELSE '' END) AS `Key`,
1198 column_default AS `Default`,
1199 (CASE
1200 WHEN is_auto_increment THEN 'auto_increment'
1201 WHEN column_default_update
1202 THEN 'on update ' || column_default_update
1203 ELSE '' END) AS `Extra`,
1204 NULL AS `Privileges`,
1205 column_comment AS `Comment`
1206 FROM data_dictionary.columns";
1207 } else {
1208 $sql = '
1209 SELECT *,
1210 `COLUMN_NAME` AS `Field`,
1211 `COLUMN_TYPE` AS `Type`,
1212 `COLLATION_NAME` AS `Collation`,
1213 `IS_NULLABLE` AS `Null`,
1214 `COLUMN_KEY` AS `Key`,
1215 `COLUMN_DEFAULT` AS `Default`,
1216 `EXTRA` AS `Extra`,
1217 `PRIVILEGES` AS `Privileges`,
1218 `COLUMN_COMMENT` AS `Comment`
1219 FROM `information_schema`.`COLUMNS`';
1221 if (count($sql_wheres)) {
1222 $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres);
1224 return $this->fetchResult($sql, $array_keys, null, $link);
1225 } else {
1226 $columns = array();
1227 if (null === $database) {
1228 foreach ($GLOBALS['pma']->databases as $database) {
1229 $columns[$database] = $this->getColumnsFull(
1230 $database, null, null, $link
1233 return $columns;
1234 } elseif (null === $table) {
1235 $tables = $this->getTables($database);
1236 foreach ($tables as $table) {
1237 $columns[$table] = $this->getColumnsFull(
1238 $database, $table, null, $link
1241 return $columns;
1243 $sql = 'SHOW FULL COLUMNS FROM '
1244 . PMA_Util::backquote($database) . '.' . PMA_Util::backquote($table);
1245 if (null !== $column) {
1246 $sql .= " LIKE '" . PMA_Util::sqlAddSlashes($column, true) . "'";
1249 $columns = $this->fetchResult($sql, 'Field', null, $link);
1250 $ordinal_position = 1;
1251 foreach ($columns as $column_name => $each_column) {
1253 // MySQL forward compatibility
1254 // so pma could use this array as if every server is of version >5.0
1255 // todo : remove and check the rest of the code for usage,
1256 // MySQL 5.0 or higher is required for current PMA version
1257 $columns[$column_name]['COLUMN_NAME']
1258 =& $columns[$column_name]['Field'];
1259 $columns[$column_name]['COLUMN_TYPE']
1260 =& $columns[$column_name]['Type'];
1261 $columns[$column_name]['COLLATION_NAME']
1262 =& $columns[$column_name]['Collation'];
1263 $columns[$column_name]['IS_NULLABLE']
1264 =& $columns[$column_name]['Null'];
1265 $columns[$column_name]['COLUMN_KEY']
1266 =& $columns[$column_name]['Key'];
1267 $columns[$column_name]['COLUMN_DEFAULT']
1268 =& $columns[$column_name]['Default'];
1269 $columns[$column_name]['EXTRA']
1270 =& $columns[$column_name]['Extra'];
1271 $columns[$column_name]['PRIVILEGES']
1272 =& $columns[$column_name]['Privileges'];
1273 $columns[$column_name]['COLUMN_COMMENT']
1274 =& $columns[$column_name]['Comment'];
1276 $columns[$column_name]['TABLE_CATALOG'] = null;
1277 $columns[$column_name]['TABLE_SCHEMA'] = $database;
1278 $columns[$column_name]['TABLE_NAME'] = $table;
1279 $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position;
1280 $columns[$column_name]['DATA_TYPE']
1281 = substr(
1282 $columns[$column_name]['COLUMN_TYPE'],
1284 strpos($columns[$column_name]['COLUMN_TYPE'], '(')
1287 * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE
1289 $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null;
1291 * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH
1293 $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null;
1294 $columns[$column_name]['NUMERIC_PRECISION'] = null;
1295 $columns[$column_name]['NUMERIC_SCALE'] = null;
1296 $columns[$column_name]['CHARACTER_SET_NAME']
1297 = substr(
1298 $columns[$column_name]['COLLATION_NAME'],
1300 strpos($columns[$column_name]['COLLATION_NAME'], '_')
1303 $ordinal_position++;
1306 if (null !== $column) {
1307 reset($columns);
1308 $columns = current($columns);
1311 return $columns;
1316 * Returns SQL query for fetching columns for a table
1318 * The 'Key' column is not calculated properly, use $GLOBALS['dbi']->getColumns()
1319 * to get correct values.
1321 * @param string $database name of database
1322 * @param string $table name of table to retrieve columns from
1323 * @param string $column name of column, null to show all columns
1324 * @param boolean $full whether to return full info or only column names
1326 * @see getColumns()
1328 * @return string
1330 public function getColumnsSql($database, $table, $column = null, $full = false)
1332 if (PMA_DRIZZLE) {
1333 // `Key` column:
1334 // * used in primary key => PRI
1335 // * unique one-column => UNI
1336 // * indexed, one-column or first in multi-column => MUL
1337 // Promotion of UNI to PRI in case no primary index exists
1338 // is done after query is executed
1339 $sql = "SELECT
1340 column_name AS `Field`,
1341 (CASE
1342 WHEN character_maximum_length > 0
1343 THEN concat(
1344 lower(data_type), '(', character_maximum_length, ')'
1346 WHEN numeric_precision > 0 OR numeric_scale > 0
1347 THEN concat(lower(data_type), '(', numeric_precision,
1348 ',', numeric_scale, ')')
1349 WHEN enum_values IS NOT NULL
1350 THEN concat(lower(data_type), '(', enum_values, ')')
1351 ELSE lower(data_type) END)
1352 AS `Type`,
1353 " . ($full ? "
1354 collation_name AS `Collation`," : '') . "
1355 (CASE is_nullable
1356 WHEN 1 THEN 'YES'
1357 ELSE 'NO' END) AS `Null`,
1358 (CASE
1359 WHEN is_used_in_primary THEN 'PRI'
1360 WHEN is_unique AND NOT is_multi THEN 'UNI'
1361 WHEN is_indexed
1362 AND (NOT is_multi OR is_first_in_multi) THEN 'MUL'
1363 ELSE '' END) AS `Key`,
1364 column_default AS `Default`,
1365 (CASE
1366 WHEN is_auto_increment THEN 'auto_increment'
1367 WHEN column_default_update <> ''
1368 THEN 'on update ' || column_default_update
1369 ELSE '' END) AS `Extra`
1370 " . ($full ? " ,
1371 NULL AS `Privileges`,
1372 column_comment AS `Comment`" : '') . "
1373 FROM data_dictionary.columns
1374 WHERE table_schema = '" . PMA_Util::sqlAddSlashes($database) . "'
1375 AND table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
1376 " . (
1377 ($column != null)
1379 AND column_name = '" . PMA_Util::sqlAddSlashes($column) . "'"
1380 : ''
1382 // ORDER BY ordinal_position
1383 } else {
1384 $sql = 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM '
1385 . PMA_Util::backquote($database) . '.' . PMA_Util::backquote($table)
1386 . (($column != null) ? "LIKE '"
1387 . PMA_Util::sqlAddSlashes($column, true) . "'" : '');
1389 return $sql;
1393 * Returns descriptions of columns in given table (all or given by $column)
1395 * @param string $database name of database
1396 * @param string $table name of table to retrieve columns from
1397 * @param string $column name of column, null to show all columns
1398 * @param boolean $full whether to return full info or only column names
1399 * @param mixed $link mysql link resource
1401 * @return false|array array indexed by column names or,
1402 * if $column is given, flat array description
1404 public function getColumns($database, $table, $column = null, $full = false,
1405 $link = null
1407 $sql = $this->getColumnsSql($database, $table, $column, $full);
1408 $fields = $this->fetchResult($sql, 'Field', null, $link);
1409 if (! is_array($fields) || count($fields) == 0) {
1410 return null;
1412 // Check if column is a part of multiple-column index and set its 'Key'.
1413 $indexes = PMA_Index::getFromTable($table, $database);
1414 foreach ($fields as $field => $field_data) {
1415 if (!empty($field_data['Key'])) {
1416 continue;
1419 foreach ($indexes as $index) {
1420 /** @var PMA_Index $index */
1421 if (!$index->hasColumn($field)) {
1422 continue;
1425 $index_columns = $index->getColumns();
1426 if ($index_columns[$field]->getSeqInIndex() > 1) {
1427 if ($index->isUnique()) {
1428 $fields[$field]['Key'] = 'UNI';
1429 } else {
1430 $fields[$field]['Key'] = 'MUL';
1435 if (PMA_DRIZZLE) {
1436 // fix Key column, it's much simpler in PHP than in SQL
1437 $has_pk = false;
1438 $has_pk_candidates = false;
1439 foreach ($fields as $f) {
1440 if ($f['Key'] == 'PRI') {
1441 $has_pk = true;
1442 break;
1443 } else if ($f['Null'] == 'NO'
1444 && ($f['Key'] == 'MUL'
1445 || $f['Key'] == 'UNI')
1447 $has_pk_candidates = true;
1450 if (! $has_pk && $has_pk_candidates) {
1451 $secureDatabase = PMA_Util::sqlAddSlashes($database);
1452 // check whether we can promote some unique index to PRI
1453 $sql = "
1454 SELECT i.index_name, p.column_name
1455 FROM data_dictionary.indexes i
1456 JOIN data_dictionary.index_parts p
1457 USING (table_schema, table_name)
1458 WHERE i.table_schema = '" . $secureDatabase . "'
1459 AND i.table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
1460 AND i.is_unique
1461 AND NOT i.is_nullable";
1462 $result = $this->fetchResult($sql, 'index_name', null, $link);
1463 $result = $result ? array_shift($result) : array();
1464 foreach ($result as $f) {
1465 $fields[$f]['Key'] = 'PRI';
1470 return ($column != null) ? array_shift($fields) : $fields;
1474 * Returns all column names in given table
1476 * @param string $database name of database
1477 * @param string $table name of table to retrieve columns from
1478 * @param mixed $link mysql link resource
1480 * @return null|array
1482 public function getColumnNames($database, $table, $link = null)
1484 $sql = $this->getColumnsSql($database, $table);
1485 // We only need the 'Field' column which contains the table's column names
1486 $fields = array_keys($this->fetchResult($sql, 'Field', null, $link));
1488 if ( ! is_array($fields) || count($fields) == 0 ) {
1489 return null;
1491 return $fields;
1495 * Returns SQL for fetching information on table indexes (SHOW INDEXES)
1497 * @param string $database name of database
1498 * @param string $table name of the table whose indexes are to be retrieved
1499 * @param string $where additional conditions for WHERE
1501 * @return string SQL for getting indexes
1503 public function getTableIndexesSql($database, $table, $where = null)
1505 if (PMA_DRIZZLE) {
1506 $sql = "SELECT
1507 ip.table_name AS `Table`,
1508 (NOT ip.is_unique) AS Non_unique,
1509 ip.index_name AS Key_name,
1510 ip.sequence_in_index+1 AS Seq_in_index,
1511 ip.column_name AS Column_name,
1512 (CASE
1513 WHEN i.index_type = 'BTREE' THEN 'A'
1514 ELSE NULL END) AS Collation,
1515 NULL AS Cardinality,
1516 compare_length AS Sub_part,
1517 NULL AS Packed,
1518 ip.is_nullable AS `Null`,
1519 i.index_type AS Index_type,
1520 NULL AS Comment,
1521 i.index_comment AS Index_comment
1522 FROM data_dictionary.index_parts ip
1523 LEFT JOIN data_dictionary.indexes i
1524 USING (table_schema, table_name, index_name)
1525 WHERE table_schema = '" . PMA_Util::sqlAddSlashes($database) . "'
1526 AND table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
1528 if ($where) {
1529 $sql = "SELECT * FROM (" . $sql . ") A WHERE (" . $where . ")";
1531 } else {
1532 $sql = 'SHOW INDEXES FROM ' . PMA_Util::backquote($database) . '.'
1533 . PMA_Util::backquote($table);
1534 if ($where) {
1535 $sql .= ' WHERE (' . $where . ')';
1538 return $sql;
1542 * Returns indexes of a table
1544 * @param string $database name of database
1545 * @param string $table name of the table whose indexes are to be retrieved
1546 * @param mixed $link mysql link resource
1548 * @return array $indexes
1550 public function getTableIndexes($database, $table, $link = null)
1552 $sql = $this->getTableIndexesSql($database, $table);
1553 $indexes = $this->fetchResult($sql, null, null, $link);
1555 if (! is_array($indexes) || count($indexes) < 1) {
1556 return array();
1558 return $indexes;
1562 * returns value of given mysql server variable
1564 * @param string $var mysql server variable name
1565 * @param int $type PMA_DatabaseInterface::GETVAR_SESSION |
1566 * PMA_DatabaseInterface::GETVAR_GLOBAL
1567 * @param mixed $link mysql link resource|object
1569 * @return mixed value for mysql server variable
1571 public function getVariable(
1572 $var, $type = self::GETVAR_SESSION, $link = null
1574 $link = $this->getLink($link);
1575 if ($link === false) {
1576 return false;
1579 switch ($type) {
1580 case self::GETVAR_SESSION:
1581 $modifier = ' SESSION';
1582 break;
1583 case self::GETVAR_GLOBAL:
1584 $modifier = ' GLOBAL';
1585 break;
1586 default:
1587 $modifier = '';
1589 return $this->fetchValue(
1590 'SHOW' . $modifier . ' VARIABLES LIKE \'' . $var . '\';', 0, 1, $link
1595 * Function called just after a connection to the MySQL database server has
1596 * been established. It sets the connection collation, and determines the
1597 * version of MySQL which is running.
1599 * @param mixed $link mysql link resource|object
1601 * @return void
1603 public function postConnect($link)
1605 if (! defined('PMA_MYSQL_INT_VERSION')) {
1606 if (PMA_Util::cacheExists('PMA_MYSQL_INT_VERSION')) {
1607 define(
1608 'PMA_MYSQL_INT_VERSION',
1609 PMA_Util::cacheGet('PMA_MYSQL_INT_VERSION')
1611 define(
1612 'PMA_MYSQL_MAJOR_VERSION',
1613 PMA_Util::cacheGet('PMA_MYSQL_MAJOR_VERSION')
1615 define(
1616 'PMA_MYSQL_STR_VERSION',
1617 PMA_Util::cacheGet('PMA_MYSQL_STR_VERSION')
1619 define(
1620 'PMA_MYSQL_VERSION_COMMENT',
1621 PMA_Util::cacheGet('PMA_MYSQL_VERSION_COMMENT')
1623 define(
1624 'PMA_DRIZZLE',
1625 PMA_Util::cacheGet('PMA_DRIZZLE')
1627 } else {
1628 $version = $this->fetchSingleRow(
1629 'SELECT @@version, @@version_comment',
1630 'ASSOC',
1631 $link
1634 if ($version) {
1635 $match = explode('.', $version['@@version']);
1636 define('PMA_MYSQL_MAJOR_VERSION', (int)$match[0]);
1637 define(
1638 'PMA_MYSQL_INT_VERSION',
1639 (int) sprintf(
1640 '%d%02d%02d', $match[0], $match[1], intval($match[2])
1643 define('PMA_MYSQL_STR_VERSION', $version['@@version']);
1644 define(
1645 'PMA_MYSQL_VERSION_COMMENT',
1646 $version['@@version_comment']
1648 } else {
1649 define('PMA_MYSQL_INT_VERSION', 50501);
1650 define('PMA_MYSQL_MAJOR_VERSION', 5);
1651 define('PMA_MYSQL_STR_VERSION', '5.05.01');
1652 define('PMA_MYSQL_VERSION_COMMENT', '');
1654 PMA_Util::cacheSet(
1655 'PMA_MYSQL_INT_VERSION',
1656 PMA_MYSQL_INT_VERSION
1658 PMA_Util::cacheSet(
1659 'PMA_MYSQL_MAJOR_VERSION',
1660 PMA_MYSQL_MAJOR_VERSION
1662 PMA_Util::cacheSet(
1663 'PMA_MYSQL_STR_VERSION',
1664 PMA_MYSQL_STR_VERSION
1666 PMA_Util::cacheSet(
1667 'PMA_MYSQL_VERSION_COMMENT',
1668 PMA_MYSQL_VERSION_COMMENT
1671 /* Detect Drizzle - it does not support charsets */
1672 $charset_result = $this->query(
1673 "SHOW VARIABLES LIKE 'character_set_results'",
1674 $link
1676 if ($this->numRows($charset_result) == 0) {
1677 define('PMA_DRIZZLE', true);
1678 } else {
1679 define('PMA_DRIZZLE', false);
1681 $this->freeResult($charset_result);
1683 PMA_Util::cacheSet(
1684 'PMA_DRIZZLE',
1685 PMA_DRIZZLE
1690 // Skip charsets for Drizzle
1691 if (!PMA_DRIZZLE) {
1692 if (PMA_MYSQL_INT_VERSION > 50503) {
1693 $default_charset = 'utf8mb4';
1694 $default_collation = 'utf8mb4_general_ci';
1695 } else {
1696 $default_charset = 'utf8';
1697 $default_collation = 'utf8_general_ci';
1699 if (! empty($GLOBALS['collation_connection'])) {
1700 $this->query(
1701 "SET CHARACTER SET '$default_charset';",
1702 $link,
1703 self::QUERY_STORE
1705 /* Automatically adjust collation to mb4 variant */
1706 if ($default_charset == 'utf8mb4'
1707 && strncmp('utf8_', $GLOBALS['collation_connection'], 5) == 0
1709 $GLOBALS['collation_connection'] = 'utf8mb4_' . substr(
1710 $GLOBALS['collation_connection'],
1714 $this->query(
1715 "SET collation_connection = '"
1716 . PMA_Util::sqlAddSlashes($GLOBALS['collation_connection'])
1717 . "';",
1718 $link,
1719 self::QUERY_STORE
1721 } else {
1722 $this->query(
1723 "SET NAMES '$default_charset' COLLATE '$default_collation';",
1724 $link,
1725 self::QUERY_STORE
1730 // Cache plugin list for Drizzle
1731 if (PMA_DRIZZLE && !PMA_Util::cacheExists('drizzle_engines')) {
1732 $sql = "SELECT p.plugin_name, m.module_library
1733 FROM data_dictionary.plugins p
1734 JOIN data_dictionary.modules m USING (module_name)
1735 WHERE p.plugin_type = 'StorageEngine'
1736 AND p.plugin_name NOT IN ('FunctionEngine', 'schema')
1737 AND p.is_active = 'YES'";
1738 $engines = $this->fetchResult($sql, 'plugin_name', null, $link);
1739 PMA_Util::cacheSet('drizzle_engines', $engines);
1744 * returns a single value from the given result or query,
1745 * if the query or the result has more than one row or field
1746 * the first field of the first row is returned
1748 * <code>
1749 * $sql = 'SELECT `name` FROM `user` WHERE `id` = 123';
1750 * $user_name = $GLOBALS['dbi']->fetchValue($sql);
1751 * // produces
1752 * // $user_name = 'John Doe'
1753 * </code>
1755 * @param string $query The query to execute
1756 * @param integer $row_number row to fetch the value from,
1757 * starting at 0, with 0 being default
1758 * @param integer|string $field field to fetch the value from,
1759 * starting at 0, with 0 being default
1760 * @param object $link mysql link
1762 * @return mixed value of first field in first row from result
1763 * or false if not found
1765 public function fetchValue($query, $row_number = 0, $field = 0, $link = null)
1767 $value = false;
1769 $result = $this->tryQuery(
1770 $query,
1771 $link,
1772 self::QUERY_STORE,
1773 false
1775 if ($result === false) {
1776 return false;
1779 // return false if result is empty or false
1780 // or requested row is larger than rows in result
1781 if ($this->numRows($result) < ($row_number + 1)) {
1782 return $value;
1785 // if $field is an integer use non associative mysql fetch function
1786 if (is_int($field)) {
1787 $fetch_function = 'fetchRow';
1788 } else {
1789 $fetch_function = 'fetchAssoc';
1792 // get requested row
1793 for ($i = 0; $i <= $row_number; $i++) {
1794 $row = $this->$fetch_function($result);
1796 $this->freeResult($result);
1798 // return requested field
1799 if (isset($row[$field])) {
1800 $value = $row[$field];
1803 return $value;
1807 * returns only the first row from the result
1809 * <code>
1810 * $sql = 'SELECT * FROM `user` WHERE `id` = 123';
1811 * $user = $GLOBALS['dbi']->fetchSingleRow($sql);
1812 * // produces
1813 * // $user = array('id' => 123, 'name' => 'John Doe')
1814 * </code>
1816 * @param string $query The query to execute
1817 * @param string $type NUM|ASSOC|BOTH returned array should either
1818 * numeric associative or both
1819 * @param object $link mysql link
1821 * @return array|boolean first row from result
1822 * or false if result is empty
1824 public function fetchSingleRow($query, $type = 'ASSOC', $link = null)
1826 $result = $this->tryQuery(
1827 $query,
1828 $link,
1829 self::QUERY_STORE,
1830 false
1832 if ($result === false) {
1833 return false;
1836 // return false if result is empty or false
1837 if (! $this->numRows($result)) {
1838 return false;
1841 switch ($type) {
1842 case 'NUM' :
1843 $fetch_function = 'fetchRow';
1844 break;
1845 case 'ASSOC' :
1846 $fetch_function = 'fetchAssoc';
1847 break;
1848 case 'BOTH' :
1849 default :
1850 $fetch_function = 'fetchArray';
1851 break;
1854 $row = $this->$fetch_function($result);
1855 $this->freeResult($result);
1856 return $row;
1860 * Returns row or element of a row
1862 * @param array $row Row to process
1863 * @param string|null $value Which column to return
1865 * @return mixed
1867 private function _fetchValue($row, $value)
1869 if (is_null($value)) {
1870 return $row;
1871 } else {
1872 return $row[$value];
1877 * returns all rows in the resultset in one array
1879 * <code>
1880 * $sql = 'SELECT * FROM `user`';
1881 * $users = $GLOBALS['dbi']->fetchResult($sql);
1882 * // produces
1883 * // $users[] = array('id' => 123, 'name' => 'John Doe')
1885 * $sql = 'SELECT `id`, `name` FROM `user`';
1886 * $users = $GLOBALS['dbi']->fetchResult($sql, 'id');
1887 * // produces
1888 * // $users['123'] = array('id' => 123, 'name' => 'John Doe')
1890 * $sql = 'SELECT `id`, `name` FROM `user`';
1891 * $users = $GLOBALS['dbi']->fetchResult($sql, 0);
1892 * // produces
1893 * // $users['123'] = array(0 => 123, 1 => 'John Doe')
1895 * $sql = 'SELECT `id`, `name` FROM `user`';
1896 * $users = $GLOBALS['dbi']->fetchResult($sql, 'id', 'name');
1897 * // or
1898 * $users = $GLOBALS['dbi']->fetchResult($sql, 0, 1);
1899 * // produces
1900 * // $users['123'] = 'John Doe'
1902 * $sql = 'SELECT `name` FROM `user`';
1903 * $users = $GLOBALS['dbi']->fetchResult($sql);
1904 * // produces
1905 * // $users[] = 'John Doe'
1907 * $sql = 'SELECT `group`, `name` FROM `user`'
1908 * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', null), 'name');
1909 * // produces
1910 * // $users['admin'][] = 'John Doe'
1912 * $sql = 'SELECT `group`, `name` FROM `user`'
1913 * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', 'name'), 'id');
1914 * // produces
1915 * // $users['admin']['John Doe'] = '123'
1916 * </code>
1918 * @param string $query query to execute
1919 * @param string|integer|array $key field-name or offset
1920 * used as key for array
1921 * or array of those
1922 * @param string|integer $value value-name or offset
1923 * used as value for array
1924 * @param object $link mysql link
1925 * @param integer $options query options
1927 * @return array resultrows or values indexed by $key
1929 public function fetchResult($query, $key = null, $value = null,
1930 $link = null, $options = 0
1932 $resultrows = array();
1934 $result = $this->tryQuery($query, $link, $options, false);
1936 // return empty array if result is empty or false
1937 if ($result === false) {
1938 return $resultrows;
1941 $fetch_function = 'fetchAssoc';
1943 // no nested array if only one field is in result
1944 if (null === $key && 1 === $this->numFields($result)) {
1945 $value = 0;
1946 $fetch_function = 'fetchRow';
1949 // if $key is an integer use non associative mysql fetch function
1950 if (is_int($key)) {
1951 $fetch_function = 'fetchRow';
1954 if (null === $key) {
1955 while ($row = $this->$fetch_function($result)) {
1956 $resultrows[] = $this->_fetchValue($row, $value);
1958 } else {
1959 if (is_array($key)) {
1960 while ($row = $this->$fetch_function($result)) {
1961 $result_target =& $resultrows;
1962 foreach ($key as $key_index) {
1963 if (null === $key_index) {
1964 $result_target =& $result_target[];
1965 continue;
1968 if (! isset($result_target[$row[$key_index]])) {
1969 $result_target[$row[$key_index]] = array();
1971 $result_target =& $result_target[$row[$key_index]];
1973 $result_target = $this->_fetchValue($row, $value);
1975 } else {
1976 while ($row = $this->$fetch_function($result)) {
1977 $resultrows[$row[$key]] = $this->_fetchValue($row, $value);
1982 $this->freeResult($result);
1983 return $resultrows;
1987 * Get supported SQL compatibility modes
1989 * @return array supported SQL compatibility modes
1991 public function getCompatibilities()
1993 // Drizzle doesn't support compatibility modes
1994 if (PMA_DRIZZLE) {
1995 return array();
1998 $compats = array('NONE');
1999 $compats[] = 'ANSI';
2000 $compats[] = 'DB2';
2001 $compats[] = 'MAXDB';
2002 $compats[] = 'MYSQL323';
2003 $compats[] = 'MYSQL40';
2004 $compats[] = 'MSSQL';
2005 $compats[] = 'ORACLE';
2006 // removed; in MySQL 5.0.33, this produces exports that
2007 // can't be read by POSTGRESQL (see our bug #1596328)
2008 //$compats[] = 'POSTGRESQL';
2009 $compats[] = 'TRADITIONAL';
2011 return $compats;
2015 * returns warnings for last query
2017 * @param object $link mysql link resource
2019 * @return array warnings
2021 public function getWarnings($link = null)
2023 $link = $this->getLink($link);
2024 if ($link === false) {
2025 return false;
2028 return $this->fetchResult('SHOW WARNINGS', null, null, $link);
2032 * returns an array of PROCEDURE or FUNCTION names for a db
2034 * @param string $db db name
2035 * @param string $which PROCEDURE | FUNCTION
2036 * @param object $link mysql link
2038 * @return array the procedure names or function names
2040 public function getProceduresOrFunctions($db, $which, $link = null)
2042 if (PMA_DRIZZLE) {
2043 // Drizzle doesn't support functions and procedures
2044 return array();
2046 $shows = $this->fetchResult(
2047 'SHOW ' . $which . ' STATUS;', null, null, $link
2049 $result = array();
2050 foreach ($shows as $one_show) {
2051 if ($one_show['Db'] == $db && $one_show['Type'] == $which) {
2052 $result[] = $one_show['Name'];
2055 return($result);
2059 * returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW
2061 * @param string $db db name
2062 * @param string $which PROCEDURE | FUNCTION | EVENT | VIEW
2063 * @param string $name the procedure|function|event|view name
2065 * @return string the definition
2067 public function getDefinition($db, $which, $name)
2069 $returned_field = array(
2070 'PROCEDURE' => 'Create Procedure',
2071 'FUNCTION' => 'Create Function',
2072 'EVENT' => 'Create Event',
2073 'VIEW' => 'Create View'
2075 $query = 'SHOW CREATE ' . $which . ' '
2076 . PMA_Util::backquote($db) . '.'
2077 . PMA_Util::backquote($name);
2078 return($this->fetchValue($query, 0, $returned_field[$which]));
2082 * returns details about the TRIGGERs for a specific table or database
2084 * @param string $db db name
2085 * @param string $table table name
2086 * @param string $delimiter the delimiter to use (may be empty)
2088 * @return array information about triggers (may be empty)
2090 public function getTriggers($db, $table = '', $delimiter = '//')
2092 if (PMA_DRIZZLE) {
2093 // Drizzle doesn't support triggers
2094 return array();
2097 $result = array();
2098 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
2099 // Note: in http://dev.mysql.com/doc/refman/5.0/en/faqs-triggers.html
2100 // their example uses WHERE TRIGGER_SCHEMA='dbname' so let's use this
2101 // instead of WHERE EVENT_OBJECT_SCHEMA='dbname'
2102 $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION'
2103 . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT'
2104 . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER'
2105 . ' FROM information_schema.TRIGGERS'
2106 . ' WHERE TRIGGER_SCHEMA ' . PMA_Util::getCollateForIS() . '='
2107 . ' \'' . PMA_Util::sqlAddSlashes($db) . '\'';
2109 if (! empty($table)) {
2110 $query .= " AND EVENT_OBJECT_TABLE = '"
2111 . PMA_Util::sqlAddSlashes($table) . "';";
2113 } else {
2114 $query = "SHOW TRIGGERS FROM " . PMA_Util::backquote($db);
2115 if (! empty($table)) {
2116 $query .= " LIKE '" . PMA_Util::sqlAddSlashes($table, true) . "';";
2120 if ($triggers = $this->fetchResult($query)) {
2121 foreach ($triggers as $trigger) {
2122 if ($GLOBALS['cfg']['Server']['DisableIS']) {
2123 $trigger['TRIGGER_NAME'] = $trigger['Trigger'];
2124 $trigger['ACTION_TIMING'] = $trigger['Timing'];
2125 $trigger['EVENT_MANIPULATION'] = $trigger['Event'];
2126 $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table'];
2127 $trigger['ACTION_STATEMENT'] = $trigger['Statement'];
2128 $trigger['DEFINER'] = $trigger['Definer'];
2130 $one_result = array();
2131 $one_result['name'] = $trigger['TRIGGER_NAME'];
2132 $one_result['table'] = $trigger['EVENT_OBJECT_TABLE'];
2133 $one_result['action_timing'] = $trigger['ACTION_TIMING'];
2134 $one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION'];
2135 $one_result['definition'] = $trigger['ACTION_STATEMENT'];
2136 $one_result['definer'] = $trigger['DEFINER'];
2138 // do not prepend the schema name; this way, importing the
2139 // definition into another schema will work
2140 $one_result['full_trigger_name'] = PMA_Util::backquote(
2141 $trigger['TRIGGER_NAME']
2143 $one_result['drop'] = 'DROP TRIGGER IF EXISTS '
2144 . $one_result['full_trigger_name'];
2145 $one_result['create'] = 'CREATE TRIGGER '
2146 . $one_result['full_trigger_name'] . ' '
2147 . $trigger['ACTION_TIMING'] . ' '
2148 . $trigger['EVENT_MANIPULATION']
2149 . ' ON ' . PMA_Util::backquote($trigger['EVENT_OBJECT_TABLE'])
2150 . "\n" . ' FOR EACH ROW '
2151 . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n";
2153 $result[] = $one_result;
2157 // Sort results by name
2158 $name = array();
2159 foreach ($result as $value) {
2160 $name[] = $value['name'];
2162 array_multisort($name, SORT_ASC, $result);
2164 return($result);
2168 * Formats database error message in a friendly way.
2169 * This is needed because some errors messages cannot
2170 * be obtained by mysql_error().
2172 * @param int $error_number Error code
2173 * @param string $error_message Error message as returned by server
2175 * @return string HML text with error details
2177 public function formatError($error_number, $error_message)
2179 if (! empty($error_message)) {
2180 $error_message = $this->convertMessage($error_message);
2183 $error_message = htmlspecialchars($error_message);
2185 $error = '#' . ((string) $error_number);
2187 if ($error_number == 2002) {
2188 $error .= ' - ' . $error_message;
2189 $error .= '<br />';
2190 $error .= __(
2191 'The server is not responding (or the local server\'s socket'
2192 . ' is not correctly configured).'
2194 } elseif ($error_number == 2003) {
2195 $error .= ' - ' . $error_message;
2196 $error .= '<br />' . __('The server is not responding.');
2197 } elseif ($error_number == 1005) {
2198 if (strpos($error_message, 'errno: 13') !== false) {
2199 $error .= ' - ' . $error_message;
2200 $error .= '<br />'
2201 . __('Please check privileges of directory containing database.');
2202 } else {
2203 /* InnoDB constraints, see
2204 * http://dev.mysql.com/doc/refman/5.0/en/
2205 * innodb-foreign-key-constraints.html
2207 $error .= ' - ' . $error_message .
2208 ' (<a href="server_engines.php' .
2209 PMA_URL_getCommon(
2210 array('engine' => 'InnoDB', 'page' => 'Status')
2211 ) . '">' . __('Details…') . '</a>)';
2213 } else {
2214 $error .= ' - ' . $error_message;
2217 return $error;
2221 * gets the current user with host
2223 * @return string the current user i.e. user@host
2225 public function getCurrentUser()
2227 if (PMA_Util::cacheExists('mysql_cur_user')) {
2228 return PMA_Util::cacheGet('mysql_cur_user');
2230 $user = $GLOBALS['dbi']->fetchValue('SELECT USER();');
2231 if ($user !== false) {
2232 PMA_Util::cacheSet('mysql_cur_user', $user);
2233 return PMA_Util::cacheGet('mysql_cur_user');
2235 return '';
2239 * Checks if current user is superuser
2241 * @return bool Whether user is a superuser
2243 public function isSuperuser()
2245 return self::isUserType('super');
2249 * Checks if current user has global create user/grant privilege
2250 * or is a superuser (i.e. SELECT on mysql.users)
2251 * while caching the result in session.
2253 * @param string $type type of user to check for
2254 * i.e. 'create', 'grant', 'super'
2256 * @return bool Whether user is a given type of user
2258 public function isUserType($type)
2260 if (PMA_Util::cacheExists('is_' . $type . 'user')) {
2261 return PMA_Util::cacheGet('is_' . $type . 'user');
2264 // when connection failed we don't have a $userlink
2265 if (! isset($GLOBALS['userlink'])) {
2266 PMA_Util::cacheSet('is_' . $type . 'user', false);
2267 return PMA_Util::cacheGet('is_' . $type . 'user');
2270 if (PMA_DRIZZLE) {
2271 // Drizzle has no authorization by default, so when no plugin is
2272 // enabled everyone is a superuser
2273 // Known authorization libraries: regex_policy, simple_user_policy
2274 // Plugins limit object visibility (dbs, tables, processes), we can
2275 // safely assume we always deal with superuser
2276 PMA_Util::cacheSet('is_' . $type . 'user', true);
2277 return PMA_Util::cacheGet('is_' . $type . 'user');
2280 if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') {
2281 // Prepare query for each user type check
2282 $query = '';
2283 if ($type === 'super') {
2284 $query = 'SELECT 1 FROM mysql.user LIMIT 1';
2285 } elseif ($type === 'create') {
2286 list($user, $host) = $this->_getCurrentUserAndHost();
2287 $query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` "
2288 . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND "
2289 . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
2290 } elseif ($type === 'grant') {
2291 list($user, $host) = $this->_getCurrentUserAndHost();
2292 $query = "SELECT 1 FROM ("
2293 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2294 . "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION "
2295 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2296 . "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION "
2297 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2298 . "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION "
2299 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2300 . "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t "
2301 . "WHERE `IS_GRANTABLE` = 'YES' AND "
2302 . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
2305 $is = false;
2306 $result = $GLOBALS['dbi']->tryQuery(
2307 $query,
2308 $GLOBALS['userlink'],
2309 self::QUERY_STORE
2311 if ($result) {
2312 $is = (bool) $GLOBALS['dbi']->numRows($result);
2314 $GLOBALS['dbi']->freeResult($result);
2316 PMA_Util::cacheSet('is_' . $type . 'user', $is);
2317 } else {
2318 $is = false;
2319 $grants = $GLOBALS['dbi']->fetchResult(
2320 "SHOW GRANTS FOR CURRENT_USER();",
2321 null,
2322 null,
2323 $GLOBALS['userlink'],
2324 self::QUERY_STORE
2326 if ($grants) {
2327 foreach ($grants as $grant) {
2328 if ($type === 'create') {
2329 if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false
2330 || strpos($grant, "CREATE USER") !== false
2332 $is = true;
2333 break;
2335 } elseif ($type === 'grant') {
2336 if (strpos($grant, "WITH GRANT OPTION") !== false) {
2337 $is = true;
2338 break;
2344 PMA_Util::cacheSet('is_' . $type . 'user', $is);
2347 return PMA_Util::cacheGet('is_' . $type . 'user');
2351 * Get the current user and host
2353 * @return array array of username and hostname
2355 private function _getCurrentUserAndHost()
2357 $user = $GLOBALS['dbi']->fetchValue("SELECT CURRENT_USER();");
2358 return explode("@", $user);
2362 * Get the list of system schemas
2364 * @return array list of system schemas
2366 public function getSystemSchemas()
2368 $schemas = array(
2369 'information_schema', 'performance_schema', 'data_dictionary', 'mysql'
2371 $systemSchemas = array();
2372 foreach ($schemas as $schema) {
2373 if ($this->isSystemSchema($schema, true)) {
2374 $systemSchemas[] = $schema;
2377 return $systemSchemas;
2381 * Checks whether given schema is a system schema: information_schema
2382 * (MySQL and Drizzle) or data_dictionary (Drizzle)
2384 * @param string $schema_name Name of schema (database) to test
2385 * @param bool $testForMysqlSchema Whether 'mysql' schema should
2386 * be treated the same as IS and DD
2388 * @return bool
2390 public function isSystemSchema($schema_name, $testForMysqlSchema = false)
2392 return strtolower($schema_name) == 'information_schema'
2393 || (!PMA_DRIZZLE
2394 && strtolower($schema_name) == 'performance_schema')
2395 || (PMA_DRIZZLE
2396 && strtolower($schema_name) == 'data_dictionary')
2397 || ($testForMysqlSchema && !PMA_DRIZZLE && $schema_name == 'mysql');
2401 * connects to the database server
2403 * @param string $user user name
2404 * @param string $password user password
2405 * @param bool $is_controluser whether this is a control user connection
2406 * @param array $server host/port/socket/persistent
2407 * @param bool $auxiliary_connection (when true, don't go back to login if
2408 * connection fails)
2410 * @return mixed false on error or a connection object on success
2412 public function connect(
2413 $user, $password, $is_controluser = false, $server = null,
2414 $auxiliary_connection = false
2416 $result = $this->_extension->connect(
2417 $user, $password, $is_controluser, $server, $auxiliary_connection
2420 if ($result) {
2421 if (! $auxiliary_connection && ! $is_controluser) {
2422 $GLOBALS['dbi']->postConnect($result);
2424 return $result;
2427 if ($is_controluser) {
2428 trigger_error(
2430 'Connection for controluser as defined in your '
2431 . 'configuration failed.'
2433 E_USER_WARNING
2435 return false;
2438 // we could be calling $GLOBALS['dbi']->connect() to connect to another
2439 // server, for example in the Synchronize feature, so do not
2440 // go back to main login if it fails
2441 if ($auxiliary_connection) {
2442 return false;
2445 PMA_logUser($user, 'mysql-denied');
2446 $GLOBALS['auth_plugin']->authFails();
2448 return $result;
2452 * selects given database
2454 * @param string $dbname database name to select
2455 * @param object $link connection object
2457 * @return boolean
2459 public function selectDb($dbname, $link = null)
2461 $link = $this->getLink($link);
2462 if ($link === false) {
2463 return false;
2465 return $this->_extension->selectDb($dbname, $link);
2469 * returns array of rows with associative and numeric keys from $result
2471 * @param object $result result set identifier
2473 * @return array
2475 public function fetchArray($result)
2477 return $this->_extension->fetchArray($result);
2481 * returns array of rows with associative keys from $result
2483 * @param object $result result set identifier
2485 * @return array
2487 public function fetchAssoc($result)
2489 return $this->_extension->fetchAssoc($result);
2493 * returns array of rows with numeric keys from $result
2495 * @param object $result result set identifier
2497 * @return array
2499 public function fetchRow($result)
2501 return $this->_extension->fetchRow($result);
2505 * Adjusts the result pointer to an arbitrary row in the result
2507 * @param object $result database result
2508 * @param integer $offset offset to seek
2510 * @return bool true on success, false on failure
2512 public function dataSeek($result, $offset)
2514 return $this->_extension->dataSeek($result, $offset);
2518 * Frees memory associated with the result
2520 * @param object $result database result
2522 * @return void
2524 public function freeResult($result)
2526 $this->_extension->freeResult($result);
2530 * Check if there are any more query results from a multi query
2532 * @param object $link the connection object
2534 * @return bool true or false
2536 public function moreResults($link = null)
2538 $link = $this->getLink($link);
2539 if ($link === false) {
2540 return false;
2542 return $this->_extension->moreResults($link);
2546 * Prepare next result from multi_query
2548 * @param object $link the connection object
2550 * @return bool true or false
2552 public function nextResult($link = null)
2554 $link = $this->getLink($link);
2555 if ($link === false) {
2556 return false;
2558 return $this->_extension->nextResult($link);
2562 * Store the result returned from multi query
2564 * @param object $link the connection object
2566 * @return mixed false when empty results / result set when not empty
2568 public function storeResult($link = null)
2570 $link = $this->getLink($link);
2571 if ($link === false) {
2572 return false;
2574 return $this->_extension->storeResult($link);
2578 * Returns a string representing the type of connection used
2580 * @param object $link mysql link
2582 * @return string type of connection used
2584 public function getHostInfo($link = null)
2586 $link = $this->getLink($link);
2587 if ($link === false) {
2588 return false;
2590 return $this->_extension->getHostInfo($link);
2594 * Returns the version of the MySQL protocol used
2596 * @param object $link mysql link
2598 * @return integer version of the MySQL protocol used
2600 public function getProtoInfo($link = null)
2602 $link = $this->getLink($link);
2603 if ($link === false) {
2604 return false;
2606 return $this->_extension->getProtoInfo($link);
2610 * returns a string that represents the client library version
2612 * @return string MySQL client library version
2614 public function getClientInfo()
2616 return $this->_extension->getClientInfo();
2620 * returns last error message or false if no errors occurred
2622 * @param object $link connection link
2624 * @return string|bool $error or false
2626 public function getError($link = null)
2628 $link = $this->getLink($link);
2629 if ($link === false) {
2630 return false;
2632 return $this->_extension->getError($link);
2636 * returns the number of rows returned by last query
2638 * @param object $result result set identifier
2640 * @return string|int
2642 public function numRows($result)
2644 return $this->_extension->numRows($result);
2648 * returns last inserted auto_increment id for given $link
2649 * or $GLOBALS['userlink']
2651 * @param object $link the connection object
2653 * @return string|int
2655 public function insertId($link = null)
2657 $link = $this->getLink($link);
2658 if ($link === false) {
2659 return false;
2661 // If the primary key is BIGINT we get an incorrect result
2662 // (sometimes negative, sometimes positive)
2663 // and in the present function we don't know if the PK is BIGINT
2664 // so better play safe and use LAST_INSERT_ID()
2666 // When no controluser is defined, using mysqli_insert_id($link)
2667 // does not always return the last insert id due to a mixup with
2668 // the tracking mechanism, but this works:
2669 return $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link);
2673 * returns the number of rows affected by last query
2675 * @param object $link the connection object
2676 * @param bool $get_from_cache whether to retrieve from cache
2678 * @return int
2680 public function affectedRows($link = null, $get_from_cache = true)
2682 $link = $this->getLink($link);
2683 if ($link === false) {
2684 return false;
2687 if ($get_from_cache) {
2688 return $GLOBALS['cached_affected_rows'];
2689 } else {
2690 return $this->_extension->affectedRows($link);
2695 * returns metainfo for fields in $result
2697 * @param object $result result set identifier
2699 * @return array meta info for fields in $result
2701 public function getFieldsMeta($result)
2703 return $this->_extension->getFieldsMeta($result);
2707 * return number of fields in given $result
2709 * @param object $result result set identifier
2711 * @return int field count
2713 public function numFields($result)
2715 return $this->_extension->numFields($result);
2719 * returns the length of the given field $i in $result
2721 * @param object $result result set identifier
2722 * @param int $i field
2724 * @return int length of field
2726 public function fieldLen($result, $i)
2728 return $this->_extension->fieldLen($result, $i);
2732 * returns name of $i. field in $result
2734 * @param object $result result set identifier
2735 * @param int $i field
2737 * @return string name of $i. field in $result
2739 public function fieldName($result, $i)
2741 return $this->_extension->fieldName($result, $i);
2745 * returns concatenated string of human readable field flags
2747 * @param object $result result set identifier
2748 * @param int $i field
2750 * @return string field flags
2752 public function fieldFlags($result, $i)
2754 return $this->_extension->fieldFlags($result, $i);
2758 * Gets server connection port
2760 * @param array|null $server host/port/socket/persistent
2762 * @return null|integer
2764 public function getServerPort($server = null)
2766 if (is_null($server)) {
2767 $server = &$GLOBALS['cfg']['Server'];
2770 if (empty($server['port'])) {
2771 return null;
2772 } else {
2773 return intval($server['port']);
2778 * Gets server connection socket
2780 * @param array|null $server host/port/socket/persistent
2782 * @return null|string
2784 public function getServerSocket($server = null)
2786 if (is_null($server)) {
2787 $server = &$GLOBALS['cfg']['Server'];
2790 if (empty($server['socket'])) {
2791 return null;
2792 } else {
2793 return $server['socket'];
2798 * Gets correct link object.
2800 * @param mixed $link optional database link to use
2802 * @return object
2804 public function getLink($link = null)
2806 if ( ! is_null($link) && $link !== false) {
2807 return $link;
2810 if (isset($GLOBALS['userlink']) && !is_null($GLOBALS['userlink'])) {
2811 return $GLOBALS['userlink'];
2812 } else {
2813 return false;
2818 * Checks if this database server is running on Amazon RDS.
2820 * @return boolean
2822 public function isAmazonRds()
2824 if (PMA_Util::cacheExists('is_amazon_rds')) {
2825 return PMA_Util::cacheGet('is_amazon_rds');
2827 $sql = 'SELECT @@basedir';
2828 $result = $this->fetchResult($sql);
2829 $rds = ($result[0] == '/rdsdbbin/mysql/');
2830 PMA_Util::cacheSet('is_amazon_rds', $rds);
2832 return $rds;
2836 * Gets SQL for killing a process.
2838 * @param int $process Process ID
2840 * @return string
2842 public function getKillQuery($process)
2844 if ($this->isAmazonRds()) {
2845 return 'CALL mysql.rds_kill(' . $process . ');';
2846 } else {
2847 return 'KILL ' . $process . ';';