Translated using Weblate (Dutch)
[phpmyadmin.git] / libraries / DatabaseInterface.php
blob9e9bc03aac381f62ec320b0ada128d91958c00e9
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\Core;
11 use PMA\libraries\dbi\DBIExtension;
12 use PMA\libraries\LanguageManager;
13 use PMA\libraries\URL;
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 natsort($tables);
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 * Convert version string to integer.
1346 * @param string $version MySQL server version
1348 * @return int
1350 public static function versionToInt($version)
1352 $match = explode('.', $version);
1353 return (int) sprintf('%d%02d%02d', $match[0], $match[1], intval($match[2]));
1357 * Function called just after a connection to the MySQL database server has
1358 * been established. It sets the connection collation, and determines the
1359 * version of MySQL which is running.
1361 * @param mixed $link mysql link resource|object
1363 * @return void
1365 public function postConnect($link)
1367 if (! defined('PMA_MYSQL_INT_VERSION')) {
1368 $version = $this->fetchSingleRow(
1369 'SELECT @@version, @@version_comment',
1370 'ASSOC',
1371 $link
1374 if ($version) {
1375 $ver_int = self::versionToInt($version['@@version']);
1376 define('PMA_MYSQL_MAJOR_VERSION', (int)($ver_int / 10000));
1377 define('PMA_MYSQL_INT_VERSION', $ver_int);
1378 define('PMA_MYSQL_STR_VERSION', $version['@@version']);
1379 define('PMA_MYSQL_VERSION_COMMENT', $version['@@version_comment']);
1380 } else {
1381 define('PMA_MYSQL_INT_VERSION', 50501);
1382 define('PMA_MYSQL_MAJOR_VERSION', 5);
1383 define('PMA_MYSQL_STR_VERSION', '5.05.01');
1384 define('PMA_MYSQL_VERSION_COMMENT', '');
1386 /* Detect MariaDB */
1387 if (mb_strpos(PMA_MYSQL_STR_VERSION, 'MariaDB') !== false) {
1388 define('PMA_MARIADB', true);
1389 } else {
1390 define('PMA_MARIADB', false);
1394 if (PMA_MYSQL_INT_VERSION > 50503) {
1395 $default_charset = 'utf8mb4';
1396 $default_collation = 'utf8mb4_general_ci';
1397 } else {
1398 $default_charset = 'utf8';
1399 $default_collation = 'utf8_general_ci';
1401 $collation_connection = $GLOBALS['PMA_Config']->get('collation_connection');
1402 if (! empty($collation_connection)) {
1403 $this->query(
1404 "SET CHARACTER SET '$default_charset';",
1405 $link,
1406 self::QUERY_STORE
1408 /* Automatically adjust collation if not supported by server */
1409 if ($default_charset == 'utf8'
1410 && strncmp('utf8mb4_', $collation_connection, 8) == 0
1412 $collation_connection = 'utf8_' . substr($collation_connection, 8);
1414 $result = $this->tryQuery(
1415 "SET collation_connection = '"
1416 . $this->escapeString($collation_connection, $link)
1417 . "';",
1418 $link,
1419 self::QUERY_STORE
1421 if ($result === false) {
1422 trigger_error(
1423 __('Failed to set configured collation connection!'),
1424 E_USER_WARNING
1426 $this->query(
1427 "SET collation_connection = '"
1428 . $this->escapeString($collation_connection, $link)
1429 . "';",
1430 $link,
1431 self::QUERY_STORE
1434 } else {
1435 $this->query(
1436 "SET NAMES '$default_charset' COLLATE '$default_collation';",
1437 $link,
1438 self::QUERY_STORE
1442 /* Locale for messages */
1443 $locale = LanguageManager::getInstance()->getCurrentLanguage()->getMySQLLocale();
1444 if (! empty($locale)) {
1445 $this->query(
1446 "SET lc_messages = '" . $locale . "';",
1447 $link,
1448 self::QUERY_STORE
1454 * returns a single value from the given result or query,
1455 * if the query or the result has more than one row or field
1456 * the first field of the first row is returned
1458 * <code>
1459 * $sql = 'SELECT `name` FROM `user` WHERE `id` = 123';
1460 * $user_name = $GLOBALS['dbi']->fetchValue($sql);
1461 * // produces
1462 * // $user_name = 'John Doe'
1463 * </code>
1465 * @param string $query The query to execute
1466 * @param integer $row_number row to fetch the value from,
1467 * starting at 0, with 0 being default
1468 * @param integer|string $field field to fetch the value from,
1469 * starting at 0, with 0 being default
1470 * @param object $link mysql link
1472 * @return mixed value of first field in first row from result
1473 * or false if not found
1475 public function fetchValue($query, $row_number = 0, $field = 0, $link = null)
1477 $value = false;
1479 $result = $this->tryQuery(
1480 $query,
1481 $link,
1482 self::QUERY_STORE,
1483 false
1485 if ($result === false) {
1486 return false;
1489 // return false if result is empty or false
1490 // or requested row is larger than rows in result
1491 if ($this->numRows($result) < ($row_number + 1)) {
1492 return $value;
1495 // if $field is an integer use non associative mysql fetch function
1496 if (is_int($field)) {
1497 $fetch_function = 'fetchRow';
1498 } else {
1499 $fetch_function = 'fetchAssoc';
1502 // get requested row
1503 for ($i = 0; $i <= $row_number; $i++) {
1504 $row = $this->$fetch_function($result);
1506 $this->freeResult($result);
1508 // return requested field
1509 if (isset($row[$field])) {
1510 $value = $row[$field];
1513 return $value;
1517 * returns only the first row from the result
1519 * <code>
1520 * $sql = 'SELECT * FROM `user` WHERE `id` = 123';
1521 * $user = $GLOBALS['dbi']->fetchSingleRow($sql);
1522 * // produces
1523 * // $user = array('id' => 123, 'name' => 'John Doe')
1524 * </code>
1526 * @param string $query The query to execute
1527 * @param string $type NUM|ASSOC|BOTH returned array should either
1528 * numeric associative or both
1529 * @param object $link mysql link
1531 * @return array|boolean first row from result
1532 * or false if result is empty
1534 public function fetchSingleRow($query, $type = 'ASSOC', $link = null)
1536 $result = $this->tryQuery(
1537 $query,
1538 $link,
1539 self::QUERY_STORE,
1540 false
1542 if ($result === false) {
1543 return false;
1546 // return false if result is empty or false
1547 if (! $this->numRows($result)) {
1548 return false;
1551 switch ($type) {
1552 case 'NUM' :
1553 $fetch_function = 'fetchRow';
1554 break;
1555 case 'ASSOC' :
1556 $fetch_function = 'fetchAssoc';
1557 break;
1558 case 'BOTH' :
1559 default :
1560 $fetch_function = 'fetchArray';
1561 break;
1564 $row = $this->$fetch_function($result);
1565 $this->freeResult($result);
1566 return $row;
1570 * Returns row or element of a row
1572 * @param array $row Row to process
1573 * @param string|null $value Which column to return
1575 * @return mixed
1577 private function _fetchValue($row, $value)
1579 if (is_null($value)) {
1580 return $row;
1581 } else {
1582 return $row[$value];
1587 * returns all rows in the resultset in one array
1589 * <code>
1590 * $sql = 'SELECT * FROM `user`';
1591 * $users = $GLOBALS['dbi']->fetchResult($sql);
1592 * // produces
1593 * // $users[] = array('id' => 123, 'name' => 'John Doe')
1595 * $sql = 'SELECT `id`, `name` FROM `user`';
1596 * $users = $GLOBALS['dbi']->fetchResult($sql, 'id');
1597 * // produces
1598 * // $users['123'] = array('id' => 123, 'name' => 'John Doe')
1600 * $sql = 'SELECT `id`, `name` FROM `user`';
1601 * $users = $GLOBALS['dbi']->fetchResult($sql, 0);
1602 * // produces
1603 * // $users['123'] = array(0 => 123, 1 => 'John Doe')
1605 * $sql = 'SELECT `id`, `name` FROM `user`';
1606 * $users = $GLOBALS['dbi']->fetchResult($sql, 'id', 'name');
1607 * // or
1608 * $users = $GLOBALS['dbi']->fetchResult($sql, 0, 1);
1609 * // produces
1610 * // $users['123'] = 'John Doe'
1612 * $sql = 'SELECT `name` FROM `user`';
1613 * $users = $GLOBALS['dbi']->fetchResult($sql);
1614 * // produces
1615 * // $users[] = 'John Doe'
1617 * $sql = 'SELECT `group`, `name` FROM `user`'
1618 * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', null), 'name');
1619 * // produces
1620 * // $users['admin'][] = 'John Doe'
1622 * $sql = 'SELECT `group`, `name` FROM `user`'
1623 * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', 'name'), 'id');
1624 * // produces
1625 * // $users['admin']['John Doe'] = '123'
1626 * </code>
1628 * @param string $query query to execute
1629 * @param string|integer|array $key field-name or offset
1630 * used as key for array
1631 * or array of those
1632 * @param string|integer $value value-name or offset
1633 * used as value for array
1634 * @param object $link mysql link
1635 * @param integer $options query options
1637 * @return array resultrows or values indexed by $key
1639 public function fetchResult($query, $key = null, $value = null,
1640 $link = null, $options = 0
1642 $resultrows = array();
1644 $result = $this->tryQuery($query, $link, $options, false);
1646 // return empty array if result is empty or false
1647 if ($result === false) {
1648 return $resultrows;
1651 $fetch_function = 'fetchAssoc';
1653 // no nested array if only one field is in result
1654 if (null === $key && 1 === $this->numFields($result)) {
1655 $value = 0;
1656 $fetch_function = 'fetchRow';
1659 // if $key is an integer use non associative mysql fetch function
1660 if (is_int($key)) {
1661 $fetch_function = 'fetchRow';
1664 if (null === $key) {
1665 while ($row = $this->$fetch_function($result)) {
1666 $resultrows[] = $this->_fetchValue($row, $value);
1668 } else {
1669 if (is_array($key)) {
1670 while ($row = $this->$fetch_function($result)) {
1671 $result_target =& $resultrows;
1672 foreach ($key as $key_index) {
1673 if (null === $key_index) {
1674 $result_target =& $result_target[];
1675 continue;
1678 if (! isset($result_target[$row[$key_index]])) {
1679 $result_target[$row[$key_index]] = array();
1681 $result_target =& $result_target[$row[$key_index]];
1683 $result_target = $this->_fetchValue($row, $value);
1685 } else {
1686 while ($row = $this->$fetch_function($result)) {
1687 $resultrows[$row[$key]] = $this->_fetchValue($row, $value);
1692 $this->freeResult($result);
1693 return $resultrows;
1697 * Get supported SQL compatibility modes
1699 * @return array supported SQL compatibility modes
1701 public function getCompatibilities()
1703 $compats = array('NONE');
1704 $compats[] = 'ANSI';
1705 $compats[] = 'DB2';
1706 $compats[] = 'MAXDB';
1707 $compats[] = 'MYSQL323';
1708 $compats[] = 'MYSQL40';
1709 $compats[] = 'MSSQL';
1710 $compats[] = 'ORACLE';
1711 // removed; in MySQL 5.0.33, this produces exports that
1712 // can't be read by POSTGRESQL (see our bug #1596328)
1713 //$compats[] = 'POSTGRESQL';
1714 $compats[] = 'TRADITIONAL';
1716 return $compats;
1720 * returns warnings for last query
1722 * @param object $link mysql link resource
1724 * @return array warnings
1726 public function getWarnings($link = null)
1728 $link = $this->getLink($link);
1729 if ($link === false) {
1730 return false;
1733 return $this->fetchResult('SHOW WARNINGS', null, null, $link);
1737 * returns an array of PROCEDURE or FUNCTION names for a db
1739 * @param string $db db name
1740 * @param string $which PROCEDURE | FUNCTION
1741 * @param object $link mysql link
1743 * @return array the procedure names or function names
1745 public function getProceduresOrFunctions($db, $which, $link = null)
1747 $shows = $this->fetchResult(
1748 'SHOW ' . $which . ' STATUS;', null, null, $link
1750 $result = array();
1751 foreach ($shows as $one_show) {
1752 if ($one_show['Db'] == $db && $one_show['Type'] == $which) {
1753 $result[] = $one_show['Name'];
1756 return($result);
1760 * returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW
1762 * @param string $db db name
1763 * @param string $which PROCEDURE | FUNCTION | EVENT | VIEW
1764 * @param string $name the procedure|function|event|view name
1765 * @param object $link MySQL link
1767 * @return string the definition
1769 public function getDefinition($db, $which, $name, $link = null)
1771 $returned_field = array(
1772 'PROCEDURE' => 'Create Procedure',
1773 'FUNCTION' => 'Create Function',
1774 'EVENT' => 'Create Event',
1775 'VIEW' => 'Create View'
1777 $query = 'SHOW CREATE ' . $which . ' '
1778 . Util::backquote($db) . '.'
1779 . Util::backquote($name);
1780 return($this->fetchValue($query, 0, $returned_field[$which], $link));
1784 * returns details about the PROCEDUREs or FUNCTIONs for a specific database
1785 * or details about a specific routine
1787 * @param string $db db name
1788 * @param string $which PROCEDURE | FUNCTION or null for both
1789 * @param string $name name of the routine (to fetch a specific routine)
1791 * @return array information about ROCEDUREs or FUNCTIONs
1793 public function getRoutines($db, $which = null, $name = '')
1795 $routines = array();
1796 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1797 $query = "SELECT"
1798 . " `ROUTINE_SCHEMA` AS `Db`,"
1799 . " `SPECIFIC_NAME` AS `Name`,"
1800 . " `ROUTINE_TYPE` AS `Type`,"
1801 . " `DEFINER` AS `Definer`,"
1802 . " `LAST_ALTERED` AS `Modified`,"
1803 . " `CREATED` AS `Created`,"
1804 . " `SECURITY_TYPE` AS `Security_type`,"
1805 . " `ROUTINE_COMMENT` AS `Comment`,"
1806 . " `CHARACTER_SET_CLIENT` AS `character_set_client`,"
1807 . " `COLLATION_CONNECTION` AS `collation_connection`,"
1808 . " `DATABASE_COLLATION` AS `Database Collation`,"
1809 . " `DTD_IDENTIFIER`"
1810 . " FROM `information_schema`.`ROUTINES`"
1811 . " WHERE `ROUTINE_SCHEMA` " . Util::getCollateForIS()
1812 . " = '" . $GLOBALS['dbi']->escapeString($db) . "'";
1813 if (Core::isValid($which, array('FUNCTION','PROCEDURE'))) {
1814 $query .= " AND `ROUTINE_TYPE` = '" . $which . "'";
1816 if (! empty($name)) {
1817 $query .= " AND `SPECIFIC_NAME`"
1818 . " = '" . $GLOBALS['dbi']->escapeString($name) . "'";
1820 $result = $this->fetchResult($query);
1821 if (!empty($result)) {
1822 $routines = $result;
1824 } else {
1825 if ($which == 'FUNCTION' || $which == null) {
1826 $query = "SHOW FUNCTION STATUS"
1827 . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'";
1828 if (! empty($name)) {
1829 $query .= " AND `Name` = '"
1830 . $GLOBALS['dbi']->escapeString($name) . "'";
1832 $result = $this->fetchResult($query);
1833 if (!empty($result)) {
1834 $routines = array_merge($routines, $result);
1837 if ($which == 'PROCEDURE' || $which == null) {
1838 $query = "SHOW PROCEDURE STATUS"
1839 . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'";
1840 if (! empty($name)) {
1841 $query .= " AND `Name` = '"
1842 . $GLOBALS['dbi']->escapeString($name) . "'";
1844 $result = $this->fetchResult($query);
1845 if (!empty($result)) {
1846 $routines = array_merge($routines, $result);
1851 $ret = array();
1852 foreach ($routines as $routine) {
1853 $one_result = array();
1854 $one_result['db'] = $routine['Db'];
1855 $one_result['name'] = $routine['Name'];
1856 $one_result['type'] = $routine['Type'];
1857 $one_result['definer'] = $routine['Definer'];
1858 $one_result['returns'] = isset($routine['DTD_IDENTIFIER'])
1859 ? $routine['DTD_IDENTIFIER'] : "";
1860 $ret[] = $one_result;
1863 // Sort results by name
1864 $name = array();
1865 foreach ($ret as $value) {
1866 $name[] = $value['name'];
1868 array_multisort($name, SORT_ASC, $ret);
1870 return($ret);
1874 * returns details about the EVENTs for a specific database
1876 * @param string $db db name
1877 * @param string $name event name
1879 * @return array information about EVENTs
1881 public function getEvents($db, $name = '')
1883 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1884 $query = "SELECT"
1885 . " `EVENT_SCHEMA` AS `Db`,"
1886 . " `EVENT_NAME` AS `Name`,"
1887 . " `DEFINER` AS `Definer`,"
1888 . " `TIME_ZONE` AS `Time zone`,"
1889 . " `EVENT_TYPE` AS `Type`,"
1890 . " `EXECUTE_AT` AS `Execute at`,"
1891 . " `INTERVAL_VALUE` AS `Interval value`,"
1892 . " `INTERVAL_FIELD` AS `Interval field`,"
1893 . " `STARTS` AS `Starts`,"
1894 . " `ENDS` AS `Ends`,"
1895 . " `STATUS` AS `Status`,"
1896 . " `ORIGINATOR` AS `Originator`,"
1897 . " `CHARACTER_SET_CLIENT` AS `character_set_client`,"
1898 . " `COLLATION_CONNECTION` AS `collation_connection`, "
1899 . "`DATABASE_COLLATION` AS `Database Collation`"
1900 . " FROM `information_schema`.`EVENTS`"
1901 . " WHERE `EVENT_SCHEMA` " . Util::getCollateForIS()
1902 . " = '" . $GLOBALS['dbi']->escapeString($db) . "'";
1903 if (! empty($name)) {
1904 $query .= " AND `EVENT_NAME`"
1905 . " = '" . $GLOBALS['dbi']->escapeString($name) . "'";
1907 } else {
1908 $query = "SHOW EVENTS FROM " . Util::backquote($db);
1909 if (! empty($name)) {
1910 $query .= " AND `Name` = '"
1911 . $GLOBALS['dbi']->escapeString($name) . "'";
1915 $result = array();
1916 if ($events = $this->fetchResult($query)) {
1917 foreach ($events as $event) {
1918 $one_result = array();
1919 $one_result['name'] = $event['Name'];
1920 $one_result['type'] = $event['Type'];
1921 $one_result['status'] = $event['Status'];
1922 $result[] = $one_result;
1926 // Sort results by name
1927 $name = array();
1928 foreach ($result as $value) {
1929 $name[] = $value['name'];
1931 array_multisort($name, SORT_ASC, $result);
1933 return $result;
1937 * returns details about the TRIGGERs for a specific table or database
1939 * @param string $db db name
1940 * @param string $table table name
1941 * @param string $delimiter the delimiter to use (may be empty)
1943 * @return array information about triggers (may be empty)
1945 public function getTriggers($db, $table = '', $delimiter = '//')
1947 $result = array();
1948 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1949 $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION'
1950 . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT'
1951 . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER'
1952 . ' FROM information_schema.TRIGGERS'
1953 . ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '='
1954 . ' \'' . $GLOBALS['dbi']->escapeString($db) . '\'';
1956 if (! empty($table)) {
1957 $query .= " AND EVENT_OBJECT_TABLE " . Util::getCollateForIS()
1958 . " = '" . $GLOBALS['dbi']->escapeString($table) . "';";
1960 } else {
1961 $query = "SHOW TRIGGERS FROM " . Util::backquote($db);
1962 if (! empty($table)) {
1963 $query .= " LIKE '" . $GLOBALS['dbi']->escapeString($table) . "';";
1967 if ($triggers = $this->fetchResult($query)) {
1968 foreach ($triggers as $trigger) {
1969 if ($GLOBALS['cfg']['Server']['DisableIS']) {
1970 $trigger['TRIGGER_NAME'] = $trigger['Trigger'];
1971 $trigger['ACTION_TIMING'] = $trigger['Timing'];
1972 $trigger['EVENT_MANIPULATION'] = $trigger['Event'];
1973 $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table'];
1974 $trigger['ACTION_STATEMENT'] = $trigger['Statement'];
1975 $trigger['DEFINER'] = $trigger['Definer'];
1977 $one_result = array();
1978 $one_result['name'] = $trigger['TRIGGER_NAME'];
1979 $one_result['table'] = $trigger['EVENT_OBJECT_TABLE'];
1980 $one_result['action_timing'] = $trigger['ACTION_TIMING'];
1981 $one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION'];
1982 $one_result['definition'] = $trigger['ACTION_STATEMENT'];
1983 $one_result['definer'] = $trigger['DEFINER'];
1985 // do not prepend the schema name; this way, importing the
1986 // definition into another schema will work
1987 $one_result['full_trigger_name'] = Util::backquote(
1988 $trigger['TRIGGER_NAME']
1990 $one_result['drop'] = 'DROP TRIGGER IF EXISTS '
1991 . $one_result['full_trigger_name'];
1992 $one_result['create'] = 'CREATE TRIGGER '
1993 . $one_result['full_trigger_name'] . ' '
1994 . $trigger['ACTION_TIMING'] . ' '
1995 . $trigger['EVENT_MANIPULATION']
1996 . ' ON ' . Util::backquote($trigger['EVENT_OBJECT_TABLE'])
1997 . "\n" . ' FOR EACH ROW '
1998 . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n";
2000 $result[] = $one_result;
2004 // Sort results by name
2005 $name = array();
2006 foreach ($result as $value) {
2007 $name[] = $value['name'];
2009 array_multisort($name, SORT_ASC, $result);
2011 return($result);
2015 * Formats database error message in a friendly way.
2016 * This is needed because some errors messages cannot
2017 * be obtained by mysql_error().
2019 * @param int $error_number Error code
2020 * @param string $error_message Error message as returned by server
2022 * @return string HML text with error details
2024 public static function formatError($error_number, $error_message)
2026 $error_message = htmlspecialchars($error_message);
2028 $error = '#' . ((string) $error_number);
2029 $separator = ' &mdash; ';
2031 if ($error_number == 2002) {
2032 $error .= ' - ' . $error_message;
2033 $error .= $separator;
2034 $error .= __(
2035 'The server is not responding (or the local server\'s socket'
2036 . ' is not correctly configured).'
2038 } elseif ($error_number == 2003) {
2039 $error .= ' - ' . $error_message;
2040 $error .= $separator . __('The server is not responding.');
2041 } elseif ($error_number == 1698 ) {
2042 $error .= ' - ' . $error_message;
2043 $error .= $separator . '<a href="logout.php' . URL::getCommon() . '">';
2044 $error .= __('Logout and try as another user.') . '</a>';
2045 } elseif ($error_number == 1005) {
2046 if (strpos($error_message, 'errno: 13') !== false) {
2047 $error .= ' - ' . $error_message;
2048 $error .= $separator
2049 . __(
2050 'Please check privileges of directory containing database.'
2052 } else {
2053 /* InnoDB constraints, see
2054 * https://dev.mysql.com/doc/refman/5.0/en/
2055 * innodb-foreign-key-constraints.html
2057 $error .= ' - ' . $error_message .
2058 ' (<a href="server_engines.php' .
2059 URL::getCommon(
2060 array('engine' => 'InnoDB', 'page' => 'Status')
2061 ) . '">' . __('Details…') . '</a>)';
2063 } else {
2064 $error .= ' - ' . $error_message;
2067 return $error;
2071 * gets the current user with host
2073 * @return string the current user i.e. user@host
2075 public function getCurrentUser()
2077 if (Util::cacheExists('mysql_cur_user')) {
2078 return Util::cacheGet('mysql_cur_user');
2080 $user = $this->fetchValue('SELECT CURRENT_USER();');
2081 if ($user !== false) {
2082 Util::cacheSet('mysql_cur_user', $user);
2083 return $user;
2085 return '@';
2089 * Checks if current user is superuser
2091 * @return bool Whether user is a superuser
2093 public function isSuperuser()
2095 return self::isUserType('super');
2099 * Checks if current user has global create user/grant privilege
2100 * or is a superuser (i.e. SELECT on mysql.users)
2101 * while caching the result in session.
2103 * @param string $type type of user to check for
2104 * i.e. 'create', 'grant', 'super'
2106 * @return bool Whether user is a given type of user
2108 public function isUserType($type)
2110 if (Util::cacheExists('is_' . $type . 'user')) {
2111 return Util::cacheGet('is_' . $type . 'user');
2114 // when connection failed we don't have a $userlink
2115 if (! isset($GLOBALS['userlink'])) {
2116 Util::cacheSet('is_' . $type . 'user', false);
2117 return Util::cacheGet('is_' . $type . 'user');
2120 if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') {
2121 // Prepare query for each user type check
2122 $query = '';
2123 if ($type === 'super') {
2124 $query = 'SELECT 1 FROM mysql.user LIMIT 1';
2125 } elseif ($type === 'create') {
2126 list($user, $host) = $this->getCurrentUserAndHost();
2127 $query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` "
2128 . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND "
2129 . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
2130 } elseif ($type === 'grant') {
2131 list($user, $host) = $this->getCurrentUserAndHost();
2132 $query = "SELECT 1 FROM ("
2133 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2134 . "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION "
2135 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2136 . "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION "
2137 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2138 . "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION "
2139 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2140 . "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t "
2141 . "WHERE `IS_GRANTABLE` = 'YES' AND "
2142 . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
2145 $is = false;
2146 $result = $this->tryQuery(
2147 $query,
2148 $GLOBALS['userlink'],
2149 self::QUERY_STORE
2151 if ($result) {
2152 $is = (bool) $this->numRows($result);
2154 $this->freeResult($result);
2156 Util::cacheSet('is_' . $type . 'user', $is);
2157 } else {
2158 $is = false;
2159 $grants = $this->fetchResult(
2160 "SHOW GRANTS FOR CURRENT_USER();",
2161 null,
2162 null,
2163 $GLOBALS['userlink'],
2164 self::QUERY_STORE
2166 if ($grants) {
2167 foreach ($grants as $grant) {
2168 if ($type === 'create') {
2169 if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false
2170 || strpos($grant, "CREATE USER") !== false
2172 $is = true;
2173 break;
2175 } elseif ($type === 'grant') {
2176 if (strpos($grant, "WITH GRANT OPTION") !== false) {
2177 $is = true;
2178 break;
2184 Util::cacheSet('is_' . $type . 'user', $is);
2187 return Util::cacheGet('is_' . $type . 'user');
2191 * Get the current user and host
2193 * @return array array of username and hostname
2195 public function getCurrentUserAndHost()
2197 if (count($this->_current_user) == 0) {
2198 $user = $this->getCurrentUser();
2199 $this->_current_user = explode("@", $user);
2201 return $this->_current_user;
2205 * Returns value for lower_case_table_names variable
2207 * @return string
2209 public function getLowerCaseNames()
2211 if (is_null($this->_lower_case_table_names)) {
2212 $this->_lower_case_table_names = $this->fetchValue(
2213 "SELECT @@lower_case_table_names"
2216 return $this->_lower_case_table_names;
2220 * Get the list of system schemas
2222 * @return array list of system schemas
2224 public function getSystemSchemas()
2226 $schemas = array(
2227 'information_schema', 'performance_schema', 'mysql', 'sys'
2229 $systemSchemas = array();
2230 foreach ($schemas as $schema) {
2231 if ($this->isSystemSchema($schema, true)) {
2232 $systemSchemas[] = $schema;
2235 return $systemSchemas;
2239 * Checks whether given schema is a system schema
2241 * @param string $schema_name Name of schema (database) to test
2242 * @param bool $testForMysqlSchema Whether 'mysql' schema should
2243 * be treated the same as IS and DD
2245 * @return bool
2247 public function isSystemSchema($schema_name, $testForMysqlSchema = false)
2249 $schema_name = strtolower($schema_name);
2250 return $schema_name == 'information_schema'
2251 || $schema_name == 'performance_schema'
2252 || ($schema_name == 'mysql' && $testForMysqlSchema)
2253 || $schema_name == 'sys';
2257 * Return connection parameters for the database server
2259 * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL
2260 * or CONNECT_AUXILIARY.
2261 * @param array $server Server information like host/port/socket/persistent
2263 * @return array user, host and server settings array
2265 public function getConnectionParams($mode, $server = null)
2267 global $cfg;
2269 $user = null;
2270 $password = null;
2272 if ($mode == DatabaseInterface::CONNECT_USER) {
2273 $user = $cfg['Server']['user'];
2274 $password = $cfg['Server']['password'];
2275 $server = $cfg['Server'];
2276 } elseif ($mode == DatabaseInterface::CONNECT_CONTROL) {
2277 $user = $cfg['Server']['controluser'];
2278 $password = $cfg['Server']['controlpass'];
2280 $server = array();
2282 if (! empty($cfg['Server']['controlhost'])) {
2283 $server['host'] = $cfg['Server']['controlhost'];
2284 } else {
2285 $server['host'] = $cfg['Server']['host'];
2287 // Share the settings if the host is same
2288 if ($server['host'] == $cfg['Server']['host']) {
2289 $shared = array(
2290 'port', 'socket', 'compress',
2291 'ssl', 'ssl_key', 'ssl_cert', 'ssl_ca',
2292 'ssl_ca_path', 'ssl_ciphers', 'ssl_verify',
2294 foreach ($shared as $item) {
2295 if (isset($cfg['Server'][$item])) {
2296 $server[$item] = $cfg['Server'][$item];
2300 // Set configured port
2301 if (! empty($cfg['Server']['controlport'])) {
2302 $server['port'] = $cfg['Server']['controlport'];
2304 // Set any configuration with control_ prefix
2305 foreach ($cfg['Server'] as $key => $val) {
2306 if (substr($key, 0, 8) === 'control_') {
2307 $server[substr($key, 8)] = $val;
2310 } else {
2311 if (is_null($server)) {
2312 return array(null, null, null);
2314 if (isset($server['user'])) {
2315 $user = $server['user'];
2317 if (isset($server['password'])) {
2318 $password = $server['password'];
2322 // Perform sanity checks on some variables
2323 if (empty($server['port'])) {
2324 $server['port'] = 0;
2325 } else {
2326 $server['port'] = intval($server['port']);
2328 if (empty($server['socket'])) {
2329 $server['socket'] = null;
2331 if (empty($server['host'])) {
2332 $server['host'] = 'localhost';
2334 if (!isset($server['ssl'])) {
2335 $server['ssl'] = false;
2337 if (!isset($server['compress'])) {
2338 $server['compress'] = false;
2341 return array($user, $password, $server);
2345 * connects to the database server
2347 * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL
2348 * or CONNECT_AUXILIARY.
2349 * @param array $server Server information like host/port/socket/persistent
2351 * @return mixed false on error or a connection object on success
2353 public function connect($mode, $server = null)
2355 list($user, $password, $server) = $this->getConnectionParams($mode, $server);
2357 if (is_null($user) || is_null($password)) {
2358 trigger_error(
2359 __('Missing connection parameters!'),
2360 E_USER_WARNING
2362 return false;
2365 // Do not show location and backtrace for connection errors
2366 $GLOBALS['error_handler']->setHideLocation(true);
2367 $result = $this->_extension->connect(
2368 $user, $password, $server
2370 $GLOBALS['error_handler']->setHideLocation(false);
2372 if ($result) {
2373 /* Run post connect for user connections */
2374 if ($mode == DatabaseInterface::CONNECT_USER) {
2375 $this->postConnect($result);
2377 return $result;
2380 if ($mode == DatabaseInterface::CONNECT_CONTROL) {
2381 trigger_error(
2383 'Connection for controluser as defined in your '
2384 . 'configuration failed.'
2386 E_USER_WARNING
2388 return false;
2389 } else if ($mode == DatabaseInterface::CONNECT_AUXILIARY) {
2390 // Do not go back to main login if connection failed
2391 // (currently used only in unit testing)
2392 return false;
2395 return $result;
2399 * selects given database
2401 * @param string $dbname database name to select
2402 * @param object $link connection object
2404 * @return boolean
2406 public function selectDb($dbname, $link = null)
2408 $link = $this->getLink($link);
2409 if ($link === false) {
2410 return false;
2412 return $this->_extension->selectDb($dbname, $link);
2416 * returns array of rows with associative and numeric keys from $result
2418 * @param object $result result set identifier
2420 * @return array
2422 public function fetchArray($result)
2424 return $this->_extension->fetchArray($result);
2428 * returns array of rows with associative keys from $result
2430 * @param object $result result set identifier
2432 * @return array
2434 public function fetchAssoc($result)
2436 return $this->_extension->fetchAssoc($result);
2440 * returns array of rows with numeric keys from $result
2442 * @param object $result result set identifier
2444 * @return array
2446 public function fetchRow($result)
2448 return $this->_extension->fetchRow($result);
2452 * Adjusts the result pointer to an arbitrary row in the result
2454 * @param object $result database result
2455 * @param integer $offset offset to seek
2457 * @return bool true on success, false on failure
2459 public function dataSeek($result, $offset)
2461 return $this->_extension->dataSeek($result, $offset);
2465 * Frees memory associated with the result
2467 * @param object $result database result
2469 * @return void
2471 public function freeResult($result)
2473 $this->_extension->freeResult($result);
2477 * Check if there are any more query results from a multi query
2479 * @param object $link the connection object
2481 * @return bool true or false
2483 public function moreResults($link = null)
2485 $link = $this->getLink($link);
2486 if ($link === false) {
2487 return false;
2489 return $this->_extension->moreResults($link);
2493 * Prepare next result from multi_query
2495 * @param object $link the connection object
2497 * @return bool true or false
2499 public function nextResult($link = null)
2501 $link = $this->getLink($link);
2502 if ($link === false) {
2503 return false;
2505 return $this->_extension->nextResult($link);
2509 * Store the result returned from multi query
2511 * @param object $link the connection object
2513 * @return mixed false when empty results / result set when not empty
2515 public function storeResult($link = null)
2517 $link = $this->getLink($link);
2518 if ($link === false) {
2519 return false;
2521 return $this->_extension->storeResult($link);
2525 * Returns a string representing the type of connection used
2527 * @param object $link mysql link
2529 * @return string type of connection used
2531 public function getHostInfo($link = null)
2533 $link = $this->getLink($link);
2534 if ($link === false) {
2535 return false;
2537 return $this->_extension->getHostInfo($link);
2541 * Returns the version of the MySQL protocol used
2543 * @param object $link mysql link
2545 * @return integer version of the MySQL protocol used
2547 public function getProtoInfo($link = null)
2549 $link = $this->getLink($link);
2550 if ($link === false) {
2551 return false;
2553 return $this->_extension->getProtoInfo($link);
2557 * returns a string that represents the client library version
2559 * @return string MySQL client library version
2561 public function getClientInfo()
2563 return $this->_extension->getClientInfo();
2567 * returns last error message or false if no errors occurred
2569 * @param object $link connection link
2571 * @return string|bool $error or false
2573 public function getError($link = null)
2575 $link = $this->getLink($link);
2576 return $this->_extension->getError($link);
2580 * returns the number of rows returned by last query
2582 * @param object $result result set identifier
2584 * @return string|int
2586 public function numRows($result)
2588 return $this->_extension->numRows($result);
2592 * returns last inserted auto_increment id for given $link
2593 * or $GLOBALS['userlink']
2595 * @param object $link the connection object
2597 * @return int|boolean
2599 public function insertId($link = null)
2601 $link = $this->getLink($link);
2602 if ($link === false) {
2603 return false;
2605 // If the primary key is BIGINT we get an incorrect result
2606 // (sometimes negative, sometimes positive)
2607 // and in the present function we don't know if the PK is BIGINT
2608 // so better play safe and use LAST_INSERT_ID()
2610 // When no controluser is defined, using mysqli_insert_id($link)
2611 // does not always return the last insert id due to a mixup with
2612 // the tracking mechanism, but this works:
2613 return $this->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link);
2617 * returns the number of rows affected by last query
2619 * @param object $link the connection object
2620 * @param bool $get_from_cache whether to retrieve from cache
2622 * @return int|boolean
2624 public function affectedRows($link = null, $get_from_cache = true)
2626 $link = $this->getLink($link);
2627 if ($link === false) {
2628 return false;
2631 if ($get_from_cache) {
2632 return $GLOBALS['cached_affected_rows'];
2633 } else {
2634 return $this->_extension->affectedRows($link);
2639 * returns metainfo for fields in $result
2641 * @param object $result result set identifier
2643 * @return array meta info for fields in $result
2645 public function getFieldsMeta($result)
2647 $result = $this->_extension->getFieldsMeta($result);
2649 if ($this->getLowerCaseNames() === '2') {
2651 * Fixup orgtable for lower_case_table_names = 2
2653 * In this setup MySQL server reports table name lower case
2654 * but we still need to operate on original case to properly
2655 * match existing strings
2657 foreach ($result as $value) {
2658 if (strlen($value->orgtable) !== 0 &&
2659 mb_strtolower($value->orgtable) === mb_strtolower($value->table)) {
2660 $value->orgtable = $value->table;
2665 return $result;
2669 * return number of fields in given $result
2671 * @param object $result result set identifier
2673 * @return int field count
2675 public function numFields($result)
2677 return $this->_extension->numFields($result);
2681 * returns the length of the given field $i in $result
2683 * @param object $result result set identifier
2684 * @param int $i field
2686 * @return int length of field
2688 public function fieldLen($result, $i)
2690 return $this->_extension->fieldLen($result, $i);
2694 * returns name of $i. field in $result
2696 * @param object $result result set identifier
2697 * @param int $i field
2699 * @return string name of $i. field in $result
2701 public function fieldName($result, $i)
2703 return $this->_extension->fieldName($result, $i);
2707 * returns concatenated string of human readable field flags
2709 * @param object $result result set identifier
2710 * @param int $i field
2712 * @return string field flags
2714 public function fieldFlags($result, $i)
2716 return $this->_extension->fieldFlags($result, $i);
2720 * returns properly escaped string for use in MySQL queries
2722 * @param string $str string to be escaped
2723 * @param mixed $link optional database link to use
2725 * @return string a MySQL escaped string
2727 public function escapeString($str, $link = null)
2729 if ($link === null) {
2730 $link = $this->getLink();
2733 if ($this->_extension === null) {
2734 return $str;
2737 return $this->_extension->escapeString($link, $str);
2741 * Gets correct link object.
2743 * @param object $link optional database link to use
2745 * @return object|boolean
2747 public function getLink($link = null)
2749 if (! is_null($link) && $link !== false) {
2750 return $link;
2753 if (isset($GLOBALS['userlink']) && !is_null($GLOBALS['userlink'])) {
2754 return $GLOBALS['userlink'];
2755 } else {
2756 return false;
2761 * Checks if this database server is running on Amazon RDS.
2763 * @return boolean
2765 public function isAmazonRds()
2767 if (Util::cacheExists('is_amazon_rds')) {
2768 return Util::cacheGet('is_amazon_rds');
2770 $sql = 'SELECT @@basedir';
2771 $result = $this->fetchValue($sql);
2772 $rds = (substr($result, 0, 10) == '/rdsdbbin/');
2773 Util::cacheSet('is_amazon_rds', $rds);
2775 return $rds;
2779 * Gets SQL for killing a process.
2781 * @param int $process Process ID
2783 * @return string
2785 public function getKillQuery($process)
2787 if ($this->isAmazonRds()) {
2788 return 'CALL mysql.rds_kill(' . $process . ');';
2789 } else {
2790 return 'KILL ' . $process . ';';
2795 * Get the phpmyadmin database manager
2797 * @return SystemDatabase
2799 public function getSystemDatabase()
2801 return new SystemDatabase($this);
2805 * Get a table with database name and table name
2807 * @param string $db_name DB name
2808 * @param string $table_name Table name
2810 * @return Table
2812 public function getTable($db_name, $table_name)
2814 return new Table($table_name, $db_name, $this);
2818 * returns collation of given db
2820 * @param string $db name of db
2822 * @return string collation of $db
2824 public function getDbCollation($db)
2826 if ($this->isSystemSchema($db)) {
2827 // We don't have to check the collation of the virtual
2828 // information_schema database: We know it!
2829 return 'utf8_general_ci';
2832 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
2833 // this is slow with thousands of databases
2834 $sql = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA'
2835 . ' WHERE SCHEMA_NAME = \'' . $this->escapeString($db)
2836 . '\' LIMIT 1';
2837 return $this->fetchValue($sql);
2838 } else {
2839 $this->selectDb($db);
2840 $return = $this->fetchValue('SELECT @@collation_database');
2841 if ($db !== $GLOBALS['db']) {
2842 $this->selectDb($GLOBALS['db']);
2844 return $return;
2849 * returns default server collation from show variables
2851 * @return string $server_collation
2853 function getServerCollation()
2855 return $this->fetchValue('SELECT @@collation_server');