Lock page when changes are done in the SQL editor
[phpmyadmin.git] / libraries / DatabaseInterface.php
blobe0e5c03f63d3c59d513809b64a45e264f492f8e6
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 namespace PMA\libraries;
10 use PMA\libraries\dbi\DBIExtension;
11 use PMA\libraries\LanguageManager;
12 use PMA\libraries\URL;
13 use PMA\libraries\Logging;
15 require_once './libraries/util.lib.php';
17 /**
18 * Main interface for database interactions
20 * @package PhpMyAdmin-DBI
22 class DatabaseInterface
24 /**
25 * Force STORE_RESULT method, ignored by classic MySQL.
27 const QUERY_STORE = 1;
28 /**
29 * Do not read whole query.
31 const QUERY_UNBUFFERED = 2;
32 /**
33 * Get session variable.
35 const GETVAR_SESSION = 1;
36 /**
37 * Get global variable.
39 const GETVAR_GLOBAL = 2;
41 /**
42 * User connection.
44 const CONNECT_USER = 0x100;
45 /**
46 * Control user connection.
48 const CONNECT_CONTROL = 0x101;
49 /**
50 * Auxiliary connection.
52 * Used for example for replication setup.
54 const CONNECT_AUXILIARY = 0x102;
56 /**
57 * @var DBIExtension
59 private $_extension;
61 /**
62 * @var array Table data cache
64 private $_table_cache;
66 /**
67 * @var array Current user and host cache
69 private $_current_user;
71 /**
72 * @var null|string lower_case_table_names value cache
74 private $_lower_case_table_names = null;
76 /**
77 * Constructor
79 * @param DBIExtension $ext Object to be used for database queries
81 public function __construct($ext)
83 $this->_extension = $ext;
84 $this->_table_cache = array();
85 $this->_current_user = array();
88 /**
89 * Checks whether database extension is loaded
91 * @param string $extension mysql extension to check
93 * @return bool
95 public static function checkDbExtension($extension = 'mysql')
97 if (function_exists($extension . '_connect')) {
98 return true;
100 return false;
104 * runs a query
106 * @param string $query SQL query to execute
107 * @param mixed $link optional database link to use
108 * @param int $options optional query options
109 * @param bool $cache_affected_rows whether to cache affected rows
111 * @return mixed
113 public function query($query, $link = null, $options = 0,
114 $cache_affected_rows = true
116 $res = $this->tryQuery($query, $link, $options, $cache_affected_rows)
117 or Util::mysqlDie($this->getError($link), $query);
118 return $res;
122 * Get a cached value from table cache.
124 * @param array $contentPath Array of the name of the target value
125 * @param mixed $default Return value on cache miss
127 * @return mixed cached value or default
129 public function getCachedTableContent($contentPath, $default = null)
131 return \PMA\Util\get($this->_table_cache, $contentPath, $default);
135 * Set an item in table cache using dot notation.
137 * @param array $contentPath Array with the target path
138 * @param mixed $value Target value
140 * @return void
142 public function cacheTableContent($contentPath, $value)
144 $loc = &$this->_table_cache;
146 if (!isset($contentPath)) {
147 $loc = $value;
148 return;
151 while (count($contentPath) > 1) {
152 $key = array_shift($contentPath);
154 // If the key doesn't exist at this depth, we will just create an empty
155 // array to hold the next value, allowing us to create the arrays to hold
156 // final values at the correct depth. Then we'll keep digging into the
157 // array.
158 if (!isset($loc[$key]) || !is_array($loc[$key])) {
159 $loc[$key] = array();
161 $loc = &$loc[$key];
164 $loc[array_shift($contentPath)] = $value;
168 * Clear the table cache.
170 * @return void
172 public function clearTableCache()
174 $this->_table_cache = array();
178 * Caches table data so Table does not require to issue
179 * SHOW TABLE STATUS again
181 * @param array $tables information for tables of some databases
182 * @param string $table table name
184 * @return void
186 private function _cacheTableData($tables, $table)
188 // Note: I don't see why we would need array_merge_recursive() here,
189 // as it creates double entries for the same table (for example a double
190 // entry for Comment when changing the storage engine in Operations)
191 // Note 2: Instead of array_merge(), simply use the + operator because
192 // array_merge() renumbers numeric keys starting with 0, therefore
193 // we would lose a db name that consists only of numbers
195 foreach ($tables as $one_database => $its_tables) {
196 if (isset($this->_table_cache[$one_database])) {
197 // the + operator does not do the intended effect
198 // when the cache for one table already exists
199 if ($table
200 && isset($this->_table_cache[$one_database][$table])
202 unset($this->_table_cache[$one_database][$table]);
204 $this->_table_cache[$one_database]
205 = $this->_table_cache[$one_database] + $tables[$one_database];
206 } else {
207 $this->_table_cache[$one_database] = $tables[$one_database];
213 * Stores query data into session data for debugging purposes
215 * @param string $query Query text
216 * @param object $link database link
217 * @param object|boolean $result Query result
218 * @param integer $time Time to execute query
220 * @return void
222 private function _dbgQuery($query, $link, $result, $time)
224 $dbgInfo = array();
225 $error_message = $this->getError($link);
226 if ($result == false && is_string($error_message)) {
227 $dbgInfo['error']
228 = '<span style="color:red">'
229 . htmlspecialchars($error_message) . '</span>';
231 $dbgInfo['query'] = htmlspecialchars($query);
232 $dbgInfo['time'] = $time;
233 // Get and slightly format backtrace, this is used
234 // in the javascript console.
235 // Strip call to _dbgQuery
236 $dbgInfo['trace'] = Error::processBacktrace(
237 array_slice(debug_backtrace(), 1)
239 $dbgInfo['hash'] = md5($query);
241 $_SESSION['debug']['queries'][] = $dbgInfo;
245 * runs a query and returns the result
247 * @param string $query query to run
248 * @param object $link mysql link resource
249 * @param integer $options query options
250 * @param bool $cache_affected_rows whether to cache affected row
252 * @return mixed
254 public function tryQuery($query, $link = null, $options = 0,
255 $cache_affected_rows = true
257 $debug = $GLOBALS['cfg']['DBG']['sql'];
258 $link = $this->getLink($link);
259 if ($link === false) {
260 return false;
263 if ($debug) {
264 $time = microtime(true);
267 $result = $this->_extension->realQuery($query, $link, $options);
269 if ($cache_affected_rows) {
270 $GLOBALS['cached_affected_rows'] = $this->affectedRows($link, false);
273 if ($debug) {
274 $time = microtime(true) - $time;
275 $this->_dbgQuery($query, $link, $result, $time);
276 if ($GLOBALS['cfg']['DBG']['sqllog']) {
277 openlog('phpMyAdmin', LOG_NDELAY | LOG_PID, LOG_USER);
278 syslog(
279 LOG_INFO,
280 'SQL[' . basename($_SERVER['SCRIPT_NAME']) . ']: '
281 . sprintf('%0.3f', $time) . ' > ' . $query
283 closelog();
287 if ($result !== false && Tracker::isActive()) {
288 Tracker::handleQuery($query);
291 return $result;
295 * Run multi query statement and return results
297 * @param string $multi_query multi query statement to execute
298 * @param mysqli $link mysqli object
300 * @return mysqli_result collection | boolean(false)
302 public function tryMultiQuery($multi_query = '', $link = null)
304 $link = $this->getLink($link);
305 if ($link === false) {
306 return false;
309 return $this->_extension->realMultiQuery($link, $multi_query);
313 * returns array with table names for given db
315 * @param string $database name of database
316 * @param mixed $link mysql link resource|object
318 * @return array tables names
320 public function getTables($database, $link = null)
322 $tables = $this->fetchResult(
323 'SHOW TABLES FROM ' . Util::backquote($database) . ';',
324 null,
326 $link,
327 self::QUERY_STORE
329 if ($GLOBALS['cfg']['NaturalOrder']) {
330 usort($tables, 'strnatcasecmp');
332 return $tables;
336 * returns a segment of the SQL WHERE clause regarding table name and type
338 * @param array|string $table table(s)
339 * @param boolean $tbl_is_group $table is a table group
340 * @param string $table_type whether table or view
342 * @return string a segment of the WHERE clause
344 private function _getTableCondition($table, $tbl_is_group, $table_type)
346 // get table information from information_schema
347 if ($table) {
348 if (is_array($table)) {
349 $sql_where_table = 'AND t.`TABLE_NAME` '
350 . Util::getCollateForIS() . ' IN (\''
351 . implode(
352 '\', \'',
353 array_map(
354 array($this, 'escapeString'),
355 $table
358 . '\')';
359 } elseif (true === $tbl_is_group) {
360 $sql_where_table = 'AND t.`TABLE_NAME` LIKE \''
361 . Util::escapeMysqlWildcards(
362 $GLOBALS['dbi']->escapeString($table)
364 . '%\'';
365 } else {
366 $sql_where_table = 'AND t.`TABLE_NAME` '
367 . Util::getCollateForIS() . ' = \''
368 . $GLOBALS['dbi']->escapeString($table) . '\'';
370 } else {
371 $sql_where_table = '';
374 if ($table_type) {
375 if ($table_type == 'view') {
376 $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE TABLE'";
377 } else if ($table_type == 'table') {
378 $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE TABLE'";
381 return $sql_where_table;
385 * returns the beginning of the SQL statement to fetch the list of tables
387 * @param string[] $this_databases databases to list
388 * @param string $sql_where_table additional condition
390 * @return string the SQL statement
392 private function _getSqlForTablesFull($this_databases, $sql_where_table)
394 $sql = '
395 SELECT *,
396 `TABLE_SCHEMA` AS `Db`,
397 `TABLE_NAME` AS `Name`,
398 `TABLE_TYPE` AS `TABLE_TYPE`,
399 `ENGINE` AS `Engine`,
400 `ENGINE` AS `Type`,
401 `VERSION` AS `Version`,
402 `ROW_FORMAT` AS `Row_format`,
403 `TABLE_ROWS` AS `Rows`,
404 `AVG_ROW_LENGTH` AS `Avg_row_length`,
405 `DATA_LENGTH` AS `Data_length`,
406 `MAX_DATA_LENGTH` AS `Max_data_length`,
407 `INDEX_LENGTH` AS `Index_length`,
408 `DATA_FREE` AS `Data_free`,
409 `AUTO_INCREMENT` AS `Auto_increment`,
410 `CREATE_TIME` AS `Create_time`,
411 `UPDATE_TIME` AS `Update_time`,
412 `CHECK_TIME` AS `Check_time`,
413 `TABLE_COLLATION` AS `Collation`,
414 `CHECKSUM` AS `Checksum`,
415 `CREATE_OPTIONS` AS `Create_options`,
416 `TABLE_COMMENT` AS `Comment`
417 FROM `information_schema`.`TABLES` t
418 WHERE `TABLE_SCHEMA` ' . Util::getCollateForIS() . '
419 IN (\'' . implode("', '", $this_databases) . '\')
420 ' . $sql_where_table;
422 return $sql;
426 * returns array of all tables in given db or dbs
427 * this function expects unquoted names:
428 * RIGHT: my_database
429 * WRONG: `my_database`
430 * WRONG: my\_database
431 * if $tbl_is_group is true, $table is used as filter for table names
433 * <code>
434 * $GLOBALS['dbi']->getTablesFull('my_database');
435 * $GLOBALS['dbi']->getTablesFull('my_database', 'my_table'));
436 * $GLOBALS['dbi']->getTablesFull('my_database', 'my_tables_', true));
437 * </code>
439 * @param string $database database
440 * @param string|array $table table name(s)
441 * @param boolean $tbl_is_group $table is a table group
442 * @param mixed $link mysql link
443 * @param integer $limit_offset zero-based offset for the count
444 * @param boolean|integer $limit_count number of tables to return
445 * @param string $sort_by table attribute to sort by
446 * @param string $sort_order direction to sort (ASC or DESC)
447 * @param string $table_type whether table or view
449 * @todo move into Table
451 * @return array list of tables in given db(s)
453 public function getTablesFull($database, $table = '',
454 $tbl_is_group = false, $link = null, $limit_offset = 0,
455 $limit_count = false, $sort_by = 'Name', $sort_order = 'ASC',
456 $table_type = null
458 if (true === $limit_count) {
459 $limit_count = $GLOBALS['cfg']['MaxTableList'];
461 // prepare and check parameters
462 if (! is_array($database)) {
463 $databases = array($database);
464 } else {
465 $databases = $database;
468 $tables = array();
470 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
471 $sql_where_table = $this->_getTableCondition(
472 $table, $tbl_is_group, $table_type
475 // for PMA bc:
476 // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME`
478 // on non-Windows servers,
479 // added BINARY in the WHERE clause to force a case sensitive
480 // comparison (if we are looking for the db Aa we don't want
481 // to find the db aa)
482 $this_databases = array_map(
483 array($this, 'escapeString'),
484 $databases
487 $sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table);
489 // Sort the tables
490 $sql .= " ORDER BY $sort_by $sort_order";
492 if ($limit_count) {
493 $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
496 $tables = $this->fetchResult(
497 $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link
500 if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
501 // here, the array's first key is by schema name
502 foreach ($tables as $one_database_name => $one_database_tables) {
503 uksort($one_database_tables, 'strnatcasecmp');
505 if ($sort_order == 'DESC') {
506 $one_database_tables = array_reverse($one_database_tables);
508 $tables[$one_database_name] = $one_database_tables;
510 } elseif ($sort_by == 'Data_length') {
511 // Size = Data_length + Index_length
512 foreach ($tables as $one_database_name => $one_database_tables) {
513 uasort(
514 $one_database_tables,
515 function ($a, $b) {
516 $aLength = $a['Data_length'] + $a['Index_length'];
517 $bLength = $b['Data_length'] + $b['Index_length'];
518 return ($aLength == $bLength)
520 : ($aLength < $bLength) ? -1 : 1;
524 if ($sort_order == 'DESC') {
525 $one_database_tables = array_reverse($one_database_tables);
527 $tables[$one_database_name] = $one_database_tables;
530 } // end (get information from table schema)
532 // If permissions are wrong on even one database directory,
533 // information_schema does not return any table info for any database
534 // this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002
535 if (empty($tables)) {
536 foreach ($databases as $each_database) {
537 if ($table || (true === $tbl_is_group) || ! empty($table_type)) {
538 $sql = 'SHOW TABLE STATUS FROM '
539 . Util::backquote($each_database)
540 . ' WHERE';
541 $needAnd = false;
542 if ($table || (true === $tbl_is_group)) {
543 if (is_array($table)) {
544 $sql .= ' `Name` IN (\''
545 . implode(
546 '\', \'',
547 array_map(
548 array($this, 'escapeString'),
549 $table,
550 $link
552 ) . '\')';
553 } else {
554 $sql .= " `Name` LIKE '"
555 . Util::escapeMysqlWildcards(
556 $this->escapeString($table, $link)
558 . "%'";
560 $needAnd = true;
562 if (! empty($table_type)) {
563 if ($needAnd) {
564 $sql .= " AND";
566 if ($table_type == 'view') {
567 $sql .= " `Comment` = 'VIEW'";
568 } else if ($table_type == 'table') {
569 $sql .= " `Comment` != 'VIEW'";
572 } else {
573 $sql = 'SHOW TABLE STATUS FROM '
574 . Util::backquote($each_database);
577 $each_tables = $this->fetchResult($sql, 'Name', null, $link);
579 // Sort naturally if the config allows it and we're sorting
580 // the Name column.
581 if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
582 uksort($each_tables, 'strnatcasecmp');
584 if ($sort_order == 'DESC') {
585 $each_tables = array_reverse($each_tables);
587 } else {
588 // Prepare to sort by creating array of the selected sort
589 // value to pass to array_multisort
591 // Size = Data_length + Index_length
592 if ($sort_by == 'Data_length') {
593 foreach ($each_tables as $table_name => $table_data) {
594 ${$sort_by}[$table_name] = strtolower(
595 $table_data['Data_length']
596 + $table_data['Index_length']
599 } else {
600 foreach ($each_tables as $table_name => $table_data) {
601 ${$sort_by}[$table_name]
602 = strtolower($table_data[$sort_by]);
606 if (! empty($$sort_by)) {
607 if ($sort_order == 'DESC') {
608 array_multisort($$sort_by, SORT_DESC, $each_tables);
609 } else {
610 array_multisort($$sort_by, SORT_ASC, $each_tables);
614 // cleanup the temporary sort array
615 unset($$sort_by);
618 if ($limit_count) {
619 $each_tables = array_slice(
620 $each_tables, $limit_offset, $limit_count
624 foreach ($each_tables as $table_name => $each_table) {
625 if (! isset($each_tables[$table_name]['Type'])
626 && isset($each_tables[$table_name]['Engine'])
628 // pma BC, same parts of PMA still uses 'Type'
629 $each_tables[$table_name]['Type']
630 =& $each_tables[$table_name]['Engine'];
631 } elseif (! isset($each_tables[$table_name]['Engine'])
632 && isset($each_tables[$table_name]['Type'])
634 // old MySQL reports Type, newer MySQL reports Engine
635 $each_tables[$table_name]['Engine']
636 =& $each_tables[$table_name]['Type'];
639 // Compatibility with INFORMATION_SCHEMA output
640 $each_tables[$table_name]['TABLE_SCHEMA']
641 = $each_database;
642 $each_tables[$table_name]['TABLE_NAME']
643 =& $each_tables[$table_name]['Name'];
644 $each_tables[$table_name]['ENGINE']
645 =& $each_tables[$table_name]['Engine'];
646 $each_tables[$table_name]['VERSION']
647 =& $each_tables[$table_name]['Version'];
648 $each_tables[$table_name]['ROW_FORMAT']
649 =& $each_tables[$table_name]['Row_format'];
650 $each_tables[$table_name]['TABLE_ROWS']
651 =& $each_tables[$table_name]['Rows'];
652 $each_tables[$table_name]['AVG_ROW_LENGTH']
653 =& $each_tables[$table_name]['Avg_row_length'];
654 $each_tables[$table_name]['DATA_LENGTH']
655 =& $each_tables[$table_name]['Data_length'];
656 $each_tables[$table_name]['MAX_DATA_LENGTH']
657 =& $each_tables[$table_name]['Max_data_length'];
658 $each_tables[$table_name]['INDEX_LENGTH']
659 =& $each_tables[$table_name]['Index_length'];
660 $each_tables[$table_name]['DATA_FREE']
661 =& $each_tables[$table_name]['Data_free'];
662 $each_tables[$table_name]['AUTO_INCREMENT']
663 =& $each_tables[$table_name]['Auto_increment'];
664 $each_tables[$table_name]['CREATE_TIME']
665 =& $each_tables[$table_name]['Create_time'];
666 $each_tables[$table_name]['UPDATE_TIME']
667 =& $each_tables[$table_name]['Update_time'];
668 $each_tables[$table_name]['CHECK_TIME']
669 =& $each_tables[$table_name]['Check_time'];
670 $each_tables[$table_name]['TABLE_COLLATION']
671 =& $each_tables[$table_name]['Collation'];
672 $each_tables[$table_name]['CHECKSUM']
673 =& $each_tables[$table_name]['Checksum'];
674 $each_tables[$table_name]['CREATE_OPTIONS']
675 =& $each_tables[$table_name]['Create_options'];
676 $each_tables[$table_name]['TABLE_COMMENT']
677 =& $each_tables[$table_name]['Comment'];
679 if (strtoupper($each_tables[$table_name]['Comment']) === 'VIEW'
680 && $each_tables[$table_name]['Engine'] == null
682 $each_tables[$table_name]['TABLE_TYPE'] = 'VIEW';
683 } elseif ($each_database == 'information_schema') {
684 $each_tables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW';
685 } else {
687 * @todo difference between 'TEMPORARY' and 'BASE TABLE'
688 * but how to detect?
690 $each_tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE';
694 $tables[$each_database] = $each_tables;
698 // cache table data
699 // so Table does not require to issue SHOW TABLE STATUS again
700 $this->_cacheTableData($tables, $table);
702 if (is_array($database)) {
703 return $tables;
706 if (isset($tables[$database])) {
707 return $tables[$database];
710 if (isset($tables[mb_strtolower($database)])) {
711 // on windows with lower_case_table_names = 1
712 // MySQL returns
713 // with SHOW DATABASES or information_schema.SCHEMATA: `Test`
714 // but information_schema.TABLES gives `test`
715 // bug #2036
716 // https://sourceforge.net/p/phpmyadmin/bugs/2036/
717 return $tables[mb_strtolower($database)];
720 return $tables;
724 * Get VIEWs in a particular database
726 * @param string $db Database name to look in
728 * @return array $views Set of VIEWs inside the database
730 public function getVirtualTables($db)
733 $tables_full = $this->getTablesFull($db);
734 $views = array();
736 foreach ($tables_full as $table=>$tmp) {
738 $_table = $this->getTable($db, $table);
739 if ($_table->isView()) {
740 $views[] = $table;
745 return $views;
751 * returns array with databases containing extended infos about them
753 * @param string $database database
754 * @param boolean $force_stats retrieve stats also for MySQL < 5
755 * @param object $link mysql link
756 * @param string $sort_by column to order by
757 * @param string $sort_order ASC or DESC
758 * @param integer $limit_offset starting offset for LIMIT
759 * @param bool|int $limit_count row count for LIMIT or true
760 * for $GLOBALS['cfg']['MaxDbList']
762 * @todo move into ListDatabase?
764 * @return array $databases
766 public function getDatabasesFull($database = null, $force_stats = false,
767 $link = null, $sort_by = 'SCHEMA_NAME', $sort_order = 'ASC',
768 $limit_offset = 0, $limit_count = false
770 $sort_order = strtoupper($sort_order);
772 if (true === $limit_count) {
773 $limit_count = $GLOBALS['cfg']['MaxDbList'];
776 $apply_limit_and_order_manual = true;
778 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
780 * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT
781 * cause MySQL does not support natural ordering,
782 * we have to do it afterward
784 $limit = '';
785 if (! $GLOBALS['cfg']['NaturalOrder']) {
786 if ($limit_count) {
787 $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
790 $apply_limit_and_order_manual = false;
793 // get table information from information_schema
794 if (! empty($database)) {
795 $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \''
796 . $this->escapeString($database, $link) . '\'';
797 } else {
798 $sql_where_schema = '';
801 $sql = 'SELECT *,
802 CAST(BIN_NAME AS CHAR CHARACTER SET utf8) AS SCHEMA_NAME
803 FROM (';
804 $sql .= 'SELECT
805 BINARY s.SCHEMA_NAME AS BIN_NAME,
806 s.DEFAULT_COLLATION_NAME';
807 if ($force_stats) {
808 $sql .= ',
809 COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,
810 SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS,
811 SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH,
812 SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH,
813 SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH,
814 SUM(t.DATA_LENGTH + t.INDEX_LENGTH)
815 AS SCHEMA_LENGTH,
816 SUM(IF(t.ENGINE <> \'InnoDB\', t.DATA_FREE, 0))
817 AS SCHEMA_DATA_FREE';
819 $sql .= '
820 FROM `information_schema`.SCHEMATA s';
821 if ($force_stats) {
822 $sql .= '
823 LEFT JOIN `information_schema`.TABLES t
824 ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME';
826 $sql .= $sql_where_schema . '
827 GROUP BY BINARY s.SCHEMA_NAME, s.DEFAULT_COLLATION_NAME
828 ORDER BY ';
829 if ($sort_by == 'SCHEMA_NAME'
830 || $sort_by == 'DEFAULT_COLLATION_NAME'
832 $sql .= 'BINARY ';
834 $sql .= Util::backquote($sort_by)
835 . ' ' . $sort_order
836 . $limit;
837 $sql .= ') a';
839 $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link);
841 $mysql_error = $this->getError($link);
842 if (! count($databases) && $GLOBALS['errno']) {
843 Util::mysqlDie($mysql_error, $sql);
846 // display only databases also in official database list
847 // f.e. to apply hide_db and only_db
848 $drops = array_diff(
849 array_keys($databases), (array) $GLOBALS['dblist']->databases
851 foreach ($drops as $drop) {
852 unset($databases[$drop]);
854 } else {
855 $databases = array();
856 foreach ($GLOBALS['dblist']->databases as $database_name) {
857 // Compatibility with INFORMATION_SCHEMA output
858 $databases[$database_name]['SCHEMA_NAME'] = $database_name;
860 $databases[$database_name]['DEFAULT_COLLATION_NAME']
861 = $this->getDbCollation($database_name);
863 if (!$force_stats) {
864 continue;
867 // get additional info about tables
868 $databases[$database_name]['SCHEMA_TABLES'] = 0;
869 $databases[$database_name]['SCHEMA_TABLE_ROWS'] = 0;
870 $databases[$database_name]['SCHEMA_DATA_LENGTH'] = 0;
871 $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] = 0;
872 $databases[$database_name]['SCHEMA_INDEX_LENGTH'] = 0;
873 $databases[$database_name]['SCHEMA_LENGTH'] = 0;
874 $databases[$database_name]['SCHEMA_DATA_FREE'] = 0;
876 $res = $this->query(
877 'SHOW TABLE STATUS FROM '
878 . Util::backquote($database_name) . ';'
881 if ($res === false) {
882 unset($res);
883 continue;
886 while ($row = $this->fetchAssoc($res)) {
887 $databases[$database_name]['SCHEMA_TABLES']++;
888 $databases[$database_name]['SCHEMA_TABLE_ROWS']
889 += $row['Rows'];
890 $databases[$database_name]['SCHEMA_DATA_LENGTH']
891 += $row['Data_length'];
892 $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH']
893 += $row['Max_data_length'];
894 $databases[$database_name]['SCHEMA_INDEX_LENGTH']
895 += $row['Index_length'];
897 // for InnoDB, this does not contain the number of
898 // overhead bytes but the total free space
899 if ('InnoDB' != $row['Engine']) {
900 $databases[$database_name]['SCHEMA_DATA_FREE']
901 += $row['Data_free'];
903 $databases[$database_name]['SCHEMA_LENGTH']
904 += $row['Data_length'] + $row['Index_length'];
906 $this->freeResult($res);
907 unset($res);
912 * apply limit and order manually now
913 * (caused by older MySQL < 5 or $GLOBALS['cfg']['NaturalOrder'])
915 if ($apply_limit_and_order_manual) {
916 $GLOBALS['callback_sort_order'] = $sort_order;
917 $GLOBALS['callback_sort_by'] = $sort_by;
918 usort(
919 $databases,
920 array('PMA\libraries\DatabaseInterface', '_usortComparisonCallback')
922 unset($GLOBALS['callback_sort_order'], $GLOBALS['callback_sort_by']);
925 * now apply limit
927 if ($limit_count) {
928 $databases = array_slice($databases, $limit_offset, $limit_count);
932 return $databases;
936 * usort comparison callback
938 * @param string $a first argument to sort
939 * @param string $b second argument to sort
941 * @return integer a value representing whether $a should be before $b in the
942 * sorted array or not
944 * @access private
946 private static function _usortComparisonCallback($a, $b)
948 if ($GLOBALS['cfg']['NaturalOrder']) {
949 $sorter = 'strnatcasecmp';
950 } else {
951 $sorter = 'strcasecmp';
953 /* No sorting when key is not present */
954 if (! isset($a[$GLOBALS['callback_sort_by']])
955 || ! isset($b[$GLOBALS['callback_sort_by']])
957 return 0;
959 // produces f.e.:
960 // return -1 * strnatcasecmp($a["SCHEMA_TABLES"], $b["SCHEMA_TABLES"])
961 return ($GLOBALS['callback_sort_order'] == 'ASC' ? 1 : -1) * $sorter(
962 $a[$GLOBALS['callback_sort_by']], $b[$GLOBALS['callback_sort_by']]
964 } // end of the '_usortComparisonCallback()' method
967 * returns detailed array with all columns for sql
969 * @param string $sql_query target SQL query to get columns
970 * @param array $view_columns alias for columns
972 * @return array
974 public function getColumnMapFromSql($sql_query, $view_columns = array())
976 $result = $this->tryQuery($sql_query);
978 if ($result === false) {
979 return array();
982 $meta = $this->getFieldsMeta(
983 $result
986 $nbFields = count($meta);
987 if ($nbFields <= 0) {
988 return array();
991 $column_map = array();
992 $nbColumns = count($view_columns);
994 for ($i=0; $i < $nbFields; $i++) {
996 $map = array();
997 $map['table_name'] = $meta[$i]->table;
998 $map['refering_column'] = $meta[$i]->name;
1000 if ($nbColumns > 1) {
1001 $map['real_column'] = $view_columns[$i];
1004 $column_map[] = $map;
1007 return $column_map;
1011 * returns detailed array with all columns for given table in database,
1012 * or all tables/databases
1014 * @param string $database name of database
1015 * @param string $table name of table to retrieve columns from
1016 * @param string $column name of specific column
1017 * @param mixed $link mysql link resource
1019 * @return array
1021 public function getColumnsFull($database = null, $table = null,
1022 $column = null, $link = null
1024 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1025 $sql_wheres = array();
1026 $array_keys = array();
1028 // get columns information from information_schema
1029 if (null !== $database) {
1030 $sql_wheres[] = '`TABLE_SCHEMA` = \''
1031 . $this->escapeString($database, $link) . '\' ';
1032 } else {
1033 $array_keys[] = 'TABLE_SCHEMA';
1035 if (null !== $table) {
1036 $sql_wheres[] = '`TABLE_NAME` = \''
1037 . $this->escapeString($table, $link) . '\' ';
1038 } else {
1039 $array_keys[] = 'TABLE_NAME';
1041 if (null !== $column) {
1042 $sql_wheres[] = '`COLUMN_NAME` = \''
1043 . $this->escapeString($column, $link) . '\' ';
1044 } else {
1045 $array_keys[] = 'COLUMN_NAME';
1048 // for PMA bc:
1049 // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]`
1050 $sql = '
1051 SELECT *,
1052 `COLUMN_NAME` AS `Field`,
1053 `COLUMN_TYPE` AS `Type`,
1054 `COLLATION_NAME` AS `Collation`,
1055 `IS_NULLABLE` AS `Null`,
1056 `COLUMN_KEY` AS `Key`,
1057 `COLUMN_DEFAULT` AS `Default`,
1058 `EXTRA` AS `Extra`,
1059 `PRIVILEGES` AS `Privileges`,
1060 `COLUMN_COMMENT` AS `Comment`
1061 FROM `information_schema`.`COLUMNS`';
1063 if (count($sql_wheres)) {
1064 $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres);
1066 return $this->fetchResult($sql, $array_keys, null, $link);
1067 } else {
1068 $columns = array();
1069 if (null === $database) {
1070 foreach ($GLOBALS['dblist']->databases as $database) {
1071 $columns[$database] = $this->getColumnsFull(
1072 $database, null, null, $link
1075 return $columns;
1076 } elseif (null === $table) {
1077 $tables = $this->getTables($database);
1078 foreach ($tables as $table) {
1079 $columns[$table] = $this->getColumnsFull(
1080 $database, $table, null, $link
1083 return $columns;
1085 $sql = 'SHOW FULL COLUMNS FROM '
1086 . Util::backquote($database) . '.' . Util::backquote($table);
1087 if (null !== $column) {
1088 $sql .= " LIKE '" . $this->escapeString($column, $link) . "'";
1091 $columns = $this->fetchResult($sql, 'Field', null, $link);
1092 $ordinal_position = 1;
1093 foreach ($columns as $column_name => $each_column) {
1095 // Compatibility with INFORMATION_SCHEMA output
1096 $columns[$column_name]['COLUMN_NAME']
1097 =& $columns[$column_name]['Field'];
1098 $columns[$column_name]['COLUMN_TYPE']
1099 =& $columns[$column_name]['Type'];
1100 $columns[$column_name]['COLLATION_NAME']
1101 =& $columns[$column_name]['Collation'];
1102 $columns[$column_name]['IS_NULLABLE']
1103 =& $columns[$column_name]['Null'];
1104 $columns[$column_name]['COLUMN_KEY']
1105 =& $columns[$column_name]['Key'];
1106 $columns[$column_name]['COLUMN_DEFAULT']
1107 =& $columns[$column_name]['Default'];
1108 $columns[$column_name]['EXTRA']
1109 =& $columns[$column_name]['Extra'];
1110 $columns[$column_name]['PRIVILEGES']
1111 =& $columns[$column_name]['Privileges'];
1112 $columns[$column_name]['COLUMN_COMMENT']
1113 =& $columns[$column_name]['Comment'];
1115 $columns[$column_name]['TABLE_CATALOG'] = null;
1116 $columns[$column_name]['TABLE_SCHEMA'] = $database;
1117 $columns[$column_name]['TABLE_NAME'] = $table;
1118 $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position;
1119 $columns[$column_name]['DATA_TYPE']
1120 = substr(
1121 $columns[$column_name]['COLUMN_TYPE'],
1123 strpos($columns[$column_name]['COLUMN_TYPE'], '(')
1126 * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE
1128 $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null;
1130 * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH
1132 $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null;
1133 $columns[$column_name]['NUMERIC_PRECISION'] = null;
1134 $columns[$column_name]['NUMERIC_SCALE'] = null;
1135 $columns[$column_name]['CHARACTER_SET_NAME']
1136 = substr(
1137 $columns[$column_name]['COLLATION_NAME'],
1139 strpos($columns[$column_name]['COLLATION_NAME'], '_')
1142 $ordinal_position++;
1145 if (null !== $column) {
1146 return reset($columns);
1149 return $columns;
1154 * Returns SQL query for fetching columns for a table
1156 * The 'Key' column is not calculated properly, use $GLOBALS['dbi']->getColumns()
1157 * to get correct values.
1159 * @param string $database name of database
1160 * @param string $table name of table to retrieve columns from
1161 * @param string $column name of column, null to show all columns
1162 * @param boolean $full whether to return full info or only column names
1164 * @see getColumns()
1166 * @return string
1168 public function getColumnsSql($database, $table, $column = null, $full = false)
1170 $sql = 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM '
1171 . Util::backquote($database) . '.' . Util::backquote($table)
1172 . (($column !== null) ? "LIKE '"
1173 . $GLOBALS['dbi']->escapeString($column) . "'" : '');
1175 return $sql;
1179 * Returns descriptions of columns in given table (all or given by $column)
1181 * @param string $database name of database
1182 * @param string $table name of table to retrieve columns from
1183 * @param string $column name of column, null to show all columns
1184 * @param boolean $full whether to return full info or only column names
1185 * @param mixed $link mysql link resource
1187 * @return array array indexed by column names or,
1188 * if $column is given, flat array description
1190 public function getColumns($database, $table, $column = null, $full = false,
1191 $link = null
1193 $sql = $this->getColumnsSql($database, $table, $column, $full);
1194 $fields = $this->fetchResult($sql, 'Field', null, $link);
1195 if (! is_array($fields) || count($fields) == 0) {
1196 return array();
1198 // Check if column is a part of multiple-column index and set its 'Key'.
1199 $indexes = Index::getFromTable($table, $database);
1200 foreach ($fields as $field => $field_data) {
1201 if (!empty($field_data['Key'])) {
1202 continue;
1205 foreach ($indexes as $index) {
1206 /** @var Index $index */
1207 if (!$index->hasColumn($field)) {
1208 continue;
1211 $index_columns = $index->getColumns();
1212 if ($index_columns[$field]->getSeqInIndex() > 1) {
1213 if ($index->isUnique()) {
1214 $fields[$field]['Key'] = 'UNI';
1215 } else {
1216 $fields[$field]['Key'] = 'MUL';
1222 return ($column != null) ? array_shift($fields) : $fields;
1226 * Returns all column names in given table
1228 * @param string $database name of database
1229 * @param string $table name of table to retrieve columns from
1230 * @param mixed $link mysql link resource
1232 * @return null|array
1234 public function getColumnNames($database, $table, $link = null)
1236 $sql = $this->getColumnsSql($database, $table);
1237 // We only need the 'Field' column which contains the table's column names
1238 $fields = array_keys($this->fetchResult($sql, 'Field', null, $link));
1240 if (! is_array($fields) || count($fields) == 0) {
1241 return null;
1243 return $fields;
1247 * Returns SQL for fetching information on table indexes (SHOW INDEXES)
1249 * @param string $database name of database
1250 * @param string $table name of the table whose indexes are to be retrieved
1251 * @param string $where additional conditions for WHERE
1253 * @return string SQL for getting indexes
1255 public function getTableIndexesSql($database, $table, $where = null)
1257 $sql = 'SHOW INDEXES FROM ' . Util::backquote($database) . '.'
1258 . Util::backquote($table);
1259 if ($where) {
1260 $sql .= ' WHERE (' . $where . ')';
1262 return $sql;
1266 * Returns indexes of a table
1268 * @param string $database name of database
1269 * @param string $table name of the table whose indexes are to be retrieved
1270 * @param mixed $link mysql link resource
1272 * @return array $indexes
1274 public function getTableIndexes($database, $table, $link = null)
1276 $sql = $this->getTableIndexesSql($database, $table);
1277 $indexes = $this->fetchResult($sql, null, null, $link);
1279 if (! is_array($indexes) || count($indexes) < 1) {
1280 return array();
1282 return $indexes;
1286 * returns value of given mysql server variable
1288 * @param string $var mysql server variable name
1289 * @param int $type DatabaseInterface::GETVAR_SESSION |
1290 * DatabaseInterface::GETVAR_GLOBAL
1291 * @param mixed $link mysql link resource|object
1293 * @return mixed value for mysql server variable
1295 public function getVariable(
1296 $var, $type = self::GETVAR_SESSION, $link = null
1298 $link = $this->getLink($link);
1299 if ($link === false) {
1300 return false;
1303 switch ($type) {
1304 case self::GETVAR_SESSION:
1305 $modifier = ' SESSION';
1306 break;
1307 case self::GETVAR_GLOBAL:
1308 $modifier = ' GLOBAL';
1309 break;
1310 default:
1311 $modifier = '';
1313 return $this->fetchValue(
1314 'SHOW' . $modifier . ' VARIABLES LIKE \'' . $var . '\';', 0, 1, $link
1319 * Sets new value for a variable if it is different from the current value
1321 * @param string $var variable name
1322 * @param string $value value to set
1323 * @param mixed $link mysql link resource|object
1325 * @return bool whether query was a successful
1327 public function setVariable($var, $value, $link = null)
1329 $link = $this->getLink($link);
1330 if ($link === false) {
1331 return false;
1333 $current_value = $this->getVariable(
1334 $var, self::GETVAR_SESSION, $link
1336 if ($current_value == $value) {
1337 return true;
1340 return $this->query("SET " . $var . " = " . $value . ';', $link);
1344 * Function called just after a connection to the MySQL database server has
1345 * been established. It sets the connection collation, and determines the
1346 * version of MySQL which is running.
1348 * @param mixed $link mysql link resource|object
1350 * @return void
1352 public function postConnect($link)
1354 if (! defined('PMA_MYSQL_INT_VERSION')) {
1355 $version = $this->fetchSingleRow(
1356 'SELECT @@version, @@version_comment',
1357 'ASSOC',
1358 $link
1361 if ($version) {
1362 $match = explode('.', $version['@@version']);
1363 define('PMA_MYSQL_MAJOR_VERSION', (int)$match[0]);
1364 define(
1365 'PMA_MYSQL_INT_VERSION',
1366 (int) sprintf(
1367 '%d%02d%02d', $match[0], $match[1], intval($match[2])
1370 define('PMA_MYSQL_STR_VERSION', $version['@@version']);
1371 define(
1372 'PMA_MYSQL_VERSION_COMMENT',
1373 $version['@@version_comment']
1375 } else {
1376 define('PMA_MYSQL_INT_VERSION', 50501);
1377 define('PMA_MYSQL_MAJOR_VERSION', 5);
1378 define('PMA_MYSQL_STR_VERSION', '5.05.01');
1379 define('PMA_MYSQL_VERSION_COMMENT', '');
1381 /* Detect MariaDB */
1382 if (mb_strpos(PMA_MYSQL_STR_VERSION, 'MariaDB') !== false) {
1383 define('PMA_MARIADB', true);
1384 } else {
1385 define('PMA_MARIADB', false);
1389 if (PMA_MYSQL_INT_VERSION > 50503) {
1390 $default_charset = 'utf8mb4';
1391 $default_collation = 'utf8mb4_general_ci';
1392 } else {
1393 $default_charset = 'utf8';
1394 $default_collation = 'utf8_general_ci';
1396 $collation_connection = $GLOBALS['PMA_Config']->get('collation_connection');
1397 if (! empty($collation_connection)) {
1398 $this->query(
1399 "SET CHARACTER SET '$default_charset';",
1400 $link,
1401 self::QUERY_STORE
1403 /* Automatically adjust collation if not supported by server */
1404 if ($default_charset == 'utf8'
1405 && strncmp('utf8mb4_', $collation_connection, 8) == 0
1407 $collation_connection = 'utf8_' . substr($collation_connection, 8);
1409 $result = $this->tryQuery(
1410 "SET collation_connection = '"
1411 . $this->escapeString($collation_connection, $link)
1412 . "';",
1413 $link,
1414 self::QUERY_STORE
1416 if ($result === false) {
1417 trigger_error(
1418 __('Failed to set configured collation connection!'),
1419 E_USER_WARNING
1421 $this->query(
1422 "SET collation_connection = '"
1423 . $this->escapeString($collation_connection, $link)
1424 . "';",
1425 $link,
1426 self::QUERY_STORE
1429 } else {
1430 $this->query(
1431 "SET NAMES '$default_charset' COLLATE '$default_collation';",
1432 $link,
1433 self::QUERY_STORE
1437 /* Locale for messages */
1438 $locale = LanguageManager::getInstance()->getCurrentLanguage()->getMySQLLocale();
1439 if (! empty($locale)) {
1440 $this->query(
1441 "SET lc_messages = '" . $locale . "';",
1442 $link,
1443 self::QUERY_STORE
1449 * returns a single value from the given result or query,
1450 * if the query or the result has more than one row or field
1451 * the first field of the first row is returned
1453 * <code>
1454 * $sql = 'SELECT `name` FROM `user` WHERE `id` = 123';
1455 * $user_name = $GLOBALS['dbi']->fetchValue($sql);
1456 * // produces
1457 * // $user_name = 'John Doe'
1458 * </code>
1460 * @param string $query The query to execute
1461 * @param integer $row_number row to fetch the value from,
1462 * starting at 0, with 0 being default
1463 * @param integer|string $field field to fetch the value from,
1464 * starting at 0, with 0 being default
1465 * @param object $link mysql link
1467 * @return mixed value of first field in first row from result
1468 * or false if not found
1470 public function fetchValue($query, $row_number = 0, $field = 0, $link = null)
1472 $value = false;
1474 $result = $this->tryQuery(
1475 $query,
1476 $link,
1477 self::QUERY_STORE,
1478 false
1480 if ($result === false) {
1481 return false;
1484 // return false if result is empty or false
1485 // or requested row is larger than rows in result
1486 if ($this->numRows($result) < ($row_number + 1)) {
1487 return $value;
1490 // if $field is an integer use non associative mysql fetch function
1491 if (is_int($field)) {
1492 $fetch_function = 'fetchRow';
1493 } else {
1494 $fetch_function = 'fetchAssoc';
1497 // get requested row
1498 for ($i = 0; $i <= $row_number; $i++) {
1499 $row = $this->$fetch_function($result);
1501 $this->freeResult($result);
1503 // return requested field
1504 if (isset($row[$field])) {
1505 $value = $row[$field];
1508 return $value;
1512 * returns only the first row from the result
1514 * <code>
1515 * $sql = 'SELECT * FROM `user` WHERE `id` = 123';
1516 * $user = $GLOBALS['dbi']->fetchSingleRow($sql);
1517 * // produces
1518 * // $user = array('id' => 123, 'name' => 'John Doe')
1519 * </code>
1521 * @param string $query The query to execute
1522 * @param string $type NUM|ASSOC|BOTH returned array should either
1523 * numeric associative or both
1524 * @param object $link mysql link
1526 * @return array|boolean first row from result
1527 * or false if result is empty
1529 public function fetchSingleRow($query, $type = 'ASSOC', $link = null)
1531 $result = $this->tryQuery(
1532 $query,
1533 $link,
1534 self::QUERY_STORE,
1535 false
1537 if ($result === false) {
1538 return false;
1541 // return false if result is empty or false
1542 if (! $this->numRows($result)) {
1543 return false;
1546 switch ($type) {
1547 case 'NUM' :
1548 $fetch_function = 'fetchRow';
1549 break;
1550 case 'ASSOC' :
1551 $fetch_function = 'fetchAssoc';
1552 break;
1553 case 'BOTH' :
1554 default :
1555 $fetch_function = 'fetchArray';
1556 break;
1559 $row = $this->$fetch_function($result);
1560 $this->freeResult($result);
1561 return $row;
1565 * Returns row or element of a row
1567 * @param array $row Row to process
1568 * @param string|null $value Which column to return
1570 * @return mixed
1572 private function _fetchValue($row, $value)
1574 if (is_null($value)) {
1575 return $row;
1576 } else {
1577 return $row[$value];
1582 * returns all rows in the resultset in one array
1584 * <code>
1585 * $sql = 'SELECT * FROM `user`';
1586 * $users = $GLOBALS['dbi']->fetchResult($sql);
1587 * // produces
1588 * // $users[] = array('id' => 123, 'name' => 'John Doe')
1590 * $sql = 'SELECT `id`, `name` FROM `user`';
1591 * $users = $GLOBALS['dbi']->fetchResult($sql, 'id');
1592 * // produces
1593 * // $users['123'] = array('id' => 123, 'name' => 'John Doe')
1595 * $sql = 'SELECT `id`, `name` FROM `user`';
1596 * $users = $GLOBALS['dbi']->fetchResult($sql, 0);
1597 * // produces
1598 * // $users['123'] = array(0 => 123, 1 => 'John Doe')
1600 * $sql = 'SELECT `id`, `name` FROM `user`';
1601 * $users = $GLOBALS['dbi']->fetchResult($sql, 'id', 'name');
1602 * // or
1603 * $users = $GLOBALS['dbi']->fetchResult($sql, 0, 1);
1604 * // produces
1605 * // $users['123'] = 'John Doe'
1607 * $sql = 'SELECT `name` FROM `user`';
1608 * $users = $GLOBALS['dbi']->fetchResult($sql);
1609 * // produces
1610 * // $users[] = 'John Doe'
1612 * $sql = 'SELECT `group`, `name` FROM `user`'
1613 * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', null), 'name');
1614 * // produces
1615 * // $users['admin'][] = 'John Doe'
1617 * $sql = 'SELECT `group`, `name` FROM `user`'
1618 * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', 'name'), 'id');
1619 * // produces
1620 * // $users['admin']['John Doe'] = '123'
1621 * </code>
1623 * @param string $query query to execute
1624 * @param string|integer|array $key field-name or offset
1625 * used as key for array
1626 * or array of those
1627 * @param string|integer $value value-name or offset
1628 * used as value for array
1629 * @param object $link mysql link
1630 * @param integer $options query options
1632 * @return array resultrows or values indexed by $key
1634 public function fetchResult($query, $key = null, $value = null,
1635 $link = null, $options = 0
1637 $resultrows = array();
1639 $result = $this->tryQuery($query, $link, $options, false);
1641 // return empty array if result is empty or false
1642 if ($result === false) {
1643 return $resultrows;
1646 $fetch_function = 'fetchAssoc';
1648 // no nested array if only one field is in result
1649 if (null === $key && 1 === $this->numFields($result)) {
1650 $value = 0;
1651 $fetch_function = 'fetchRow';
1654 // if $key is an integer use non associative mysql fetch function
1655 if (is_int($key)) {
1656 $fetch_function = 'fetchRow';
1659 if (null === $key) {
1660 while ($row = $this->$fetch_function($result)) {
1661 $resultrows[] = $this->_fetchValue($row, $value);
1663 } else {
1664 if (is_array($key)) {
1665 while ($row = $this->$fetch_function($result)) {
1666 $result_target =& $resultrows;
1667 foreach ($key as $key_index) {
1668 if (null === $key_index) {
1669 $result_target =& $result_target[];
1670 continue;
1673 if (! isset($result_target[$row[$key_index]])) {
1674 $result_target[$row[$key_index]] = array();
1676 $result_target =& $result_target[$row[$key_index]];
1678 $result_target = $this->_fetchValue($row, $value);
1680 } else {
1681 while ($row = $this->$fetch_function($result)) {
1682 $resultrows[$row[$key]] = $this->_fetchValue($row, $value);
1687 $this->freeResult($result);
1688 return $resultrows;
1692 * Get supported SQL compatibility modes
1694 * @return array supported SQL compatibility modes
1696 public function getCompatibilities()
1698 $compats = array('NONE');
1699 $compats[] = 'ANSI';
1700 $compats[] = 'DB2';
1701 $compats[] = 'MAXDB';
1702 $compats[] = 'MYSQL323';
1703 $compats[] = 'MYSQL40';
1704 $compats[] = 'MSSQL';
1705 $compats[] = 'ORACLE';
1706 // removed; in MySQL 5.0.33, this produces exports that
1707 // can't be read by POSTGRESQL (see our bug #1596328)
1708 //$compats[] = 'POSTGRESQL';
1709 $compats[] = 'TRADITIONAL';
1711 return $compats;
1715 * returns warnings for last query
1717 * @param object $link mysql link resource
1719 * @return array warnings
1721 public function getWarnings($link = null)
1723 $link = $this->getLink($link);
1724 if ($link === false) {
1725 return false;
1728 return $this->fetchResult('SHOW WARNINGS', null, null, $link);
1732 * returns an array of PROCEDURE or FUNCTION names for a db
1734 * @param string $db db name
1735 * @param string $which PROCEDURE | FUNCTION
1736 * @param object $link mysql link
1738 * @return array the procedure names or function names
1740 public function getProceduresOrFunctions($db, $which, $link = null)
1742 $shows = $this->fetchResult(
1743 'SHOW ' . $which . ' STATUS;', null, null, $link
1745 $result = array();
1746 foreach ($shows as $one_show) {
1747 if ($one_show['Db'] == $db && $one_show['Type'] == $which) {
1748 $result[] = $one_show['Name'];
1751 return($result);
1755 * returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW
1757 * @param string $db db name
1758 * @param string $which PROCEDURE | FUNCTION | EVENT | VIEW
1759 * @param string $name the procedure|function|event|view name
1760 * @param object $link MySQL link
1762 * @return string the definition
1764 public function getDefinition($db, $which, $name, $link = null)
1766 $returned_field = array(
1767 'PROCEDURE' => 'Create Procedure',
1768 'FUNCTION' => 'Create Function',
1769 'EVENT' => 'Create Event',
1770 'VIEW' => 'Create View'
1772 $query = 'SHOW CREATE ' . $which . ' '
1773 . Util::backquote($db) . '.'
1774 . Util::backquote($name);
1775 return($this->fetchValue($query, 0, $returned_field[$which], $link));
1779 * returns details about the PROCEDUREs or FUNCTIONs for a specific database
1780 * or details about a specific routine
1782 * @param string $db db name
1783 * @param string $which PROCEDURE | FUNCTION or null for both
1784 * @param string $name name of the routine (to fetch a specific routine)
1786 * @return array information about ROCEDUREs or FUNCTIONs
1788 public function getRoutines($db, $which = null, $name = '')
1790 $routines = array();
1791 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1792 $query = "SELECT"
1793 . " `ROUTINE_SCHEMA` AS `Db`,"
1794 . " `SPECIFIC_NAME` AS `Name`,"
1795 . " `ROUTINE_TYPE` AS `Type`,"
1796 . " `DEFINER` AS `Definer`,"
1797 . " `LAST_ALTERED` AS `Modified`,"
1798 . " `CREATED` AS `Created`,"
1799 . " `SECURITY_TYPE` AS `Security_type`,"
1800 . " `ROUTINE_COMMENT` AS `Comment`,"
1801 . " `CHARACTER_SET_CLIENT` AS `character_set_client`,"
1802 . " `COLLATION_CONNECTION` AS `collation_connection`,"
1803 . " `DATABASE_COLLATION` AS `Database Collation`,"
1804 . " `DTD_IDENTIFIER`"
1805 . " FROM `information_schema`.`ROUTINES`"
1806 . " WHERE `ROUTINE_SCHEMA` " . Util::getCollateForIS()
1807 . " = '" . $GLOBALS['dbi']->escapeString($db) . "'";
1808 if (PMA_isValid($which, array('FUNCTION','PROCEDURE'))) {
1809 $query .= " AND `ROUTINE_TYPE` = '" . $which . "'";
1811 if (! empty($name)) {
1812 $query .= " AND `SPECIFIC_NAME`"
1813 . " = '" . $GLOBALS['dbi']->escapeString($name) . "'";
1815 $result = $this->fetchResult($query);
1816 if (!empty($result)) {
1817 $routines = $result;
1819 } else {
1820 if ($which == 'FUNCTION' || $which == null) {
1821 $query = "SHOW FUNCTION STATUS"
1822 . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'";
1823 if (! empty($name)) {
1824 $query .= " AND `Name` = '"
1825 . $GLOBALS['dbi']->escapeString($name) . "'";
1827 $result = $this->fetchResult($query);
1828 if (!empty($result)) {
1829 $routines = array_merge($routines, $result);
1832 if ($which == 'PROCEDURE' || $which == null) {
1833 $query = "SHOW PROCEDURE STATUS"
1834 . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'";
1835 if (! empty($name)) {
1836 $query .= " AND `Name` = '"
1837 . $GLOBALS['dbi']->escapeString($name) . "'";
1839 $result = $this->fetchResult($query);
1840 if (!empty($result)) {
1841 $routines = array_merge($routines, $result);
1846 $ret = array();
1847 foreach ($routines as $routine) {
1848 $one_result = array();
1849 $one_result['db'] = $routine['Db'];
1850 $one_result['name'] = $routine['Name'];
1851 $one_result['type'] = $routine['Type'];
1852 $one_result['definer'] = $routine['Definer'];
1853 $one_result['returns'] = isset($routine['DTD_IDENTIFIER'])
1854 ? $routine['DTD_IDENTIFIER'] : "";
1855 $ret[] = $one_result;
1858 // Sort results by name
1859 $name = array();
1860 foreach ($ret as $value) {
1861 $name[] = $value['name'];
1863 array_multisort($name, SORT_ASC, $ret);
1865 return($ret);
1869 * returns details about the EVENTs for a specific database
1871 * @param string $db db name
1872 * @param string $name event name
1874 * @return array information about EVENTs
1876 public function getEvents($db, $name = '')
1878 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1879 $query = "SELECT"
1880 . " `EVENT_SCHEMA` AS `Db`,"
1881 . " `EVENT_NAME` AS `Name`,"
1882 . " `DEFINER` AS `Definer`,"
1883 . " `TIME_ZONE` AS `Time zone`,"
1884 . " `EVENT_TYPE` AS `Type`,"
1885 . " `EXECUTE_AT` AS `Execute at`,"
1886 . " `INTERVAL_VALUE` AS `Interval value`,"
1887 . " `INTERVAL_FIELD` AS `Interval field`,"
1888 . " `STARTS` AS `Starts`,"
1889 . " `ENDS` AS `Ends`,"
1890 . " `STATUS` AS `Status`,"
1891 . " `ORIGINATOR` AS `Originator`,"
1892 . " `CHARACTER_SET_CLIENT` AS `character_set_client`,"
1893 . " `COLLATION_CONNECTION` AS `collation_connection`, "
1894 . "`DATABASE_COLLATION` AS `Database Collation`"
1895 . " FROM `information_schema`.`EVENTS`"
1896 . " WHERE `EVENT_SCHEMA` " . Util::getCollateForIS()
1897 . " = '" . $GLOBALS['dbi']->escapeString($db) . "'";
1898 if (! empty($name)) {
1899 $query .= " AND `EVENT_NAME`"
1900 . " = '" . $GLOBALS['dbi']->escapeString($name) . "'";
1902 } else {
1903 $query = "SHOW EVENTS FROM " . Util::backquote($db);
1904 if (! empty($name)) {
1905 $query .= " AND `Name` = '"
1906 . $GLOBALS['dbi']->escapeString($name) . "'";
1910 $result = array();
1911 if ($events = $this->fetchResult($query)) {
1912 foreach ($events as $event) {
1913 $one_result = array();
1914 $one_result['name'] = $event['Name'];
1915 $one_result['type'] = $event['Type'];
1916 $one_result['status'] = $event['Status'];
1917 $result[] = $one_result;
1921 // Sort results by name
1922 $name = array();
1923 foreach ($result as $value) {
1924 $name[] = $value['name'];
1926 array_multisort($name, SORT_ASC, $result);
1928 return $result;
1932 * returns details about the TRIGGERs for a specific table or database
1934 * @param string $db db name
1935 * @param string $table table name
1936 * @param string $delimiter the delimiter to use (may be empty)
1938 * @return array information about triggers (may be empty)
1940 public function getTriggers($db, $table = '', $delimiter = '//')
1942 $result = array();
1943 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1944 $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION'
1945 . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT'
1946 . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER'
1947 . ' FROM information_schema.TRIGGERS'
1948 . ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '='
1949 . ' \'' . $GLOBALS['dbi']->escapeString($db) . '\'';
1951 if (! empty($table)) {
1952 $query .= " AND EVENT_OBJECT_TABLE " . Util::getCollateForIS()
1953 . " = '" . $GLOBALS['dbi']->escapeString($table) . "';";
1955 } else {
1956 $query = "SHOW TRIGGERS FROM " . Util::backquote($db);
1957 if (! empty($table)) {
1958 $query .= " LIKE '" . $GLOBALS['dbi']->escapeString($table) . "';";
1962 if ($triggers = $this->fetchResult($query)) {
1963 foreach ($triggers as $trigger) {
1964 if ($GLOBALS['cfg']['Server']['DisableIS']) {
1965 $trigger['TRIGGER_NAME'] = $trigger['Trigger'];
1966 $trigger['ACTION_TIMING'] = $trigger['Timing'];
1967 $trigger['EVENT_MANIPULATION'] = $trigger['Event'];
1968 $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table'];
1969 $trigger['ACTION_STATEMENT'] = $trigger['Statement'];
1970 $trigger['DEFINER'] = $trigger['Definer'];
1972 $one_result = array();
1973 $one_result['name'] = $trigger['TRIGGER_NAME'];
1974 $one_result['table'] = $trigger['EVENT_OBJECT_TABLE'];
1975 $one_result['action_timing'] = $trigger['ACTION_TIMING'];
1976 $one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION'];
1977 $one_result['definition'] = $trigger['ACTION_STATEMENT'];
1978 $one_result['definer'] = $trigger['DEFINER'];
1980 // do not prepend the schema name; this way, importing the
1981 // definition into another schema will work
1982 $one_result['full_trigger_name'] = Util::backquote(
1983 $trigger['TRIGGER_NAME']
1985 $one_result['drop'] = 'DROP TRIGGER IF EXISTS '
1986 . $one_result['full_trigger_name'];
1987 $one_result['create'] = 'CREATE TRIGGER '
1988 . $one_result['full_trigger_name'] . ' '
1989 . $trigger['ACTION_TIMING'] . ' '
1990 . $trigger['EVENT_MANIPULATION']
1991 . ' ON ' . Util::backquote($trigger['EVENT_OBJECT_TABLE'])
1992 . "\n" . ' FOR EACH ROW '
1993 . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n";
1995 $result[] = $one_result;
1999 // Sort results by name
2000 $name = array();
2001 foreach ($result as $value) {
2002 $name[] = $value['name'];
2004 array_multisort($name, SORT_ASC, $result);
2006 return($result);
2010 * Formats database error message in a friendly way.
2011 * This is needed because some errors messages cannot
2012 * be obtained by mysql_error().
2014 * @param int $error_number Error code
2015 * @param string $error_message Error message as returned by server
2017 * @return string HML text with error details
2019 public function formatError($error_number, $error_message)
2021 $error_message = htmlspecialchars($error_message);
2023 $error = '#' . ((string) $error_number);
2024 $separator = ' &mdash; ';
2026 if ($error_number == 2002) {
2027 $error .= ' - ' . $error_message;
2028 $error .= $separator;
2029 $error .= __(
2030 'The server is not responding (or the local server\'s socket'
2031 . ' is not correctly configured).'
2033 } elseif ($error_number == 2003) {
2034 $error .= ' - ' . $error_message;
2035 $error .= $separator . __('The server is not responding.');
2036 } elseif ($error_number == 1005) {
2037 if (strpos($error_message, 'errno: 13') !== false) {
2038 $error .= ' - ' . $error_message;
2039 $error .= $separator
2040 . __(
2041 'Please check privileges of directory containing database.'
2043 } else {
2044 /* InnoDB constraints, see
2045 * https://dev.mysql.com/doc/refman/5.0/en/
2046 * innodb-foreign-key-constraints.html
2048 $error .= ' - ' . $error_message .
2049 ' (<a href="server_engines.php' .
2050 URL::getCommon(
2051 array('engine' => 'InnoDB', 'page' => 'Status')
2052 ) . '">' . __('Details…') . '</a>)';
2054 } else {
2055 $error .= ' - ' . $error_message;
2058 return $error;
2062 * gets the current user with host
2064 * @return string the current user i.e. user@host
2066 public function getCurrentUser()
2068 if (Util::cacheExists('mysql_cur_user')) {
2069 return Util::cacheGet('mysql_cur_user');
2071 $user = $this->fetchValue('SELECT CURRENT_USER();');
2072 if ($user !== false) {
2073 Util::cacheSet('mysql_cur_user', $user);
2074 return $user;
2076 return '@';
2080 * Checks if current user is superuser
2082 * @return bool Whether user is a superuser
2084 public function isSuperuser()
2086 return self::isUserType('super');
2090 * Checks if current user has global create user/grant privilege
2091 * or is a superuser (i.e. SELECT on mysql.users)
2092 * while caching the result in session.
2094 * @param string $type type of user to check for
2095 * i.e. 'create', 'grant', 'super'
2097 * @return bool Whether user is a given type of user
2099 public function isUserType($type)
2101 if (Util::cacheExists('is_' . $type . 'user')) {
2102 return Util::cacheGet('is_' . $type . 'user');
2105 // when connection failed we don't have a $userlink
2106 if (! isset($GLOBALS['userlink'])) {
2107 Util::cacheSet('is_' . $type . 'user', false);
2108 return Util::cacheGet('is_' . $type . 'user');
2111 if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') {
2112 // Prepare query for each user type check
2113 $query = '';
2114 if ($type === 'super') {
2115 $query = 'SELECT 1 FROM mysql.user LIMIT 1';
2116 } elseif ($type === 'create') {
2117 list($user, $host) = $this->getCurrentUserAndHost();
2118 $query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` "
2119 . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND "
2120 . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
2121 } elseif ($type === 'grant') {
2122 list($user, $host) = $this->getCurrentUserAndHost();
2123 $query = "SELECT 1 FROM ("
2124 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2125 . "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION "
2126 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2127 . "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION "
2128 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2129 . "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION "
2130 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2131 . "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t "
2132 . "WHERE `IS_GRANTABLE` = 'YES' AND "
2133 . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
2136 $is = false;
2137 $result = $this->tryQuery(
2138 $query,
2139 $GLOBALS['userlink'],
2140 self::QUERY_STORE
2142 if ($result) {
2143 $is = (bool) $this->numRows($result);
2145 $this->freeResult($result);
2147 Util::cacheSet('is_' . $type . 'user', $is);
2148 } else {
2149 $is = false;
2150 $grants = $this->fetchResult(
2151 "SHOW GRANTS FOR CURRENT_USER();",
2152 null,
2153 null,
2154 $GLOBALS['userlink'],
2155 self::QUERY_STORE
2157 if ($grants) {
2158 foreach ($grants as $grant) {
2159 if ($type === 'create') {
2160 if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false
2161 || strpos($grant, "CREATE USER") !== false
2163 $is = true;
2164 break;
2166 } elseif ($type === 'grant') {
2167 if (strpos($grant, "WITH GRANT OPTION") !== false) {
2168 $is = true;
2169 break;
2175 Util::cacheSet('is_' . $type . 'user', $is);
2178 return Util::cacheGet('is_' . $type . 'user');
2182 * Get the current user and host
2184 * @return array array of username and hostname
2186 public function getCurrentUserAndHost()
2188 if (count($this->_current_user) == 0) {
2189 $user = $this->getCurrentUser();
2190 $this->_current_user = explode("@", $user);
2192 return $this->_current_user;
2196 * Returns value for lower_case_table_names variable
2198 * @return string
2200 public function getLowerCaseNames()
2202 if (is_null($this->_lower_case_table_names)) {
2203 $this->_lower_case_table_names = $this->fetchValue(
2204 "SELECT @@lower_case_table_names"
2207 return $this->_lower_case_table_names;
2211 * Get the list of system schemas
2213 * @return array list of system schemas
2215 public function getSystemSchemas()
2217 $schemas = array(
2218 'information_schema', 'performance_schema', 'mysql', 'sys'
2220 $systemSchemas = array();
2221 foreach ($schemas as $schema) {
2222 if ($this->isSystemSchema($schema, true)) {
2223 $systemSchemas[] = $schema;
2226 return $systemSchemas;
2230 * Checks whether given schema is a system schema
2232 * @param string $schema_name Name of schema (database) to test
2233 * @param bool $testForMysqlSchema Whether 'mysql' schema should
2234 * be treated the same as IS and DD
2236 * @return bool
2238 public function isSystemSchema($schema_name, $testForMysqlSchema = false)
2240 $schema_name = strtolower($schema_name);
2241 return $schema_name == 'information_schema'
2242 || $schema_name == 'performance_schema'
2243 || ($schema_name == 'mysql' && $testForMysqlSchema)
2244 || $schema_name == 'sys';
2248 * Return connection parameters for the database server
2250 * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL
2251 * or CONNECT_AUXILIARY.
2252 * @param array $server Server information like host/port/socket/persistent
2254 * @return array user, host and server settings array
2256 public function getConnectionParams($mode, $server = null)
2258 global $cfg;
2260 $user = null;
2261 $password = null;
2263 if ($mode == DatabaseInterface::CONNECT_USER) {
2264 $user = $cfg['Server']['user'];
2265 $password = $cfg['Server']['password'];
2266 $server = $cfg['Server'];
2267 } elseif ($mode == DatabaseInterface::CONNECT_CONTROL) {
2268 $user = $cfg['Server']['controluser'];
2269 $password = $cfg['Server']['controlpass'];
2271 $server = array();
2273 if (! empty($cfg['Server']['controlhost'])) {
2274 $server['host'] = $cfg['Server']['controlhost'];
2275 } else {
2276 $server['host'] = $cfg['Server']['host'];
2278 // Share the settings if the host is same
2279 if ($server['host'] == $cfg['Server']['host']) {
2280 $shared = array(
2281 'port', 'socket', 'compress',
2282 'ssl', 'ssl_key', 'ssl_cert', 'ssl_ca',
2283 'ssl_ca_path', 'ssl_ciphers', 'ssl_verify',
2285 foreach ($shared as $item) {
2286 if (isset($cfg['Server'][$item])) {
2287 $server[$item] = $cfg['Server'][$item];
2291 // Set configured port
2292 if (! empty($cfg['Server']['controlport'])) {
2293 $server['port'] = $cfg['Server']['controlport'];
2295 // Set any configuration with control_ prefix
2296 foreach ($cfg['Server'] as $key => $val) {
2297 if (substr($key, 0, 8) === 'control_') {
2298 $server[substr($key, 8)] = $val;
2301 } else {
2302 if (is_null($server)) {
2303 return array(null, null, null);
2305 if (isset($server['user'])) {
2306 $user = $server['user'];
2308 if (isset($server['password'])) {
2309 $password = $server['password'];
2313 // Perform sanity checks on some variables
2314 if (empty($server['port'])) {
2315 $server['port'] = 0;
2316 } else {
2317 $server['port'] = intval($server['port']);
2319 if (empty($server['socket'])) {
2320 $server['socket'] = null;
2322 if (empty($server['host'])) {
2323 $server['host'] = 'localhost';
2325 if (!isset($server['ssl'])) {
2326 $server['ssl'] = false;
2328 if (!isset($server['compress'])) {
2329 $server['compress'] = false;
2332 return array($user, $password, $server);
2336 * connects to the database server
2338 * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL
2339 * or CONNECT_AUXILIARY.
2340 * @param array $server Server information like host/port/socket/persistent
2342 * @return mixed false on error or a connection object on success
2344 public function connect($mode, $server = null)
2346 list($user, $password, $server) = $this->getConnectionParams($mode, $server);
2348 if (is_null($user) || is_null($password)) {
2349 if ($mode == DatabaseInterface::CONNECT_USER) {
2350 Logging::logUser($user, 'mysql-denied');
2351 $GLOBALS['auth_plugin']->authFails();
2353 trigger_error(
2354 __('Missing connection parameters!'),
2355 E_USER_WARNING
2357 return false;
2360 // Do not show location and backtrace for connection errors
2361 $GLOBALS['error_handler']->setHideLocation(true);
2362 $result = $this->_extension->connect(
2363 $user, $password, $server
2365 $GLOBALS['error_handler']->setHideLocation(false);
2367 if ($result) {
2368 /* Run post connect for user connections */
2369 if ($mode == DatabaseInterface::CONNECT_USER) {
2370 $this->postConnect($result);
2372 return $result;
2375 if ($mode == DatabaseInterface::CONNECT_CONTROL) {
2376 trigger_error(
2378 'Connection for controluser as defined in your '
2379 . 'configuration failed.'
2381 E_USER_WARNING
2383 return false;
2384 } else if ($mode == DatabaseInterface::CONNECT_AUXILIARY) {
2385 // Do not go back to main login if connection failed
2386 // (currently used only in unit testing)
2387 return false;
2390 Logging::logUser($user, 'mysql-denied');
2391 $GLOBALS['auth_plugin']->authFails();
2393 return $result;
2397 * selects given database
2399 * @param string $dbname database name to select
2400 * @param object $link connection object
2402 * @return boolean
2404 public function selectDb($dbname, $link = null)
2406 $link = $this->getLink($link);
2407 if ($link === false) {
2408 return false;
2410 return $this->_extension->selectDb($dbname, $link);
2414 * returns array of rows with associative and numeric keys from $result
2416 * @param object $result result set identifier
2418 * @return array
2420 public function fetchArray($result)
2422 return $this->_extension->fetchArray($result);
2426 * returns array of rows with associative keys from $result
2428 * @param object $result result set identifier
2430 * @return array
2432 public function fetchAssoc($result)
2434 return $this->_extension->fetchAssoc($result);
2438 * returns array of rows with numeric keys from $result
2440 * @param object $result result set identifier
2442 * @return array
2444 public function fetchRow($result)
2446 return $this->_extension->fetchRow($result);
2450 * Adjusts the result pointer to an arbitrary row in the result
2452 * @param object $result database result
2453 * @param integer $offset offset to seek
2455 * @return bool true on success, false on failure
2457 public function dataSeek($result, $offset)
2459 return $this->_extension->dataSeek($result, $offset);
2463 * Frees memory associated with the result
2465 * @param object $result database result
2467 * @return void
2469 public function freeResult($result)
2471 $this->_extension->freeResult($result);
2475 * Check if there are any more query results from a multi query
2477 * @param object $link the connection object
2479 * @return bool true or false
2481 public function moreResults($link = null)
2483 $link = $this->getLink($link);
2484 if ($link === false) {
2485 return false;
2487 return $this->_extension->moreResults($link);
2491 * Prepare next result from multi_query
2493 * @param object $link the connection object
2495 * @return bool true or false
2497 public function nextResult($link = null)
2499 $link = $this->getLink($link);
2500 if ($link === false) {
2501 return false;
2503 return $this->_extension->nextResult($link);
2507 * Store the result returned from multi query
2509 * @param object $link the connection object
2511 * @return mixed false when empty results / result set when not empty
2513 public function storeResult($link = null)
2515 $link = $this->getLink($link);
2516 if ($link === false) {
2517 return false;
2519 return $this->_extension->storeResult($link);
2523 * Returns a string representing the type of connection used
2525 * @param object $link mysql link
2527 * @return string type of connection used
2529 public function getHostInfo($link = null)
2531 $link = $this->getLink($link);
2532 if ($link === false) {
2533 return false;
2535 return $this->_extension->getHostInfo($link);
2539 * Returns the version of the MySQL protocol used
2541 * @param object $link mysql link
2543 * @return integer version of the MySQL protocol used
2545 public function getProtoInfo($link = null)
2547 $link = $this->getLink($link);
2548 if ($link === false) {
2549 return false;
2551 return $this->_extension->getProtoInfo($link);
2555 * returns a string that represents the client library version
2557 * @return string MySQL client library version
2559 public function getClientInfo()
2561 return $this->_extension->getClientInfo();
2565 * returns last error message or false if no errors occurred
2567 * @param object $link connection link
2569 * @return string|bool $error or false
2571 public function getError($link = null)
2573 $link = $this->getLink($link);
2574 return $this->_extension->getError($link);
2578 * returns the number of rows returned by last query
2580 * @param object $result result set identifier
2582 * @return string|int
2584 public function numRows($result)
2586 return $this->_extension->numRows($result);
2590 * returns last inserted auto_increment id for given $link
2591 * or $GLOBALS['userlink']
2593 * @param object $link the connection object
2595 * @return int|boolean
2597 public function insertId($link = null)
2599 $link = $this->getLink($link);
2600 if ($link === false) {
2601 return false;
2603 // If the primary key is BIGINT we get an incorrect result
2604 // (sometimes negative, sometimes positive)
2605 // and in the present function we don't know if the PK is BIGINT
2606 // so better play safe and use LAST_INSERT_ID()
2608 // When no controluser is defined, using mysqli_insert_id($link)
2609 // does not always return the last insert id due to a mixup with
2610 // the tracking mechanism, but this works:
2611 return $this->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link);
2615 * returns the number of rows affected by last query
2617 * @param object $link the connection object
2618 * @param bool $get_from_cache whether to retrieve from cache
2620 * @return int|boolean
2622 public function affectedRows($link = null, $get_from_cache = true)
2624 $link = $this->getLink($link);
2625 if ($link === false) {
2626 return false;
2629 if ($get_from_cache) {
2630 return $GLOBALS['cached_affected_rows'];
2631 } else {
2632 return $this->_extension->affectedRows($link);
2637 * returns metainfo for fields in $result
2639 * @param object $result result set identifier
2641 * @return array meta info for fields in $result
2643 public function getFieldsMeta($result)
2645 $result = $this->_extension->getFieldsMeta($result);
2647 if ($this->getLowerCaseNames() === '2') {
2649 * Fixup orgtable for lower_case_table_names = 2
2651 * In this setup MySQL server reports table name lower case
2652 * but we still need to operate on original case to properly
2653 * match existing strings
2655 foreach ($result as $value) {
2656 if (strlen($value->orgtable) !== 0 &&
2657 mb_strtolower($value->orgtable) === mb_strtolower($value->table)) {
2658 $value->orgtable = $value->table;
2663 return $result;
2667 * return number of fields in given $result
2669 * @param object $result result set identifier
2671 * @return int field count
2673 public function numFields($result)
2675 return $this->_extension->numFields($result);
2679 * returns the length of the given field $i in $result
2681 * @param object $result result set identifier
2682 * @param int $i field
2684 * @return int length of field
2686 public function fieldLen($result, $i)
2688 return $this->_extension->fieldLen($result, $i);
2692 * returns name of $i. field in $result
2694 * @param object $result result set identifier
2695 * @param int $i field
2697 * @return string name of $i. field in $result
2699 public function fieldName($result, $i)
2701 return $this->_extension->fieldName($result, $i);
2705 * returns concatenated string of human readable field flags
2707 * @param object $result result set identifier
2708 * @param int $i field
2710 * @return string field flags
2712 public function fieldFlags($result, $i)
2714 return $this->_extension->fieldFlags($result, $i);
2718 * returns properly escaped string for use in MySQL queries
2720 * @param string $str string to be escaped
2721 * @param mixed $link optional database link to use
2723 * @return string a MySQL escaped string
2725 public function escapeString($str, $link = null)
2727 if ($link === null) {
2728 $link = $this->getLink();
2731 if ($this->_extension === null) {
2732 return $str;
2735 return $this->_extension->escapeString($link, $str);
2739 * Gets correct link object.
2741 * @param object $link optional database link to use
2743 * @return object|boolean
2745 public function getLink($link = null)
2747 if (! is_null($link) && $link !== false) {
2748 return $link;
2751 if (isset($GLOBALS['userlink']) && !is_null($GLOBALS['userlink'])) {
2752 return $GLOBALS['userlink'];
2753 } else {
2754 return false;
2759 * Checks if this database server is running on Amazon RDS.
2761 * @return boolean
2763 public function isAmazonRds()
2765 if (Util::cacheExists('is_amazon_rds')) {
2766 return Util::cacheGet('is_amazon_rds');
2768 $sql = 'SELECT @@basedir';
2769 $result = $this->fetchValue($sql);
2770 $rds = (substr($result, 0, 10) == '/rdsdbbin/');
2771 Util::cacheSet('is_amazon_rds', $rds);
2773 return $rds;
2777 * Gets SQL for killing a process.
2779 * @param int $process Process ID
2781 * @return string
2783 public function getKillQuery($process)
2785 if ($this->isAmazonRds()) {
2786 return 'CALL mysql.rds_kill(' . $process . ');';
2787 } else {
2788 return 'KILL ' . $process . ';';
2793 * Get the phpmyadmin database manager
2795 * @return SystemDatabase
2797 public function getSystemDatabase()
2799 return new SystemDatabase($this);
2803 * Get a table with database name and table name
2805 * @param string $db_name DB name
2806 * @param string $table_name Table name
2808 * @return Table
2810 public function getTable($db_name, $table_name)
2812 return new Table($table_name, $db_name, $this);
2816 * returns collation of given db
2818 * @param string $db name of db
2820 * @return string collation of $db
2822 public function getDbCollation($db)
2824 if ($this->isSystemSchema($db)) {
2825 // We don't have to check the collation of the virtual
2826 // information_schema database: We know it!
2827 return 'utf8_general_ci';
2830 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
2831 // this is slow with thousands of databases
2832 $sql = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA'
2833 . ' WHERE SCHEMA_NAME = \'' . $this->escapeString($db)
2834 . '\' LIMIT 1';
2835 return $this->fetchValue($sql);
2836 } else {
2837 $this->selectDb($db);
2838 $return = $this->fetchValue('SELECT @@collation_database');
2839 if ($db !== $GLOBALS['db']) {
2840 $this->selectDb($GLOBALS['db']);
2842 return $return;
2847 * returns default server collation from show variables
2849 * @return string $server_collation
2851 function getServerCollation()
2853 return $this->fetchValue('SELECT @@collation_server');