Translated using Weblate (Hindi)
[phpmyadmin.git] / libraries / DatabaseInterface.class.php
blob6f1f7241b4c87c6e745a1ea9065273588d104965
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Main interface for database interactions
6 * @package PhpMyAdmin-DBI
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 require_once './libraries/logging.lib.php';
13 require_once './libraries/Index.class.php';
15 /**
16 * Main interface for database interactions
18 * @package PhpMyAdmin-DBI
20 class PMA_DatabaseInterface
22 /**
23 * Force STORE_RESULT method, ignored by classic MySQL.
25 const QUERY_STORE = 1;
26 /**
27 * Do not read whole query.
29 const QUERY_UNBUFFERED = 2;
30 /**
31 * Get session variable.
33 const GETVAR_SESSION = 1;
34 /**
35 * Get global variable.
37 const GETVAR_GLOBAL = 2;
39 /**
40 * @var PMA_DBI_Extension
42 private $_extension;
44 /**
45 * Constructor
47 * @param PMA_DBI_Extension $ext Object to be used for database queries
49 public function __construct(PMA_DBI_Extension $ext)
51 $this->_extension = $ext;
54 /**
55 * Checks whether database extension is loaded
57 * @param string $extension mysql extension to check
59 * @return bool
61 public static function checkDbExtension($extension = 'mysql')
63 if ($extension == 'drizzle' && function_exists('drizzle_create')) {
64 return true;
65 } else if (function_exists($extension . '_connect')) {
66 return true;
68 return false;
71 /**
72 * runs a query
74 * @param string $query SQL query to execute
75 * @param mixed $link optional database link to use
76 * @param int $options optional query options
77 * @param bool $cache_affected_rows whether to cache affected rows
79 * @return mixed
81 public function query($query, $link = null, $options = 0,
82 $cache_affected_rows = true
83 ) {
84 $res = $this->tryQuery($query, $link, $options, $cache_affected_rows)
85 or PMA_Util::mysqlDie($this->getError($link), $query);
86 return $res;
90 /**
91 * Caches table data so PMA_Table does not require to issue
92 * SHOW TABLE STATUS again
94 * @param array $tables information for tables of some databases
95 * @param string $table table name
97 * @return void
99 private function _cacheTableData($tables, $table)
101 // Note: I don't see why we would need array_merge_recursive() here,
102 // as it creates double entries for the same table (for example a double
103 // entry for Comment when changing the storage engine in Operations)
104 // Note 2: Instead of array_merge(), simply use the + operator because
105 // array_merge() renumbers numeric keys starting with 0, therefore
106 // we would lose a db name that consists only of numbers
108 foreach ($tables as $one_database => $its_tables) {
109 if (isset(PMA_Table::$cache[$one_database])) {
110 // the + operator does not do the intended effect
111 // when the cache for one table already exists
112 if ($table
113 && isset(PMA_Table::$cache[$one_database][$table])
115 unset(PMA_Table::$cache[$one_database][$table]);
117 PMA_Table::$cache[$one_database]
118 = PMA_Table::$cache[$one_database] + $tables[$one_database];
119 } else {
120 PMA_Table::$cache[$one_database] = $tables[$one_database];
126 * Stores query data into session data for debugging purposes
128 * @param string $query Query text
129 * @param object $link database link
130 * @param object $result Query result
131 * @param integer $time Time to execute query
133 * @return void
135 private function _dbgQuery($query, $link, $result, $time)
137 $hash = md5($query);
139 if (isset($_SESSION['debug']['queries'][$hash])) {
140 $_SESSION['debug']['queries'][$hash]['count']++;
141 } else {
142 $_SESSION['debug']['queries'][$hash] = array();
143 $error_message = $this->getError($link);
144 if ($result == false && is_string($error_message)) {
145 $_SESSION['debug']['queries'][$hash]['error']
146 = '<b style="color:red">'
147 . htmlspecialchars($error_message) . '</b>';
149 $_SESSION['debug']['queries'][$hash]['count'] = 1;
150 $_SESSION['debug']['queries'][$hash]['query'] = htmlspecialchars($query);
151 $_SESSION['debug']['queries'][$hash]['time'] = $time;
154 $_SESSION['debug']['queries'][$hash]['trace'][] = PMA_Error::formatBacktrace(
155 debug_backtrace(),
156 " ",
157 "\n"
162 * runs a query and returns the result
164 * @param string $query query to run
165 * @param object $link mysql link resource
166 * @param integer $options query options
167 * @param bool $cache_affected_rows whether to cache affected row
169 * @return mixed
171 public function tryQuery($query, $link = null, $options = 0,
172 $cache_affected_rows = true
174 $link = $this->getLink($link);
175 if ($link === false) {
176 return false;
179 if ($GLOBALS['cfg']['DBG']['sql']) {
180 $time = microtime(true);
183 $result = $this->_extension->realQuery($query, $link, $options);
185 if ($cache_affected_rows) {
186 $GLOBALS['cached_affected_rows'] = $this->affectedRows($link, false);
189 if ($GLOBALS['cfg']['DBG']['sql']) {
190 $time = microtime(true) - $time;
191 $this->_dbgQuery($query, $link, $result, $time);
193 if ($result != false && PMA_Tracker::isActive() == true ) {
194 PMA_Tracker::handleQuery($query);
197 return $result;
201 * Run multi query statement and return results
203 * @param string $multi_query multi query statement to execute
204 * @param mysqli $link mysqli object
206 * @return mysqli_result collection | boolean(false)
208 public function tryMultiQuery($multi_query = '', $link = null)
210 $link = $this->getLink($link);
211 if ($link === false) {
212 return false;
215 return $this->_extension->realMultiQuery($link, $multi_query);
219 * converts charset of a mysql message, usually coming from mysql_error(),
220 * into PMA charset, usually UTF-8
221 * uses language to charset mapping from mysql/share/errmsg.txt
222 * and charset names to ISO charset from information_schema.CHARACTER_SETS
224 * @param string $message the message
226 * @return string $message
228 public function convertMessage($message)
230 // latin always last!
231 $encodings = array(
232 'japanese' => 'EUC-JP', //'ujis',
233 'japanese-sjis' => 'Shift-JIS', //'sjis',
234 'korean' => 'EUC-KR', //'euckr',
235 'russian' => 'KOI8-R', //'koi8r',
236 'ukrainian' => 'KOI8-U', //'koi8u',
237 'greek' => 'ISO-8859-7', //'greek',
238 'serbian' => 'CP1250', //'cp1250',
239 'estonian' => 'ISO-8859-13', //'latin7',
240 'slovak' => 'ISO-8859-2', //'latin2',
241 'czech' => 'ISO-8859-2', //'latin2',
242 'hungarian' => 'ISO-8859-2', //'latin2',
243 'polish' => 'ISO-8859-2', //'latin2',
244 'romanian' => 'ISO-8859-2', //'latin2',
245 'spanish' => 'CP1252', //'latin1',
246 'swedish' => 'CP1252', //'latin1',
247 'italian' => 'CP1252', //'latin1',
248 'norwegian-ny' => 'CP1252', //'latin1',
249 'norwegian' => 'CP1252', //'latin1',
250 'portuguese' => 'CP1252', //'latin1',
251 'danish' => 'CP1252', //'latin1',
252 'dutch' => 'CP1252', //'latin1',
253 'english' => 'CP1252', //'latin1',
254 'french' => 'CP1252', //'latin1',
255 'german' => 'CP1252', //'latin1',
258 $server_language = $this->fetchValue(
259 'SHOW VARIABLES LIKE \'language\';',
263 if ($server_language) {
264 $found = array();
265 $match = preg_match(
266 '&(?:\\\|\\/)([^\\\\\/]*)(?:\\\|\\/)$&i',
267 $server_language,
268 $found
270 if ($match) {
271 $server_language = $found[1];
275 if (! empty($server_language) && isset($encodings[$server_language])) {
276 $encoding = $encodings[$server_language];
277 } else {
278 /* Fallback to CP1252 if we can not detect */
279 $encoding = 'CP1252';
282 return PMA_convertString($encoding, 'utf-8', $message);
286 * returns array with table names for given db
288 * @param string $database name of database
289 * @param mixed $link mysql link resource|object
291 * @return array tables names
293 public function getTables($database, $link = null)
295 return $this->fetchResult(
296 'SHOW TABLES FROM ' . PMA_Util::backquote($database) . ';',
297 null,
299 $link,
300 self::QUERY_STORE
305 * returns a segment of the SQL WHERE clause regarding table name and type
307 * @param string $table table
308 * @param boolean $tbl_is_group $table is a table group
309 * @param string $table_type whether table or view
311 * @return string a segment of the WHERE clause
313 private function _getTableCondition($table, $tbl_is_group, $table_type)
315 // get table information from information_schema
316 if ($table) {
317 if (true === $tbl_is_group) {
318 $sql_where_table = 'AND t.`TABLE_NAME` LIKE \''
319 . PMA_Util::escapeMysqlWildcards(
320 PMA_Util::sqlAddSlashes($table)
322 . '%\'';
323 } else {
324 $sql_where_table = 'AND t.`TABLE_NAME` = \''
325 . PMA_Util::sqlAddSlashes($table) . '\'';
327 } else {
328 $sql_where_table = '';
331 if ($table_type) {
332 if ($table_type == 'view') {
333 if (PMA_DRIZZLE) {
334 $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE'";
335 } else {
336 $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE TABLE'";
338 } else if ($table_type == 'table') {
339 if (PMA_DRIZZLE) {
340 $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE'";
341 } else {
342 $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE TABLE'";
346 return $sql_where_table;
350 * returns the beginning of the SQL statement to fetch the list of tables
352 * @param string[] $this_databases databases to list
353 * @param string $sql_where_table additional condition
355 * @return string the SQL statement
357 private function _getSqlForTablesFull($this_databases, $sql_where_table)
359 if (PMA_DRIZZLE) {
360 $stats_join = $this->_getDrizzeStatsJoin();
362 // data_dictionary.table_cache may not contain any data
363 // for some tables, it's just a table cache
364 // auto_increment == 0 is cast to NULL because currently
365 // (2011.03.13 GA)
366 // Drizzle doesn't provide correct value
367 $sql = "
368 SELECT t.*,
369 t.TABLE_SCHEMA AS `Db`,
370 t.TABLE_NAME AS `Name`,
371 t.TABLE_TYPE AS `TABLE_TYPE`,
372 t.ENGINE AS `Engine`,
373 t.ENGINE AS `Type`,
374 t.TABLE_VERSION AS `Version`,-- VERSION
375 t.ROW_FORMAT AS `Row_format`,
376 coalesce(tc.ROWS, stat.NUM_ROWS)
377 AS `Rows`,-- TABLE_ROWS,
378 coalesce(tc.ROWS, stat.NUM_ROWS)
379 AS `TABLE_ROWS`,
380 tc.AVG_ROW_LENGTH AS `Avg_row_length`, -- AVG_ROW_LENGTH
381 tc.TABLE_SIZE AS `Data_length`, -- DATA_LENGTH
382 NULL AS `Max_data_length`, -- MAX_DATA_LENGTH
383 NULL AS `Index_length`, -- INDEX_LENGTH
384 NULL AS `Data_free`, -- DATA_FREE
385 nullif(t.AUTO_INCREMENT, 0)
386 AS `Auto_increment`,
387 t.TABLE_CREATION_TIME AS `Create_time`, -- CREATE_TIME
388 t.TABLE_UPDATE_TIME AS `Update_time`, -- UPDATE_TIME
389 NULL AS `Check_time`, -- CHECK_TIME
390 t.TABLE_COLLATION AS `Collation`,
391 NULL AS `Checksum`, -- CHECKSUM
392 NULL AS `Create_options`, -- CREATE_OPTIONS
393 t.TABLE_COMMENT AS `Comment`
394 FROM data_dictionary.TABLES t
395 LEFT JOIN data_dictionary.TABLE_CACHE tc
396 ON tc.TABLE_SCHEMA = t.TABLE_SCHEMA AND tc.TABLE_NAME
397 = t.TABLE_NAME
398 $stats_join
399 WHERE t.TABLE_SCHEMA IN ('" . implode("', '", $this_databases) . "')
400 " . $sql_where_table;
401 } else {
402 $sql = '
403 SELECT *,
404 `TABLE_SCHEMA` AS `Db`,
405 `TABLE_NAME` AS `Name`,
406 `TABLE_TYPE` AS `TABLE_TYPE`,
407 `ENGINE` AS `Engine`,
408 `ENGINE` AS `Type`,
409 `VERSION` AS `Version`,
410 `ROW_FORMAT` AS `Row_format`,
411 `TABLE_ROWS` AS `Rows`,
412 `AVG_ROW_LENGTH` AS `Avg_row_length`,
413 `DATA_LENGTH` AS `Data_length`,
414 `MAX_DATA_LENGTH` AS `Max_data_length`,
415 `INDEX_LENGTH` AS `Index_length`,
416 `DATA_FREE` AS `Data_free`,
417 `AUTO_INCREMENT` AS `Auto_increment`,
418 `CREATE_TIME` AS `Create_time`,
419 `UPDATE_TIME` AS `Update_time`,
420 `CHECK_TIME` AS `Check_time`,
421 `TABLE_COLLATION` AS `Collation`,
422 `CHECKSUM` AS `Checksum`,
423 `CREATE_OPTIONS` AS `Create_options`,
424 `TABLE_COMMENT` AS `Comment`
425 FROM `information_schema`.`TABLES` t
426 WHERE ' . (PMA_IS_WINDOWS ? '' : 'BINARY') . ' `TABLE_SCHEMA`
427 IN (\'' . implode("', '", $this_databases) . '\')
428 ' . $sql_where_table;
430 return $sql;
434 * returns array of all tables in given db or dbs
435 * this function expects unquoted names:
436 * RIGHT: my_database
437 * WRONG: `my_database`
438 * WRONG: my\_database
439 * if $tbl_is_group is true, $table is used as filter for table names
441 * <code>
442 * $GLOBALS['dbi']->getTablesFull('my_database');
443 * $GLOBALS['dbi']->getTablesFull('my_database', 'my_table'));
444 * $GLOBALS['dbi']->getTablesFull('my_database', 'my_tables_', true));
445 * </code>
447 * @param string $database database
448 * @param string $table table name
449 * @param boolean $tbl_is_group $table is a table group
450 * @param mixed $link mysql link
451 * @param integer $limit_offset zero-based offset for the count
452 * @param boolean|integer $limit_count number of tables to return
453 * @param string $sort_by table attribute to sort by
454 * @param string $sort_order direction to sort (ASC or DESC)
455 * @param string $table_type whether table or view
457 * @todo move into PMA_Table
459 * @return array list of tables in given db(s)
461 public function getTablesFull($database, $table = '',
462 $tbl_is_group = false, $link = null, $limit_offset = 0,
463 $limit_count = false, $sort_by = 'Name', $sort_order = 'ASC',
464 $table_type = null
466 if (true === $limit_count) {
467 $limit_count = $GLOBALS['cfg']['MaxTableList'];
469 // prepare and check parameters
470 if (! is_array($database)) {
471 $databases = array($database);
472 } else {
473 $databases = $database;
476 $tables = array();
478 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
479 $sql_where_table = $this->_getTableCondition(
480 $table, $tbl_is_group, $table_type
483 // for PMA bc:
484 // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME`
486 // on non-Windows servers,
487 // added BINARY in the WHERE clause to force a case sensitive
488 // comparison (if we are looking for the db Aa we don't want
489 // to find the db aa)
490 $this_databases = array_map('PMA_Util::sqlAddSlashes', $databases);
492 $sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table);
494 // Sort the tables
495 $sql .= " ORDER BY $sort_by $sort_order";
497 if ($limit_count) {
498 $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
501 $tables = $this->fetchResult(
502 $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link
505 if (PMA_DRIZZLE) {
506 // correct I_S and D_D names returned by D_D.TABLES -
507 // Drizzle generally uses lower case for them,
508 // but TABLES returns uppercase
509 foreach ((array)$database as $db) {
510 $db_upper = /*overload*/mb_strtoupper($db);
511 if (!isset($tables[$db]) && isset($tables[$db_upper])) {
512 $tables[$db] = $tables[$db_upper];
513 unset($tables[$db_upper]);
518 if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
519 // here, the array's first key is by schema name
520 foreach ($tables as $one_database_name => $one_database_tables) {
521 uksort($one_database_tables, 'strnatcasecmp');
523 if ($sort_order == 'DESC') {
524 $one_database_tables = array_reverse($one_database_tables);
526 $tables[$one_database_name] = $one_database_tables;
528 } else if ($sort_by == 'Data_length') { // Size = Data_length + Index_length
529 foreach ($tables as $one_database_name => $one_database_tables) {
530 uasort(
531 $one_database_tables,
532 function ($a, $b) {
533 $aLength = $a['Data_length'] + $a['Index_length'];
534 $bLength = $b['Data_length'] + $b['Index_length'];
535 return ($aLength == $bLength)
537 : ($aLength < $bLength) ? -1 : 1;
541 if ($sort_order == 'DESC') {
542 $one_database_tables = array_reverse($one_database_tables);
544 $tables[$one_database_name] = $one_database_tables;
547 } // end (get information from table schema)
549 // If permissions are wrong on even one database directory,
550 // information_schema does not return any table info for any database
551 // this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002
552 if (empty($tables) && !PMA_DRIZZLE) {
553 foreach ($databases as $each_database) {
554 if ($table || (true === $tbl_is_group) || $table_type) {
555 $sql = 'SHOW TABLE STATUS FROM '
556 . PMA_Util::backquote($each_database)
557 . ' WHERE';
558 $needAnd = false;
559 if ($table || (true === $tbl_is_group)) {
560 $sql .= " `Name` LIKE '"
561 . PMA_Util::escapeMysqlWildcards(
562 PMA_Util::sqlAddSlashes($table, true)
564 . "%'";
565 $needAnd = true;
567 if ($table_type) {
568 if ($needAnd) {
569 $sql .= " AND";
571 if ($table_type == 'view') {
572 $sql .= " `Comment` = 'VIEW'";
573 } else if ($table_type == 'table') {
574 $sql .= " `Comment` != 'VIEW'";
577 } else {
578 $sql = 'SHOW TABLE STATUS FROM '
579 . PMA_Util::backquote($each_database);
582 $useStatusCache = false;
584 if (extension_loaded('apc')
585 && isset($GLOBALS['cfg']['Server']['StatusCacheDatabases'])
586 && ! empty($GLOBALS['cfg']['Server']['StatusCacheLifetime'])
588 $statusCacheDatabases
589 = (array) $GLOBALS['cfg']['Server']['StatusCacheDatabases'];
590 if (in_array($each_database, $statusCacheDatabases)) {
591 $useStatusCache = true;
595 $each_tables = null;
597 if ($useStatusCache) {
598 $cacheKey = 'phpMyAdmin_tableStatus_'
599 . sha1($GLOBALS['cfg']['Server']['host'] . '_' . $sql);
601 $each_tables = apc_fetch($cacheKey);
604 if (! $each_tables) {
605 $each_tables = $this->fetchResult($sql, 'Name', null, $link);
608 if ($useStatusCache) {
609 apc_store(
610 $cacheKey, $each_tables,
611 $GLOBALS['cfg']['Server']['StatusCacheLifetime']
615 // Sort naturally if the config allows it and we're sorting
616 // the Name column.
617 if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
618 uksort($each_tables, 'strnatcasecmp');
620 if ($sort_order == 'DESC') {
621 $each_tables = array_reverse($each_tables);
623 } else {
624 // Prepare to sort by creating array of the selected sort
625 // value to pass to array_multisort
627 // Size = Data_length + Index_length
628 if ($sort_by == 'Data_length') {
629 foreach ($each_tables as $table_name => $table_data) {
630 ${$sort_by}[$table_name] = strtolower(
631 $table_data['Data_length'] + $table_data['Index_length']
634 } else {
635 foreach ($each_tables as $table_name => $table_data) {
636 ${$sort_by}[$table_name]
637 = strtolower($table_data[$sort_by]);
641 if (! empty($$sort_by)) {
642 if ($sort_order == 'DESC') {
643 array_multisort($$sort_by, SORT_DESC, $each_tables);
644 } else {
645 array_multisort($$sort_by, SORT_ASC, $each_tables);
649 // cleanup the temporary sort array
650 unset($$sort_by);
653 if ($limit_count) {
654 $each_tables = array_slice(
655 $each_tables, $limit_offset, $limit_count
659 foreach ($each_tables as $table_name => $each_table) {
660 if (! isset($each_tables[$table_name]['Type'])
661 && isset($each_tables[$table_name]['Engine'])
663 // pma BC, same parts of PMA still uses 'Type'
664 $each_tables[$table_name]['Type']
665 =& $each_tables[$table_name]['Engine'];
666 } elseif (! isset($each_tables[$table_name]['Engine'])
667 && isset($each_tables[$table_name]['Type'])
669 // old MySQL reports Type, newer MySQL reports Engine
670 $each_tables[$table_name]['Engine']
671 =& $each_tables[$table_name]['Type'];
674 // MySQL forward compatibility
675 // so pma could use this array as if every server
676 // is of version >5.0
677 // todo : remove and check usage in the rest of the code,
678 // MySQL 5.0 is required by current PMA version
679 $each_tables[$table_name]['TABLE_SCHEMA']
680 = $each_database;
681 $each_tables[$table_name]['TABLE_NAME']
682 =& $each_tables[$table_name]['Name'];
683 $each_tables[$table_name]['ENGINE']
684 =& $each_tables[$table_name]['Engine'];
685 $each_tables[$table_name]['VERSION']
686 =& $each_tables[$table_name]['Version'];
687 $each_tables[$table_name]['ROW_FORMAT']
688 =& $each_tables[$table_name]['Row_format'];
689 $each_tables[$table_name]['TABLE_ROWS']
690 =& $each_tables[$table_name]['Rows'];
691 $each_tables[$table_name]['AVG_ROW_LENGTH']
692 =& $each_tables[$table_name]['Avg_row_length'];
693 $each_tables[$table_name]['DATA_LENGTH']
694 =& $each_tables[$table_name]['Data_length'];
695 $each_tables[$table_name]['MAX_DATA_LENGTH']
696 =& $each_tables[$table_name]['Max_data_length'];
697 $each_tables[$table_name]['INDEX_LENGTH']
698 =& $each_tables[$table_name]['Index_length'];
699 $each_tables[$table_name]['DATA_FREE']
700 =& $each_tables[$table_name]['Data_free'];
701 $each_tables[$table_name]['AUTO_INCREMENT']
702 =& $each_tables[$table_name]['Auto_increment'];
703 $each_tables[$table_name]['CREATE_TIME']
704 =& $each_tables[$table_name]['Create_time'];
705 $each_tables[$table_name]['UPDATE_TIME']
706 =& $each_tables[$table_name]['Update_time'];
707 $each_tables[$table_name]['CHECK_TIME']
708 =& $each_tables[$table_name]['Check_time'];
709 $each_tables[$table_name]['TABLE_COLLATION']
710 =& $each_tables[$table_name]['Collation'];
711 $each_tables[$table_name]['CHECKSUM']
712 =& $each_tables[$table_name]['Checksum'];
713 $each_tables[$table_name]['CREATE_OPTIONS']
714 =& $each_tables[$table_name]['Create_options'];
715 $each_tables[$table_name]['TABLE_COMMENT']
716 =& $each_tables[$table_name]['Comment'];
718 if (strtoupper($each_tables[$table_name]['Comment']) === 'VIEW'
719 && $each_tables[$table_name]['Engine'] == null
721 $each_tables[$table_name]['TABLE_TYPE'] = 'VIEW';
722 } elseif ($each_database == 'information_schema') {
723 $each_tables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW';
724 } else {
726 * @todo difference between 'TEMPORARY' and 'BASE TABLE'
727 * but how to detect?
729 $each_tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE';
733 $tables[$each_database] = $each_tables;
737 // cache table data
738 // so PMA_Table does not require to issue SHOW TABLE STATUS again
739 $this->_cacheTableData($tables, $table);
741 if (is_array($database)) {
742 return $tables;
745 if (isset($tables[$database])) {
746 return $tables[$database];
749 if (isset($tables[/*overload*/mb_strtolower($database)])) {
750 // on windows with lower_case_table_names = 1
751 // MySQL returns
752 // with SHOW DATABASES or information_schema.SCHEMATA: `Test`
753 // but information_schema.TABLES gives `test`
754 // bug #2036
755 // https://sourceforge.net/p/phpmyadmin/bugs/2036/
756 return $tables[/*overload*/mb_strtolower($database)];
759 // one database but inexact letter case match
760 // as Drizzle is always case insensitive,
761 // we can safely return the only result
762 if (!PMA_DRIZZLE || !count($tables) == 1) {
763 return $tables;
766 $keys = array_keys($tables);
767 if (/*overload*/mb_strlen(array_pop($keys)) == /*overload*/mb_strlen($database)) {
768 return array_pop($tables);
770 return $tables;
774 * Copies the table properties to the set of property names used by PMA.
776 * @param array $tables array of table properties
777 * @param string $database database name
779 * @return array array with added properties
781 public function copyTableProperties($tables, $database)
783 foreach ($tables as $table_name => $each_table) {
784 if (! isset($tables[$table_name]['Type'])
785 && isset($tables[$table_name]['Engine'])
787 // pma BC, same parts of PMA still uses 'Type'
788 $tables[$table_name]['Type']
789 =& $tables[$table_name]['Engine'];
790 } elseif (! isset($tables[$table_name]['Engine'])
791 && isset($tables[$table_name]['Type'])
793 // old MySQL reports Type, newer MySQL reports Engine
794 $tables[$table_name]['Engine']
795 =& $tables[$table_name]['Type'];
798 // MySQL forward compatibility
799 // so pma could use this array as if every server
800 // is of version >5.0
801 // todo : remove and check usage in the rest of the code,
802 // MySQL 5.0 is required by current PMA version
803 $tables[$table_name]['TABLE_SCHEMA']
804 = $database;
805 $tables[$table_name]['TABLE_NAME']
806 =& $tables[$table_name]['Name'];
807 $tables[$table_name]['ENGINE']
808 =& $tables[$table_name]['Engine'];
809 $tables[$table_name]['VERSION']
810 =& $tables[$table_name]['Version'];
811 $tables[$table_name]['ROW_FORMAT']
812 =& $tables[$table_name]['Row_format'];
813 $tables[$table_name]['TABLE_ROWS']
814 =& $tables[$table_name]['Rows'];
815 $tables[$table_name]['AVG_ROW_LENGTH']
816 =& $tables[$table_name]['Avg_row_length'];
817 $tables[$table_name]['DATA_LENGTH']
818 =& $tables[$table_name]['Data_length'];
819 $tables[$table_name]['MAX_DATA_LENGTH']
820 =& $tables[$table_name]['Max_data_length'];
821 $tables[$table_name]['INDEX_LENGTH']
822 =& $tables[$table_name]['Index_length'];
823 $tables[$table_name]['DATA_FREE']
824 =& $tables[$table_name]['Data_free'];
825 $tables[$table_name]['AUTO_INCREMENT']
826 =& $tables[$table_name]['Auto_increment'];
827 $tables[$table_name]['CREATE_TIME']
828 =& $tables[$table_name]['Create_time'];
829 $tables[$table_name]['UPDATE_TIME']
830 =& $tables[$table_name]['Update_time'];
831 $tables[$table_name]['CHECK_TIME']
832 =& $tables[$table_name]['Check_time'];
833 $tables[$table_name]['TABLE_COLLATION']
834 =& $tables[$table_name]['Collation'];
835 $tables[$table_name]['CHECKSUM']
836 =& $tables[$table_name]['Checksum'];
837 $tables[$table_name]['CREATE_OPTIONS']
838 =& $tables[$table_name]['Create_options'];
839 $tables[$table_name]['TABLE_COMMENT']
840 =& $tables[$table_name]['Comment'];
842 $commentUpper = /*overload*/mb_strtoupper(
843 $tables[$table_name]['Comment']
845 if ($commentUpper === 'VIEW'
846 && $tables[$table_name]['Engine'] == null
848 $tables[$table_name]['TABLE_TYPE'] = 'VIEW';
849 } else {
851 * @todo difference between 'TEMPORARY' and 'BASE TABLE'
852 * but how to detect?
854 $tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE';
857 return $tables;
861 * Get VIEWs in a particular database
863 * @param string $db Database name to look in
865 * @return array $views Set of VIEWs inside the database
867 public function getVirtualTables($db)
870 $tables_full = $this->getTablesFull($db);
871 $views = array();
873 foreach ($tables_full as $table=>$tmp) {
875 if (PMA_Table::isView($db, $table)) {
876 $views[] = $table;
881 return $views;
887 * returns array with databases containing extended infos about them
889 * @param string $database database
890 * @param boolean $force_stats retrieve stats also for MySQL < 5
891 * @param object $link mysql link
892 * @param string $sort_by column to order by
893 * @param string $sort_order ASC or DESC
894 * @param integer $limit_offset starting offset for LIMIT
895 * @param bool|int $limit_count row count for LIMIT or true
896 * for $GLOBALS['cfg']['MaxDbList']
898 * @todo move into PMA_List_Database?
900 * @return array $databases
902 public function getDatabasesFull($database = null, $force_stats = false,
903 $link = null, $sort_by = 'SCHEMA_NAME', $sort_order = 'ASC',
904 $limit_offset = 0, $limit_count = false
906 $sort_order = strtoupper($sort_order);
908 if (true === $limit_count) {
909 $limit_count = $GLOBALS['cfg']['MaxDbList'];
912 $apply_limit_and_order_manual = true;
914 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
916 * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT
917 * cause MySQL does not support natural ordering,
918 * we have to do it afterward
920 $limit = '';
921 if (! $GLOBALS['cfg']['NaturalOrder']) {
922 if ($limit_count) {
923 $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
926 $apply_limit_and_order_manual = false;
929 // get table information from information_schema
930 if ($database) {
931 $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \''
932 . PMA_Util::sqlAddSlashes($database) . '\'';
933 } else {
934 $sql_where_schema = '';
937 if (PMA_DRIZZLE) {
938 // data_dictionary.table_cache may not contain any data for some
939 // tables, it's just a table cache
940 $sql = 'SELECT
941 s.SCHEMA_NAME,
942 s.DEFAULT_COLLATION_NAME';
943 if ($force_stats) {
944 // no TABLE_CACHE data, stable results are better than
945 // constantly changing
946 $sql .= ',
947 COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,
948 SUM(stat.NUM_ROWS) AS SCHEMA_TABLE_ROWS';
950 $sql .= '
951 FROM data_dictionary.SCHEMAS s';
952 if ($force_stats) {
953 $stats_join = $this->_getDrizzeStatsJoin();
955 $sql .= "
956 LEFT JOIN data_dictionary.TABLES t
957 ON t.TABLE_SCHEMA = s.SCHEMA_NAME
958 $stats_join";
960 $sql .= $sql_where_schema . '
961 GROUP BY s.SCHEMA_NAME, s.DEFAULT_COLLATION_NAME
962 ORDER BY ' . PMA_Util::backquote($sort_by) . ' ' . $sort_order
963 . $limit;
964 } else {
965 $sql = 'SELECT *,
966 CAST(BIN_NAME AS CHAR CHARACTER SET utf8) AS SCHEMA_NAME
967 FROM (';
968 $sql .= 'SELECT
969 BINARY s.SCHEMA_NAME AS BIN_NAME,
970 s.DEFAULT_COLLATION_NAME';
971 if ($force_stats) {
972 $sql .= ',
973 COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,
974 SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS,
975 SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH,
976 SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH,
977 SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH,
978 SUM(t.DATA_LENGTH + t.INDEX_LENGTH)
979 AS SCHEMA_LENGTH,
980 SUM(t.DATA_FREE) AS SCHEMA_DATA_FREE';
982 $sql .= '
983 FROM `information_schema`.SCHEMATA s';
984 if ($force_stats) {
985 $sql .= '
986 LEFT JOIN `information_schema`.TABLES t
987 ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME';
989 $sql .= $sql_where_schema . '
990 GROUP BY BINARY s.SCHEMA_NAME, s.DEFAULT_COLLATION_NAME
991 ORDER BY ';
992 if ($sort_by == 'SCHEMA_NAME'
993 || $sort_by == 'DEFAULT_COLLATION_NAME'
995 $sql .= 'BINARY ';
997 $sql .= PMA_Util::backquote($sort_by)
998 . ' ' . $sort_order
999 . $limit;
1000 $sql .= ') a';
1003 $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link);
1005 $mysql_error = $this->getError($link);
1006 if (! count($databases) && $GLOBALS['errno']) {
1007 PMA_Util::mysqlDie($mysql_error, $sql);
1010 // display only databases also in official database list
1011 // f.e. to apply hide_db and only_db
1012 $drops = array_diff(
1013 array_keys($databases), (array) $GLOBALS['pma']->databases
1015 foreach ($drops as $drop) {
1016 unset($databases[$drop]);
1018 } else {
1019 $databases = array();
1020 foreach ($GLOBALS['pma']->databases as $database_name) {
1021 // MySQL forward compatibility
1022 // so pma could use this array as if every server is of version >5.0
1023 // todo : remove and check the rest of the code for usage,
1024 // MySQL 5.0 or higher is required for current PMA version
1025 $databases[$database_name]['SCHEMA_NAME'] = $database_name;
1027 include_once './libraries/mysql_charsets.inc.php';
1028 $databases[$database_name]['DEFAULT_COLLATION_NAME']
1029 = PMA_getDbCollation($database_name);
1031 if ($force_stats) {
1033 // get additional info about tables
1034 $databases[$database_name]['SCHEMA_TABLES'] = 0;
1035 $databases[$database_name]['SCHEMA_TABLE_ROWS'] = 0;
1036 $databases[$database_name]['SCHEMA_DATA_LENGTH'] = 0;
1037 $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] = 0;
1038 $databases[$database_name]['SCHEMA_INDEX_LENGTH'] = 0;
1039 $databases[$database_name]['SCHEMA_LENGTH'] = 0;
1040 $databases[$database_name]['SCHEMA_DATA_FREE'] = 0;
1042 $res = $this->query(
1043 'SHOW TABLE STATUS FROM '
1044 . PMA_Util::backquote($database_name) . ';'
1047 while ($row = $this->fetchAssoc($res)) {
1048 $databases[$database_name]['SCHEMA_TABLES']++;
1049 $databases[$database_name]['SCHEMA_TABLE_ROWS']
1050 += $row['Rows'];
1051 $databases[$database_name]['SCHEMA_DATA_LENGTH']
1052 += $row['Data_length'];
1053 $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH']
1054 += $row['Max_data_length'];
1055 $databases[$database_name]['SCHEMA_INDEX_LENGTH']
1056 += $row['Index_length'];
1058 // for InnoDB, this does not contain the number of
1059 // overhead bytes but the total free space
1060 if ('InnoDB' != $row['Engine']) {
1061 $databases[$database_name]['SCHEMA_DATA_FREE']
1062 += $row['Data_free'];
1064 $databases[$database_name]['SCHEMA_LENGTH']
1065 += $row['Data_length'] + $row['Index_length'];
1067 $this->freeResult($res);
1068 unset($res);
1074 * apply limit and order manually now
1075 * (caused by older MySQL < 5 or $GLOBALS['cfg']['NaturalOrder'])
1077 if ($apply_limit_and_order_manual) {
1078 $GLOBALS['callback_sort_order'] = $sort_order;
1079 $GLOBALS['callback_sort_by'] = $sort_by;
1080 usort(
1081 $databases,
1082 array('PMA_DatabaseInterface', '_usortComparisonCallback')
1084 unset($GLOBALS['callback_sort_order'], $GLOBALS['callback_sort_by']);
1087 * now apply limit
1089 if ($limit_count) {
1090 $databases = array_slice($databases, $limit_offset, $limit_count);
1094 return $databases;
1099 * Generates JOIN part for the Drizzle query to get database/table stats.
1101 * @return string
1103 private function _getDrizzeStatsJoin()
1105 $engine_info = PMA_Util::cacheGet('drizzle_engines');
1106 $stats_join = "LEFT JOIN (SELECT 0 NUM_ROWS) AS stat ON false";
1107 if (isset($engine_info['InnoDB'])
1108 && $engine_info['InnoDB']['module_library'] == 'innobase'
1110 $stats_join
1111 = "LEFT JOIN data_dictionary.INNODB_SYS_TABLESTATS stat"
1112 . " ON (t.ENGINE = 'InnoDB' AND stat.NAME"
1113 . " = (t.TABLE_SCHEMA || '/') || t.TABLE_NAME)";
1115 return $stats_join;
1120 * usort comparison callback
1122 * @param string $a first argument to sort
1123 * @param string $b second argument to sort
1125 * @return integer a value representing whether $a should be before $b in the
1126 * sorted array or not
1128 * @access private
1130 private static function _usortComparisonCallback($a, $b)
1132 if ($GLOBALS['cfg']['NaturalOrder']) {
1133 $sorter = 'strnatcasecmp';
1134 } else {
1135 $sorter = 'strcasecmp';
1137 /* No sorting when key is not present */
1138 if (! isset($a[$GLOBALS['callback_sort_by']])
1139 || ! isset($b[$GLOBALS['callback_sort_by']])
1141 return 0;
1143 // produces f.e.:
1144 // return -1 * strnatcasecmp($a["SCHEMA_TABLES"], $b["SCHEMA_TABLES"])
1145 return ($GLOBALS['callback_sort_order'] == 'ASC' ? 1 : -1) * $sorter(
1146 $a[$GLOBALS['callback_sort_by']], $b[$GLOBALS['callback_sort_by']]
1148 } // end of the '_usortComparisonCallback()' method
1151 * returns detailed array with all columns for given table in database,
1152 * or all tables/databases
1154 * @param string $database name of database
1155 * @param string $table name of table to retrieve columns from
1156 * @param string $column name of specific column
1157 * @param mixed $link mysql link resource
1159 * @return array
1161 public function getColumnsFull($database = null, $table = null,
1162 $column = null, $link = null
1164 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
1165 $sql_wheres = array();
1166 $array_keys = array();
1168 // get columns information from information_schema
1169 if (null !== $database) {
1170 $sql_wheres[] = '`TABLE_SCHEMA` = \''
1171 . PMA_Util::sqlAddSlashes($database) . '\' ';
1172 } else {
1173 $array_keys[] = 'TABLE_SCHEMA';
1175 if (null !== $table) {
1176 $sql_wheres[] = '`TABLE_NAME` = \''
1177 . PMA_Util::sqlAddSlashes($table) . '\' ';
1178 } else {
1179 $array_keys[] = 'TABLE_NAME';
1181 if (null !== $column) {
1182 $sql_wheres[] = '`COLUMN_NAME` = \''
1183 . PMA_Util::sqlAddSlashes($column) . '\' ';
1184 } else {
1185 $array_keys[] = 'COLUMN_NAME';
1188 // for PMA bc:
1189 // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]`
1190 if (PMA_DRIZZLE) {
1191 $sql = "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME,
1192 column_name AS `Field`,
1193 (CASE
1194 WHEN character_maximum_length > 0
1195 THEN concat(lower(data_type), '(', character_maximum_length, ')')
1196 WHEN numeric_precision > 0 OR numeric_scale > 0
1197 THEN concat(lower(data_type), '(', numeric_precision,
1198 ',', numeric_scale, ')')
1199 WHEN enum_values IS NOT NULL
1200 THEN concat(lower(data_type), '(', enum_values, ')')
1201 ELSE lower(data_type) END)
1202 AS `Type`,
1203 collation_name AS `Collation`,
1204 (CASE is_nullable
1205 WHEN 1 THEN 'YES'
1206 ELSE 'NO' END) AS `Null`,
1207 (CASE
1208 WHEN is_used_in_primary THEN 'PRI'
1209 ELSE '' END) AS `Key`,
1210 column_default AS `Default`,
1211 (CASE
1212 WHEN is_auto_increment THEN 'auto_increment'
1213 WHEN column_default_update
1214 THEN 'on update ' || column_default_update
1215 ELSE '' END) AS `Extra`,
1216 NULL AS `Privileges`,
1217 column_comment AS `Comment`
1218 FROM data_dictionary.columns";
1219 } else {
1220 $sql = '
1221 SELECT *,
1222 `COLUMN_NAME` AS `Field`,
1223 `COLUMN_TYPE` AS `Type`,
1224 `COLLATION_NAME` AS `Collation`,
1225 `IS_NULLABLE` AS `Null`,
1226 `COLUMN_KEY` AS `Key`,
1227 `COLUMN_DEFAULT` AS `Default`,
1228 `EXTRA` AS `Extra`,
1229 `PRIVILEGES` AS `Privileges`,
1230 `COLUMN_COMMENT` AS `Comment`
1231 FROM `information_schema`.`COLUMNS`';
1233 if (count($sql_wheres)) {
1234 $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres);
1236 return $this->fetchResult($sql, $array_keys, null, $link);
1237 } else {
1238 $columns = array();
1239 if (null === $database) {
1240 foreach ($GLOBALS['pma']->databases as $database) {
1241 $columns[$database] = $this->getColumnsFull(
1242 $database, null, null, $link
1245 return $columns;
1246 } elseif (null === $table) {
1247 $tables = $this->getTables($database);
1248 foreach ($tables as $table) {
1249 $columns[$table] = $this->getColumnsFull(
1250 $database, $table, null, $link
1253 return $columns;
1255 $sql = 'SHOW FULL COLUMNS FROM '
1256 . PMA_Util::backquote($database) . '.' . PMA_Util::backquote($table);
1257 if (null !== $column) {
1258 $sql .= " LIKE '" . PMA_Util::sqlAddSlashes($column, true) . "'";
1261 $columns = $this->fetchResult($sql, 'Field', null, $link);
1262 $ordinal_position = 1;
1263 foreach ($columns as $column_name => $each_column) {
1265 // MySQL forward compatibility
1266 // so pma could use this array as if every server is of version >5.0
1267 // todo : remove and check the rest of the code for usage,
1268 // MySQL 5.0 or higher is required for current PMA version
1269 $columns[$column_name]['COLUMN_NAME']
1270 =& $columns[$column_name]['Field'];
1271 $columns[$column_name]['COLUMN_TYPE']
1272 =& $columns[$column_name]['Type'];
1273 $columns[$column_name]['COLLATION_NAME']
1274 =& $columns[$column_name]['Collation'];
1275 $columns[$column_name]['IS_NULLABLE']
1276 =& $columns[$column_name]['Null'];
1277 $columns[$column_name]['COLUMN_KEY']
1278 =& $columns[$column_name]['Key'];
1279 $columns[$column_name]['COLUMN_DEFAULT']
1280 =& $columns[$column_name]['Default'];
1281 $columns[$column_name]['EXTRA']
1282 =& $columns[$column_name]['Extra'];
1283 $columns[$column_name]['PRIVILEGES']
1284 =& $columns[$column_name]['Privileges'];
1285 $columns[$column_name]['COLUMN_COMMENT']
1286 =& $columns[$column_name]['Comment'];
1288 $columns[$column_name]['TABLE_CATALOG'] = null;
1289 $columns[$column_name]['TABLE_SCHEMA'] = $database;
1290 $columns[$column_name]['TABLE_NAME'] = $table;
1291 $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position;
1292 $columns[$column_name]['DATA_TYPE']
1293 = substr(
1294 $columns[$column_name]['COLUMN_TYPE'],
1296 strpos($columns[$column_name]['COLUMN_TYPE'], '(')
1299 * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE
1301 $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null;
1303 * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH
1305 $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null;
1306 $columns[$column_name]['NUMERIC_PRECISION'] = null;
1307 $columns[$column_name]['NUMERIC_SCALE'] = null;
1308 $columns[$column_name]['CHARACTER_SET_NAME']
1309 = substr(
1310 $columns[$column_name]['COLLATION_NAME'],
1312 strpos($columns[$column_name]['COLLATION_NAME'], '_')
1315 $ordinal_position++;
1318 if (null !== $column) {
1319 reset($columns);
1320 $columns = current($columns);
1323 return $columns;
1328 * Returns SQL query for fetching columns for a table
1330 * The 'Key' column is not calculated properly, use $GLOBALS['dbi']->getColumns()
1331 * to get correct values.
1333 * @param string $database name of database
1334 * @param string $table name of table to retrieve columns from
1335 * @param string $column name of column, null to show all columns
1336 * @param boolean $full whether to return full info or only column names
1338 * @see getColumns()
1340 * @return string
1342 public function getColumnsSql($database, $table, $column = null, $full = false)
1344 if (defined('PMA_DRIZZLE') && PMA_DRIZZLE) {
1345 // `Key` column:
1346 // * used in primary key => PRI
1347 // * unique one-column => UNI
1348 // * indexed, one-column or first in multi-column => MUL
1349 // Promotion of UNI to PRI in case no primary index exists
1350 // is done after query is executed
1351 $sql = "SELECT
1352 column_name AS `Field`,
1353 (CASE
1354 WHEN character_maximum_length > 0
1355 THEN concat(
1356 lower(data_type), '(', character_maximum_length, ')'
1358 WHEN numeric_precision > 0 OR numeric_scale > 0
1359 THEN concat(lower(data_type), '(', numeric_precision,
1360 ',', numeric_scale, ')')
1361 WHEN enum_values IS NOT NULL
1362 THEN concat(lower(data_type), '(', enum_values, ')')
1363 ELSE lower(data_type) END)
1364 AS `Type`,
1365 " . ($full ? "
1366 collation_name AS `Collation`," : '') . "
1367 (CASE is_nullable
1368 WHEN 1 THEN 'YES'
1369 ELSE 'NO' END) AS `Null`,
1370 (CASE
1371 WHEN is_used_in_primary THEN 'PRI'
1372 WHEN is_unique AND NOT is_multi THEN 'UNI'
1373 WHEN is_indexed
1374 AND (NOT is_multi OR is_first_in_multi) THEN 'MUL'
1375 ELSE '' END) AS `Key`,
1376 column_default AS `Default`,
1377 (CASE
1378 WHEN is_auto_increment THEN 'auto_increment'
1379 WHEN column_default_update <> ''
1380 THEN 'on update ' || column_default_update
1381 ELSE '' END) AS `Extra`
1382 " . ($full ? " ,
1383 NULL AS `Privileges`,
1384 column_comment AS `Comment`" : '') . "
1385 FROM data_dictionary.columns
1386 WHERE table_schema = '" . PMA_Util::sqlAddSlashes($database) . "'
1387 AND table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
1388 " . (
1389 ($column != null)
1391 AND column_name = '" . PMA_Util::sqlAddSlashes($column) . "'"
1392 : ''
1394 // ORDER BY ordinal_position
1395 } else {
1396 $sql = 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM '
1397 . PMA_Util::backquote($database) . '.' . PMA_Util::backquote($table)
1398 . (($column != null) ? "LIKE '"
1399 . PMA_Util::sqlAddSlashes($column, true) . "'" : '');
1401 return $sql;
1405 * Returns descriptions of columns in given table (all or given by $column)
1407 * @param string $database name of database
1408 * @param string $table name of table to retrieve columns from
1409 * @param string $column name of column, null to show all columns
1410 * @param boolean $full whether to return full info or only column names
1411 * @param mixed $link mysql link resource
1413 * @return false|array array indexed by column names or,
1414 * if $column is given, flat array description
1416 public function getColumns($database, $table, $column = null, $full = false,
1417 $link = null
1419 $sql = $this->getColumnsSql($database, $table, $column, $full);
1420 $fields = $this->fetchResult($sql, 'Field', null, $link);
1421 if (! is_array($fields) || count($fields) == 0) {
1422 return null;
1424 // Check if column is a part of multiple-column index and set its 'Key'.
1425 $indexes = PMA_Index::getFromTable($table, $database);
1426 foreach ($fields as $field => $field_data) {
1427 if (!empty($field_data['Key'])) {
1428 continue;
1431 foreach ($indexes as $index) {
1432 /** @var PMA_Index $index */
1433 if (!$index->hasColumn($field)) {
1434 continue;
1437 $index_columns = $index->getColumns();
1438 if ($index_columns[$field]->getSeqInIndex() > 1) {
1439 if ($index->isUnique()) {
1440 $fields[$field]['Key'] = 'UNI';
1441 } else {
1442 $fields[$field]['Key'] = 'MUL';
1447 if (PMA_DRIZZLE) {
1448 // fix Key column, it's much simpler in PHP than in SQL
1449 $has_pk = false;
1450 $has_pk_candidates = false;
1451 foreach ($fields as $f) {
1452 if ($f['Key'] == 'PRI') {
1453 $has_pk = true;
1454 break;
1455 } else if ($f['Null'] == 'NO'
1456 && ($f['Key'] == 'MUL'
1457 || $f['Key'] == 'UNI')
1459 $has_pk_candidates = true;
1462 if (! $has_pk && $has_pk_candidates) {
1463 $secureDatabase = PMA_Util::sqlAddSlashes($database);
1464 // check whether we can promote some unique index to PRI
1465 $sql = "
1466 SELECT i.index_name, p.column_name
1467 FROM data_dictionary.indexes i
1468 JOIN data_dictionary.index_parts p
1469 USING (table_schema, table_name)
1470 WHERE i.table_schema = '" . $secureDatabase . "'
1471 AND i.table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
1472 AND i.is_unique
1473 AND NOT i.is_nullable";
1474 $result = $this->fetchResult($sql, 'index_name', null, $link);
1475 $result = $result ? array_shift($result) : array();
1476 foreach ($result as $f) {
1477 $fields[$f]['Key'] = 'PRI';
1482 return ($column != null) ? array_shift($fields) : $fields;
1486 * Returns all column names in given table
1488 * @param string $database name of database
1489 * @param string $table name of table to retrieve columns from
1490 * @param mixed $link mysql link resource
1492 * @return null|array
1494 public function getColumnNames($database, $table, $link = null)
1496 $sql = $this->getColumnsSql($database, $table);
1497 // We only need the 'Field' column which contains the table's column names
1498 $fields = array_keys($this->fetchResult($sql, 'Field', null, $link));
1500 if ( ! is_array($fields) || count($fields) == 0 ) {
1501 return null;
1503 return $fields;
1507 * Returns SQL for fetching information on table indexes (SHOW INDEXES)
1509 * @param string $database name of database
1510 * @param string $table name of the table whose indexes are to be retrieved
1511 * @param string $where additional conditions for WHERE
1513 * @return string SQL for getting indexes
1515 public function getTableIndexesSql($database, $table, $where = null)
1517 if (PMA_DRIZZLE) {
1518 $sql = "SELECT
1519 ip.table_name AS `Table`,
1520 (NOT ip.is_unique) AS Non_unique,
1521 ip.index_name AS Key_name,
1522 ip.sequence_in_index+1 AS Seq_in_index,
1523 ip.column_name AS Column_name,
1524 (CASE
1525 WHEN i.index_type = 'BTREE' THEN 'A'
1526 ELSE NULL END) AS Collation,
1527 NULL AS Cardinality,
1528 compare_length AS Sub_part,
1529 NULL AS Packed,
1530 ip.is_nullable AS `Null`,
1531 i.index_type AS Index_type,
1532 NULL AS Comment,
1533 i.index_comment AS Index_comment
1534 FROM data_dictionary.index_parts ip
1535 LEFT JOIN data_dictionary.indexes i
1536 USING (table_schema, table_name, index_name)
1537 WHERE table_schema = '" . PMA_Util::sqlAddSlashes($database) . "'
1538 AND table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
1540 if ($where) {
1541 $sql = "SELECT * FROM (" . $sql . ") A WHERE (" . $where . ")";
1543 } else {
1544 $sql = 'SHOW INDEXES FROM ' . PMA_Util::backquote($database) . '.'
1545 . PMA_Util::backquote($table);
1546 if ($where) {
1547 $sql .= ' WHERE (' . $where . ')';
1550 return $sql;
1554 * Returns indexes of a table
1556 * @param string $database name of database
1557 * @param string $table name of the table whose indexes are to be retrieved
1558 * @param mixed $link mysql link resource
1560 * @return array $indexes
1562 public function getTableIndexes($database, $table, $link = null)
1564 $sql = $this->getTableIndexesSql($database, $table);
1565 $indexes = $this->fetchResult($sql, null, null, $link);
1567 if (! is_array($indexes) || count($indexes) < 1) {
1568 return array();
1570 return $indexes;
1574 * returns value of given mysql server variable
1576 * @param string $var mysql server variable name
1577 * @param int $type PMA_DatabaseInterface::GETVAR_SESSION |
1578 * PMA_DatabaseInterface::GETVAR_GLOBAL
1579 * @param mixed $link mysql link resource|object
1581 * @return mixed value for mysql server variable
1583 public function getVariable(
1584 $var, $type = self::GETVAR_SESSION, $link = null
1586 $link = $this->getLink($link);
1587 if ($link === false) {
1588 return false;
1591 switch ($type) {
1592 case self::GETVAR_SESSION:
1593 $modifier = ' SESSION';
1594 break;
1595 case self::GETVAR_GLOBAL:
1596 $modifier = ' GLOBAL';
1597 break;
1598 default:
1599 $modifier = '';
1601 return $this->fetchValue(
1602 'SHOW' . $modifier . ' VARIABLES LIKE \'' . $var . '\';', 0, 1, $link
1607 * Function called just after a connection to the MySQL database server has
1608 * been established. It sets the connection collation, and determines the
1609 * version of MySQL which is running.
1611 * @param mixed $link mysql link resource|object
1613 * @return void
1615 public function postConnect($link)
1617 if (! defined('PMA_MYSQL_INT_VERSION')) {
1618 if (PMA_Util::cacheExists('PMA_MYSQL_INT_VERSION')) {
1619 define(
1620 'PMA_MYSQL_INT_VERSION',
1621 PMA_Util::cacheGet('PMA_MYSQL_INT_VERSION')
1623 define(
1624 'PMA_MYSQL_MAJOR_VERSION',
1625 PMA_Util::cacheGet('PMA_MYSQL_MAJOR_VERSION')
1627 define(
1628 'PMA_MYSQL_STR_VERSION',
1629 PMA_Util::cacheGet('PMA_MYSQL_STR_VERSION')
1631 define(
1632 'PMA_MYSQL_VERSION_COMMENT',
1633 PMA_Util::cacheGet('PMA_MYSQL_VERSION_COMMENT')
1635 define(
1636 'PMA_DRIZZLE',
1637 PMA_Util::cacheGet('PMA_DRIZZLE')
1639 } else {
1640 $version = $this->fetchSingleRow(
1641 'SELECT @@version, @@version_comment',
1642 'ASSOC',
1643 $link
1646 if ($version) {
1647 $match = explode('.', $version['@@version']);
1648 define('PMA_MYSQL_MAJOR_VERSION', (int)$match[0]);
1649 define(
1650 'PMA_MYSQL_INT_VERSION',
1651 (int) sprintf(
1652 '%d%02d%02d', $match[0], $match[1], intval($match[2])
1655 define('PMA_MYSQL_STR_VERSION', $version['@@version']);
1656 define(
1657 'PMA_MYSQL_VERSION_COMMENT',
1658 $version['@@version_comment']
1660 } else {
1661 define('PMA_MYSQL_INT_VERSION', 50501);
1662 define('PMA_MYSQL_MAJOR_VERSION', 5);
1663 define('PMA_MYSQL_STR_VERSION', '5.05.01');
1664 define('PMA_MYSQL_VERSION_COMMENT', '');
1666 PMA_Util::cacheSet(
1667 'PMA_MYSQL_INT_VERSION',
1668 PMA_MYSQL_INT_VERSION
1670 PMA_Util::cacheSet(
1671 'PMA_MYSQL_MAJOR_VERSION',
1672 PMA_MYSQL_MAJOR_VERSION
1674 PMA_Util::cacheSet(
1675 'PMA_MYSQL_STR_VERSION',
1676 PMA_MYSQL_STR_VERSION
1678 PMA_Util::cacheSet(
1679 'PMA_MYSQL_VERSION_COMMENT',
1680 PMA_MYSQL_VERSION_COMMENT
1683 /* Detect Drizzle - it does not support charsets */
1684 $charset_result = $this->query(
1685 "SHOW VARIABLES LIKE 'character_set_results'",
1686 $link
1688 if ($this->numRows($charset_result) == 0) {
1689 define('PMA_DRIZZLE', true);
1690 } else {
1691 define('PMA_DRIZZLE', false);
1693 $this->freeResult($charset_result);
1695 PMA_Util::cacheSet(
1696 'PMA_DRIZZLE',
1697 PMA_DRIZZLE
1702 // Skip charsets for Drizzle
1703 if (!PMA_DRIZZLE) {
1704 if (PMA_MYSQL_INT_VERSION > 50503) {
1705 $default_charset = 'utf8mb4';
1706 $default_collation = 'utf8mb4_general_ci';
1707 } else {
1708 $default_charset = 'utf8';
1709 $default_collation = 'utf8_general_ci';
1711 if (! empty($GLOBALS['collation_connection'])) {
1712 $this->query(
1713 "SET CHARACTER SET '$default_charset';",
1714 $link,
1715 self::QUERY_STORE
1717 /* Automatically adjust collation to mb4 variant */
1718 if ($default_charset == 'utf8mb4'
1719 && strncmp('utf8_', $GLOBALS['collation_connection'], 5) == 0
1721 $GLOBALS['collation_connection'] = 'utf8mb4_' . substr(
1722 $GLOBALS['collation_connection'],
1726 $this->query(
1727 "SET collation_connection = '"
1728 . PMA_Util::sqlAddSlashes($GLOBALS['collation_connection'])
1729 . "';",
1730 $link,
1731 self::QUERY_STORE
1733 } else {
1734 $this->query(
1735 "SET NAMES '$default_charset' COLLATE '$default_collation';",
1736 $link,
1737 self::QUERY_STORE
1742 // Cache plugin list for Drizzle
1743 if (PMA_DRIZZLE && !PMA_Util::cacheExists('drizzle_engines')) {
1744 $sql = "SELECT p.plugin_name, m.module_library
1745 FROM data_dictionary.plugins p
1746 JOIN data_dictionary.modules m USING (module_name)
1747 WHERE p.plugin_type = 'StorageEngine'
1748 AND p.plugin_name NOT IN ('FunctionEngine', 'schema')
1749 AND p.is_active = 'YES'";
1750 $engines = $this->fetchResult($sql, 'plugin_name', null, $link);
1751 PMA_Util::cacheSet('drizzle_engines', $engines);
1756 * returns a single value from the given result or query,
1757 * if the query or the result has more than one row or field
1758 * the first field of the first row is returned
1760 * <code>
1761 * $sql = 'SELECT `name` FROM `user` WHERE `id` = 123';
1762 * $user_name = $GLOBALS['dbi']->fetchValue($sql);
1763 * // produces
1764 * // $user_name = 'John Doe'
1765 * </code>
1767 * @param string $query The query to execute
1768 * @param integer $row_number row to fetch the value from,
1769 * starting at 0, with 0 being default
1770 * @param integer|string $field field to fetch the value from,
1771 * starting at 0, with 0 being default
1772 * @param object $link mysql link
1774 * @return mixed value of first field in first row from result
1775 * or false if not found
1777 public function fetchValue($query, $row_number = 0, $field = 0, $link = null)
1779 $value = false;
1781 $result = $this->tryQuery(
1782 $query,
1783 $link,
1784 self::QUERY_STORE,
1785 false
1787 if ($result === false) {
1788 return false;
1791 // return false if result is empty or false
1792 // or requested row is larger than rows in result
1793 if ($this->numRows($result) < ($row_number + 1)) {
1794 return $value;
1797 // if $field is an integer use non associative mysql fetch function
1798 if (is_int($field)) {
1799 $fetch_function = 'fetchRow';
1800 } else {
1801 $fetch_function = 'fetchAssoc';
1804 // get requested row
1805 for ($i = 0; $i <= $row_number; $i++) {
1806 $row = $this->$fetch_function($result);
1808 $this->freeResult($result);
1810 // return requested field
1811 if (isset($row[$field])) {
1812 $value = $row[$field];
1815 return $value;
1819 * returns only the first row from the result
1821 * <code>
1822 * $sql = 'SELECT * FROM `user` WHERE `id` = 123';
1823 * $user = $GLOBALS['dbi']->fetchSingleRow($sql);
1824 * // produces
1825 * // $user = array('id' => 123, 'name' => 'John Doe')
1826 * </code>
1828 * @param string $query The query to execute
1829 * @param string $type NUM|ASSOC|BOTH returned array should either
1830 * numeric associative or both
1831 * @param object $link mysql link
1833 * @return array|boolean first row from result
1834 * or false if result is empty
1836 public function fetchSingleRow($query, $type = 'ASSOC', $link = null)
1838 $result = $this->tryQuery(
1839 $query,
1840 $link,
1841 self::QUERY_STORE,
1842 false
1844 if ($result === false) {
1845 return false;
1848 // return false if result is empty or false
1849 if (! $this->numRows($result)) {
1850 return false;
1853 switch ($type) {
1854 case 'NUM' :
1855 $fetch_function = 'fetchRow';
1856 break;
1857 case 'ASSOC' :
1858 $fetch_function = 'fetchAssoc';
1859 break;
1860 case 'BOTH' :
1861 default :
1862 $fetch_function = 'fetchArray';
1863 break;
1866 $row = $this->$fetch_function($result);
1867 $this->freeResult($result);
1868 return $row;
1872 * Returns row or element of a row
1874 * @param array $row Row to process
1875 * @param string|null $value Which column to return
1877 * @return mixed
1879 private function _fetchValue($row, $value)
1881 if (is_null($value)) {
1882 return $row;
1883 } else {
1884 return $row[$value];
1889 * returns all rows in the resultset in one array
1891 * <code>
1892 * $sql = 'SELECT * FROM `user`';
1893 * $users = $GLOBALS['dbi']->fetchResult($sql);
1894 * // produces
1895 * // $users[] = array('id' => 123, 'name' => 'John Doe')
1897 * $sql = 'SELECT `id`, `name` FROM `user`';
1898 * $users = $GLOBALS['dbi']->fetchResult($sql, 'id');
1899 * // produces
1900 * // $users['123'] = array('id' => 123, 'name' => 'John Doe')
1902 * $sql = 'SELECT `id`, `name` FROM `user`';
1903 * $users = $GLOBALS['dbi']->fetchResult($sql, 0);
1904 * // produces
1905 * // $users['123'] = array(0 => 123, 1 => 'John Doe')
1907 * $sql = 'SELECT `id`, `name` FROM `user`';
1908 * $users = $GLOBALS['dbi']->fetchResult($sql, 'id', 'name');
1909 * // or
1910 * $users = $GLOBALS['dbi']->fetchResult($sql, 0, 1);
1911 * // produces
1912 * // $users['123'] = 'John Doe'
1914 * $sql = 'SELECT `name` FROM `user`';
1915 * $users = $GLOBALS['dbi']->fetchResult($sql);
1916 * // produces
1917 * // $users[] = 'John Doe'
1919 * $sql = 'SELECT `group`, `name` FROM `user`'
1920 * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', null), 'name');
1921 * // produces
1922 * // $users['admin'][] = 'John Doe'
1924 * $sql = 'SELECT `group`, `name` FROM `user`'
1925 * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', 'name'), 'id');
1926 * // produces
1927 * // $users['admin']['John Doe'] = '123'
1928 * </code>
1930 * @param string $query query to execute
1931 * @param string|integer|array $key field-name or offset
1932 * used as key for array
1933 * or array of those
1934 * @param string|integer $value value-name or offset
1935 * used as value for array
1936 * @param object $link mysql link
1937 * @param integer $options query options
1939 * @return array resultrows or values indexed by $key
1941 public function fetchResult($query, $key = null, $value = null,
1942 $link = null, $options = 0
1944 $resultrows = array();
1946 $result = $this->tryQuery($query, $link, $options, false);
1948 // return empty array if result is empty or false
1949 if ($result === false) {
1950 return $resultrows;
1953 $fetch_function = 'fetchAssoc';
1955 // no nested array if only one field is in result
1956 if (null === $key && 1 === $this->numFields($result)) {
1957 $value = 0;
1958 $fetch_function = 'fetchRow';
1961 // if $key is an integer use non associative mysql fetch function
1962 if (is_int($key)) {
1963 $fetch_function = 'fetchRow';
1966 if (null === $key) {
1967 while ($row = $this->$fetch_function($result)) {
1968 $resultrows[] = $this->_fetchValue($row, $value);
1970 } else {
1971 if (is_array($key)) {
1972 while ($row = $this->$fetch_function($result)) {
1973 $result_target =& $resultrows;
1974 foreach ($key as $key_index) {
1975 if (null === $key_index) {
1976 $result_target =& $result_target[];
1977 continue;
1980 if (! isset($result_target[$row[$key_index]])) {
1981 $result_target[$row[$key_index]] = array();
1983 $result_target =& $result_target[$row[$key_index]];
1985 $result_target = $this->_fetchValue($row, $value);
1987 } else {
1988 while ($row = $this->$fetch_function($result)) {
1989 $resultrows[$row[$key]] = $this->_fetchValue($row, $value);
1994 $this->freeResult($result);
1995 return $resultrows;
1999 * Get supported SQL compatibility modes
2001 * @return array supported SQL compatibility modes
2003 public function getCompatibilities()
2005 // Drizzle doesn't support compatibility modes
2006 if (PMA_DRIZZLE) {
2007 return array();
2010 $compats = array('NONE');
2011 $compats[] = 'ANSI';
2012 $compats[] = 'DB2';
2013 $compats[] = 'MAXDB';
2014 $compats[] = 'MYSQL323';
2015 $compats[] = 'MYSQL40';
2016 $compats[] = 'MSSQL';
2017 $compats[] = 'ORACLE';
2018 // removed; in MySQL 5.0.33, this produces exports that
2019 // can't be read by POSTGRESQL (see our bug #1596328)
2020 //$compats[] = 'POSTGRESQL';
2021 $compats[] = 'TRADITIONAL';
2023 return $compats;
2027 * returns warnings for last query
2029 * @param object $link mysql link resource
2031 * @return array warnings
2033 public function getWarnings($link = null)
2035 $link = $this->getLink($link);
2036 if ($link === false) {
2037 return false;
2040 return $this->fetchResult('SHOW WARNINGS', null, null, $link);
2044 * returns an array of PROCEDURE or FUNCTION names for a db
2046 * @param string $db db name
2047 * @param string $which PROCEDURE | FUNCTION
2048 * @param object $link mysql link
2050 * @return array the procedure names or function names
2052 public function getProceduresOrFunctions($db, $which, $link = null)
2054 if (PMA_DRIZZLE) {
2055 // Drizzle doesn't support functions and procedures
2056 return array();
2058 $shows = $this->fetchResult(
2059 'SHOW ' . $which . ' STATUS;', null, null, $link
2061 $result = array();
2062 foreach ($shows as $one_show) {
2063 if ($one_show['Db'] == $db && $one_show['Type'] == $which) {
2064 $result[] = $one_show['Name'];
2067 return($result);
2071 * returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW
2073 * @param string $db db name
2074 * @param string $which PROCEDURE | FUNCTION | EVENT | VIEW
2075 * @param string $name the procedure|function|event|view name
2077 * @return string the definition
2079 public function getDefinition($db, $which, $name)
2081 $returned_field = array(
2082 'PROCEDURE' => 'Create Procedure',
2083 'FUNCTION' => 'Create Function',
2084 'EVENT' => 'Create Event',
2085 'VIEW' => 'Create View'
2087 $query = 'SHOW CREATE ' . $which . ' '
2088 . PMA_Util::backquote($db) . '.'
2089 . PMA_Util::backquote($name);
2090 return($this->fetchValue($query, 0, $returned_field[$which]));
2094 * returns details about the TRIGGERs for a specific table or database
2096 * @param string $db db name
2097 * @param string $table table name
2098 * @param string $delimiter the delimiter to use (may be empty)
2100 * @return array information about triggers (may be empty)
2102 public function getTriggers($db, $table = '', $delimiter = '//')
2104 if (PMA_DRIZZLE) {
2105 // Drizzle doesn't support triggers
2106 return array();
2109 $result = array();
2110 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
2111 $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION'
2112 . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT'
2113 . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER'
2114 . ' FROM information_schema.TRIGGERS'
2115 . ' WHERE EVENT_OBJECT_SCHEMA ' . PMA_Util::getCollateForIS() . '='
2116 . ' \'' . PMA_Util::sqlAddSlashes($db) . '\'';
2118 if (! empty($table)) {
2119 $query .= " AND EVENT_OBJECT_TABLE = '"
2120 . PMA_Util::sqlAddSlashes($table) . "';";
2122 } else {
2123 $query = "SHOW TRIGGERS FROM " . PMA_Util::backquote($db);
2124 if (! empty($table)) {
2125 $query .= " LIKE '" . PMA_Util::sqlAddSlashes($table, true) . "';";
2129 if ($triggers = $this->fetchResult($query)) {
2130 foreach ($triggers as $trigger) {
2131 if ($GLOBALS['cfg']['Server']['DisableIS']) {
2132 $trigger['TRIGGER_NAME'] = $trigger['Trigger'];
2133 $trigger['ACTION_TIMING'] = $trigger['Timing'];
2134 $trigger['EVENT_MANIPULATION'] = $trigger['Event'];
2135 $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table'];
2136 $trigger['ACTION_STATEMENT'] = $trigger['Statement'];
2137 $trigger['DEFINER'] = $trigger['Definer'];
2139 $one_result = array();
2140 $one_result['name'] = $trigger['TRIGGER_NAME'];
2141 $one_result['table'] = $trigger['EVENT_OBJECT_TABLE'];
2142 $one_result['action_timing'] = $trigger['ACTION_TIMING'];
2143 $one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION'];
2144 $one_result['definition'] = $trigger['ACTION_STATEMENT'];
2145 $one_result['definer'] = $trigger['DEFINER'];
2147 // do not prepend the schema name; this way, importing the
2148 // definition into another schema will work
2149 $one_result['full_trigger_name'] = PMA_Util::backquote(
2150 $trigger['TRIGGER_NAME']
2152 $one_result['drop'] = 'DROP TRIGGER IF EXISTS '
2153 . $one_result['full_trigger_name'];
2154 $one_result['create'] = 'CREATE TRIGGER '
2155 . $one_result['full_trigger_name'] . ' '
2156 . $trigger['ACTION_TIMING'] . ' '
2157 . $trigger['EVENT_MANIPULATION']
2158 . ' ON ' . PMA_Util::backquote($trigger['EVENT_OBJECT_TABLE'])
2159 . "\n" . ' FOR EACH ROW '
2160 . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n";
2162 $result[] = $one_result;
2166 // Sort results by name
2167 $name = array();
2168 foreach ($result as $value) {
2169 $name[] = $value['name'];
2171 array_multisort($name, SORT_ASC, $result);
2173 return($result);
2177 * Formats database error message in a friendly way.
2178 * This is needed because some errors messages cannot
2179 * be obtained by mysql_error().
2181 * @param int $error_number Error code
2182 * @param string $error_message Error message as returned by server
2184 * @return string HML text with error details
2186 public function formatError($error_number, $error_message)
2188 if (! empty($error_message)) {
2189 $error_message = $this->convertMessage($error_message);
2192 $error_message = htmlspecialchars($error_message);
2194 $error = '#' . ((string) $error_number);
2196 if ($error_number == 2002) {
2197 $error .= ' - ' . $error_message;
2198 $error .= '<br />';
2199 $error .= __(
2200 'The server is not responding (or the local server\'s socket'
2201 . ' is not correctly configured).'
2203 } elseif ($error_number == 2003) {
2204 $error .= ' - ' . $error_message;
2205 $error .= '<br />' . __('The server is not responding.');
2206 } elseif ($error_number == 1005) {
2207 if (strpos($error_message, 'errno: 13') !== false) {
2208 $error .= ' - ' . $error_message;
2209 $error .= '<br />'
2210 . __('Please check privileges of directory containing database.');
2211 } else {
2212 /* InnoDB constraints, see
2213 * http://dev.mysql.com/doc/refman/5.0/en/
2214 * innodb-foreign-key-constraints.html
2216 $error .= ' - ' . $error_message .
2217 ' (<a href="server_engines.php' .
2218 PMA_URL_getCommon(
2219 array('engine' => 'InnoDB', 'page' => 'Status')
2220 ) . '">' . __('Details…') . '</a>)';
2222 } else {
2223 $error .= ' - ' . $error_message;
2226 return $error;
2230 * gets the current user with host
2232 * @return string the current user i.e. user@host
2234 public function getCurrentUser()
2236 if (PMA_Util::cacheExists('mysql_cur_user')) {
2237 return PMA_Util::cacheGet('mysql_cur_user');
2239 $user = $GLOBALS['dbi']->fetchValue('SELECT USER();');
2240 if ($user !== false) {
2241 PMA_Util::cacheSet('mysql_cur_user', $user);
2242 return PMA_Util::cacheGet('mysql_cur_user');
2244 return '';
2248 * Checks if current user is superuser
2250 * @return bool Whether user is a superuser
2252 public function isSuperuser()
2254 return self::isUserType('super');
2258 * Checks if current user has global create user/grant privilege
2259 * or is a superuser (i.e. SELECT on mysql.users)
2260 * while caching the result in session.
2262 * @param string $type type of user to check for
2263 * i.e. 'create', 'grant', 'super'
2265 * @return bool Whether user is a given type of user
2267 public function isUserType($type)
2269 if (PMA_Util::cacheExists('is_' . $type . 'user')) {
2270 return PMA_Util::cacheGet('is_' . $type . 'user');
2273 // when connection failed we don't have a $userlink
2274 if (! isset($GLOBALS['userlink'])) {
2275 PMA_Util::cacheSet('is_' . $type . 'user', false);
2276 return PMA_Util::cacheGet('is_' . $type . 'user');
2279 if (PMA_DRIZZLE) {
2280 // Drizzle has no authorization by default, so when no plugin is
2281 // enabled everyone is a superuser
2282 // Known authorization libraries: regex_policy, simple_user_policy
2283 // Plugins limit object visibility (dbs, tables, processes), we can
2284 // safely assume we always deal with superuser
2285 PMA_Util::cacheSet('is_' . $type . 'user', true);
2286 return PMA_Util::cacheGet('is_' . $type . 'user');
2289 if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') {
2290 // Prepare query for each user type check
2291 $query = '';
2292 if ($type === 'super') {
2293 $query = 'SELECT 1 FROM mysql.user LIMIT 1';
2294 } elseif ($type === 'create') {
2295 list($user, $host) = $this->_getCurrentUserAndHost();
2296 $query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` "
2297 . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND "
2298 . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
2299 } elseif ($type === 'grant') {
2300 list($user, $host) = $this->_getCurrentUserAndHost();
2301 $query = "SELECT 1 FROM ("
2302 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2303 . "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION "
2304 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2305 . "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION "
2306 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2307 . "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION "
2308 . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
2309 . "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t "
2310 . "WHERE `IS_GRANTABLE` = 'YES' AND "
2311 . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
2314 $is = false;
2315 $result = $GLOBALS['dbi']->tryQuery(
2316 $query,
2317 $GLOBALS['userlink'],
2318 self::QUERY_STORE
2320 if ($result) {
2321 $is = (bool) $GLOBALS['dbi']->numRows($result);
2323 $GLOBALS['dbi']->freeResult($result);
2325 PMA_Util::cacheSet('is_' . $type . 'user', $is);
2326 } else {
2327 $is = false;
2328 $grants = $GLOBALS['dbi']->fetchResult(
2329 "SHOW GRANTS FOR CURRENT_USER();",
2330 null,
2331 null,
2332 $GLOBALS['userlink'],
2333 self::QUERY_STORE
2335 if ($grants) {
2336 foreach ($grants as $grant) {
2337 if ($type === 'create') {
2338 if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false
2339 || strpos($grant, "CREATE USER") !== false
2341 $is = true;
2342 break;
2344 } elseif ($type === 'grant') {
2345 if (strpos($grant, "WITH GRANT OPTION") !== false) {
2346 $is = true;
2347 break;
2353 PMA_Util::cacheSet('is_' . $type . 'user', $is);
2356 return PMA_Util::cacheGet('is_' . $type . 'user');
2360 * Get the current user and host
2362 * @return array array of username and hostname
2364 private function _getCurrentUserAndHost()
2366 $user = $GLOBALS['dbi']->fetchValue("SELECT CURRENT_USER();");
2367 return explode("@", $user);
2371 * Get the list of system schemas
2373 * @return array list of system schemas
2375 public function getSystemSchemas()
2377 $schemas = array(
2378 'information_schema', 'performance_schema', 'data_dictionary', 'mysql'
2380 $systemSchemas = array();
2381 foreach ($schemas as $schema) {
2382 if ($this->isSystemSchema($schema, true)) {
2383 $systemSchemas[] = $schema;
2386 return $systemSchemas;
2390 * Checks whether given schema is a system schema: information_schema
2391 * (MySQL and Drizzle) or data_dictionary (Drizzle)
2393 * @param string $schema_name Name of schema (database) to test
2394 * @param bool $testForMysqlSchema Whether 'mysql' schema should
2395 * be treated the same as IS and DD
2397 * @return bool
2399 public function isSystemSchema($schema_name, $testForMysqlSchema = false)
2401 if (!defined("PMA_DRIZZLE")) {
2402 define("PMA_DRIZZLE", false);
2405 return strtolower($schema_name) == 'information_schema'
2406 || (!PMA_DRIZZLE
2407 && strtolower($schema_name) == 'performance_schema')
2408 || (PMA_DRIZZLE
2409 && strtolower($schema_name) == 'data_dictionary')
2410 || ($testForMysqlSchema && !PMA_DRIZZLE && $schema_name == 'mysql');
2414 * connects to the database server
2416 * @param string $user user name
2417 * @param string $password user password
2418 * @param bool $is_controluser whether this is a control user connection
2419 * @param array $server host/port/socket/persistent
2420 * @param bool $auxiliary_connection (when true, don't go back to login if
2421 * connection fails)
2423 * @return mixed false on error or a connection object on success
2425 public function connect(
2426 $user, $password, $is_controluser = false, $server = null,
2427 $auxiliary_connection = false
2429 $result = $this->_extension->connect(
2430 $user, $password, $is_controluser, $server, $auxiliary_connection
2433 if ($result) {
2434 if (! $auxiliary_connection && ! $is_controluser) {
2435 $GLOBALS['dbi']->postConnect($result);
2437 return $result;
2440 if ($is_controluser) {
2441 trigger_error(
2443 'Connection for controluser as defined in your '
2444 . 'configuration failed.'
2446 E_USER_WARNING
2448 return false;
2451 // we could be calling $GLOBALS['dbi']->connect() to connect to another
2452 // server, for example in the Synchronize feature, so do not
2453 // go back to main login if it fails
2454 if ($auxiliary_connection) {
2455 return false;
2458 PMA_logUser($user, 'mysql-denied');
2459 $GLOBALS['auth_plugin']->authFails();
2461 return $result;
2465 * selects given database
2467 * @param string $dbname database name to select
2468 * @param object $link connection object
2470 * @return boolean
2472 public function selectDb($dbname, $link = null)
2474 $link = $this->getLink($link);
2475 if ($link === false) {
2476 return false;
2478 return $this->_extension->selectDb($dbname, $link);
2482 * returns array of rows with associative and numeric keys from $result
2484 * @param object $result result set identifier
2486 * @return array
2488 public function fetchArray($result)
2490 return $this->_extension->fetchArray($result);
2494 * returns array of rows with associative keys from $result
2496 * @param object $result result set identifier
2498 * @return array
2500 public function fetchAssoc($result)
2502 return $this->_extension->fetchAssoc($result);
2506 * returns array of rows with numeric keys from $result
2508 * @param object $result result set identifier
2510 * @return array
2512 public function fetchRow($result)
2514 return $this->_extension->fetchRow($result);
2518 * Adjusts the result pointer to an arbitrary row in the result
2520 * @param object $result database result
2521 * @param integer $offset offset to seek
2523 * @return bool true on success, false on failure
2525 public function dataSeek($result, $offset)
2527 return $this->_extension->dataSeek($result, $offset);
2531 * Frees memory associated with the result
2533 * @param object $result database result
2535 * @return void
2537 public function freeResult($result)
2539 $this->_extension->freeResult($result);
2543 * Check if there are any more query results from a multi query
2545 * @param object $link the connection object
2547 * @return bool true or false
2549 public function moreResults($link = null)
2551 $link = $this->getLink($link);
2552 if ($link === false) {
2553 return false;
2555 return $this->_extension->moreResults($link);
2559 * Prepare next result from multi_query
2561 * @param object $link the connection object
2563 * @return bool true or false
2565 public function nextResult($link = null)
2567 $link = $this->getLink($link);
2568 if ($link === false) {
2569 return false;
2571 return $this->_extension->nextResult($link);
2575 * Store the result returned from multi query
2577 * @param object $link the connection object
2579 * @return mixed false when empty results / result set when not empty
2581 public function storeResult($link = null)
2583 $link = $this->getLink($link);
2584 if ($link === false) {
2585 return false;
2587 return $this->_extension->storeResult($link);
2591 * Returns a string representing the type of connection used
2593 * @param object $link mysql link
2595 * @return string type of connection used
2597 public function getHostInfo($link = null)
2599 $link = $this->getLink($link);
2600 if ($link === false) {
2601 return false;
2603 return $this->_extension->getHostInfo($link);
2607 * Returns the version of the MySQL protocol used
2609 * @param object $link mysql link
2611 * @return integer version of the MySQL protocol used
2613 public function getProtoInfo($link = null)
2615 $link = $this->getLink($link);
2616 if ($link === false) {
2617 return false;
2619 return $this->_extension->getProtoInfo($link);
2623 * returns a string that represents the client library version
2625 * @return string MySQL client library version
2627 public function getClientInfo()
2629 return $this->_extension->getClientInfo();
2633 * returns last error message or false if no errors occurred
2635 * @param object $link connection link
2637 * @return string|bool $error or false
2639 public function getError($link = null)
2641 $link = $this->getLink($link);
2642 if ($link === false) {
2643 return false;
2645 return $this->_extension->getError($link);
2649 * returns the number of rows returned by last query
2651 * @param object $result result set identifier
2653 * @return string|int
2655 public function numRows($result)
2657 return $this->_extension->numRows($result);
2661 * returns last inserted auto_increment id for given $link
2662 * or $GLOBALS['userlink']
2664 * @param object $link the connection object
2666 * @return int|boolean
2668 public function insertId($link = null)
2670 $link = $this->getLink($link);
2671 if ($link === false) {
2672 return false;
2674 // If the primary key is BIGINT we get an incorrect result
2675 // (sometimes negative, sometimes positive)
2676 // and in the present function we don't know if the PK is BIGINT
2677 // so better play safe and use LAST_INSERT_ID()
2679 // When no controluser is defined, using mysqli_insert_id($link)
2680 // does not always return the last insert id due to a mixup with
2681 // the tracking mechanism, but this works:
2682 return $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link);
2686 * returns the number of rows affected by last query
2688 * @param object $link the connection object
2689 * @param bool $get_from_cache whether to retrieve from cache
2691 * @return int|boolean
2693 public function affectedRows($link = null, $get_from_cache = true)
2695 $link = $this->getLink($link);
2696 if ($link === false) {
2697 return false;
2700 if ($get_from_cache) {
2701 return $GLOBALS['cached_affected_rows'];
2702 } else {
2703 return $this->_extension->affectedRows($link);
2708 * returns metainfo for fields in $result
2710 * @param object $result result set identifier
2712 * @return array meta info for fields in $result
2714 public function getFieldsMeta($result)
2716 return $this->_extension->getFieldsMeta($result);
2720 * return number of fields in given $result
2722 * @param object $result result set identifier
2724 * @return int field count
2726 public function numFields($result)
2728 return $this->_extension->numFields($result);
2732 * returns the length of the given field $i in $result
2734 * @param object $result result set identifier
2735 * @param int $i field
2737 * @return int length of field
2739 public function fieldLen($result, $i)
2741 return $this->_extension->fieldLen($result, $i);
2745 * returns name of $i. field in $result
2747 * @param object $result result set identifier
2748 * @param int $i field
2750 * @return string name of $i. field in $result
2752 public function fieldName($result, $i)
2754 return $this->_extension->fieldName($result, $i);
2758 * returns concatenated string of human readable field flags
2760 * @param object $result result set identifier
2761 * @param int $i field
2763 * @return string field flags
2765 public function fieldFlags($result, $i)
2767 return $this->_extension->fieldFlags($result, $i);
2771 * Gets server connection port
2773 * @param array|null $server host/port/socket/persistent
2775 * @return null|integer
2777 public function getServerPort($server = null)
2779 if (is_null($server)) {
2780 $server = &$GLOBALS['cfg']['Server'];
2783 if (empty($server['port'])) {
2784 return null;
2785 } else {
2786 return intval($server['port']);
2791 * Gets server connection socket
2793 * @param array|null $server host/port/socket/persistent
2795 * @return null|string
2797 public function getServerSocket($server = null)
2799 if (is_null($server)) {
2800 $server = &$GLOBALS['cfg']['Server'];
2803 if (empty($server['socket'])) {
2804 return null;
2805 } else {
2806 return $server['socket'];
2811 * Gets correct link object.
2813 * @param object $link optional database link to use
2815 * @return object|boolean
2817 public function getLink($link = null)
2819 if ( ! is_null($link) && $link !== false) {
2820 return $link;
2823 if (isset($GLOBALS['userlink']) && !is_null($GLOBALS['userlink'])) {
2824 return $GLOBALS['userlink'];
2825 } else {
2826 return false;
2831 * Checks if this database server is running on Amazon RDS.
2833 * @return boolean
2835 public function isAmazonRds()
2837 if (PMA_Util::cacheExists('is_amazon_rds')) {
2838 return PMA_Util::cacheGet('is_amazon_rds');
2840 $sql = 'SELECT @@basedir';
2841 $result = $this->fetchResult($sql);
2842 $rds = ($result[0] == '/rdsdbbin/mysql/');
2843 PMA_Util::cacheSet('is_amazon_rds', $rds);
2845 return $rds;
2849 * Gets SQL for killing a process.
2851 * @param int $process Process ID
2853 * @return string
2855 public function getKillQuery($process)
2857 if ($this->isAmazonRds()) {
2858 return 'CALL mysql.rds_kill(' . $process . ');';
2859 } else {
2860 return 'KILL ' . $process . ';';