DatabaseInterface.php 104 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Main interface for database interactions
  5. *
  6. * @package PhpMyAdmin-DBI
  7. */
  8. namespace PhpMyAdmin;
  9. use PhpMyAdmin\Core;
  10. use PhpMyAdmin\Database\DatabaseList;
  11. use PhpMyAdmin\Dbi\DbiExtension;
  12. use PhpMyAdmin\Dbi\DbiDummy;
  13. use PhpMyAdmin\Dbi\DbiMysql;
  14. use PhpMyAdmin\Dbi\DbiMysqli;
  15. use PhpMyAdmin\Di\Container;
  16. use PhpMyAdmin\Error;
  17. use PhpMyAdmin\Index;
  18. use PhpMyAdmin\LanguageManager;
  19. use PhpMyAdmin\Relation;
  20. use PhpMyAdmin\SystemDatabase;
  21. use PhpMyAdmin\Table;
  22. use PhpMyAdmin\Types;
  23. use PhpMyAdmin\Tracker;
  24. use PhpMyAdmin\Url;
  25. use PhpMyAdmin\Util;
  26. /**
  27. * Main interface for database interactions
  28. *
  29. * @package PhpMyAdmin-DBI
  30. */
  31. class DatabaseInterface
  32. {
  33. /**
  34. * Force STORE_RESULT method, ignored by classic MySQL.
  35. */
  36. const QUERY_STORE = 1;
  37. /**
  38. * Do not read whole query.
  39. */
  40. const QUERY_UNBUFFERED = 2;
  41. /**
  42. * Get session variable.
  43. */
  44. const GETVAR_SESSION = 1;
  45. /**
  46. * Get global variable.
  47. */
  48. const GETVAR_GLOBAL = 2;
  49. /**
  50. * User connection.
  51. */
  52. const CONNECT_USER = 0x100;
  53. /**
  54. * Control user connection.
  55. */
  56. const CONNECT_CONTROL = 0x101;
  57. /**
  58. * Auxiliary connection.
  59. *
  60. * Used for example for replication setup.
  61. */
  62. const CONNECT_AUXILIARY = 0x102;
  63. /**
  64. * @var DbiExtension
  65. */
  66. private $_extension;
  67. /**
  68. * Opened database links
  69. *
  70. * @var array
  71. */
  72. private $_links;
  73. /**
  74. * @var array Table data cache
  75. */
  76. private $_table_cache;
  77. /**
  78. * @var array Current user and host cache
  79. */
  80. private $_current_user;
  81. /**
  82. * @var null|string lower_case_table_names value cache
  83. */
  84. private $_lower_case_table_names = null;
  85. /**
  86. * @var boolean Whether connection is MariaDB
  87. */
  88. private $_is_mariadb = false;
  89. /**
  90. * @var boolean Whether connection is Percona
  91. */
  92. private $_is_percona = false;
  93. /**
  94. * @var integer Server version as number
  95. */
  96. private $_version_int = 55000;
  97. /**
  98. * @var string Server version
  99. */
  100. private $_version_str = '5.50.0';
  101. /**
  102. * @var string Server version comment
  103. */
  104. private $_version_comment = '';
  105. /**
  106. * @var Types MySQL types data
  107. */
  108. public $types;
  109. /**
  110. * @var Relation $relation
  111. */
  112. private $relation;
  113. /**
  114. * Constructor
  115. *
  116. * @param DbiExtension $ext Object to be used for database queries
  117. */
  118. public function __construct($ext)
  119. {
  120. $this->_extension = $ext;
  121. $this->_links = array();
  122. if (defined('TESTSUITE')) {
  123. $this->_links[DatabaseInterface::CONNECT_USER] = 1;
  124. $this->_links[DatabaseInterface::CONNECT_CONTROL] = 2;
  125. }
  126. $this->_table_cache = array();
  127. $this->_current_user = array();
  128. $this->types = new Types($this);
  129. $this->relation = new Relation();
  130. }
  131. /**
  132. * Checks whether database extension is loaded
  133. *
  134. * @param string $extension mysql extension to check
  135. *
  136. * @return bool
  137. */
  138. public static function checkDbExtension($extension = 'mysql')
  139. {
  140. return function_exists($extension . '_connect');
  141. }
  142. /**
  143. * runs a query
  144. *
  145. * @param string $query SQL query to execute
  146. * @param mixed $link optional database link to use
  147. * @param int $options optional query options
  148. * @param bool $cache_affected_rows whether to cache affected rows
  149. *
  150. * @return mixed
  151. */
  152. public function query($query, $link = DatabaseInterface::CONNECT_USER, $options = 0,
  153. $cache_affected_rows = true
  154. ) {
  155. $res = $this->tryQuery($query, $link, $options, $cache_affected_rows)
  156. or Util::mysqlDie($this->getError($link), $query);
  157. return $res;
  158. }
  159. /**
  160. * Get a cached value from table cache.
  161. *
  162. * @param array $contentPath Array of the name of the target value
  163. * @param mixed $default Return value on cache miss
  164. *
  165. * @return mixed cached value or default
  166. */
  167. public function getCachedTableContent(array $contentPath, $default = null)
  168. {
  169. return Util::getValueByKey($this->_table_cache, $contentPath, $default);
  170. }
  171. /**
  172. * Set an item in table cache using dot notation.
  173. *
  174. * @param array $contentPath Array with the target path
  175. * @param mixed $value Target value
  176. *
  177. * @return void
  178. */
  179. public function cacheTableContent(array $contentPath, $value)
  180. {
  181. $loc = &$this->_table_cache;
  182. if (!isset($contentPath)) {
  183. $loc = $value;
  184. return;
  185. }
  186. while (count($contentPath) > 1) {
  187. $key = array_shift($contentPath);
  188. // If the key doesn't exist at this depth, we will just create an empty
  189. // array to hold the next value, allowing us to create the arrays to hold
  190. // final values at the correct depth. Then we'll keep digging into the
  191. // array.
  192. if (!isset($loc[$key]) || !is_array($loc[$key])) {
  193. $loc[$key] = array();
  194. }
  195. $loc = &$loc[$key];
  196. }
  197. $loc[array_shift($contentPath)] = $value;
  198. }
  199. /**
  200. * Clear the table cache.
  201. *
  202. * @return void
  203. */
  204. public function clearTableCache()
  205. {
  206. $this->_table_cache = array();
  207. }
  208. /**
  209. * Caches table data so Table does not require to issue
  210. * SHOW TABLE STATUS again
  211. *
  212. * @param array $tables information for tables of some databases
  213. * @param string $table table name
  214. *
  215. * @return void
  216. */
  217. private function _cacheTableData(array $tables, $table)
  218. {
  219. // Note: I don't see why we would need array_merge_recursive() here,
  220. // as it creates double entries for the same table (for example a double
  221. // entry for Comment when changing the storage engine in Operations)
  222. // Note 2: Instead of array_merge(), simply use the + operator because
  223. // array_merge() renumbers numeric keys starting with 0, therefore
  224. // we would lose a db name that consists only of numbers
  225. foreach ($tables as $one_database => $its_tables) {
  226. if (isset($this->_table_cache[$one_database])) {
  227. // the + operator does not do the intended effect
  228. // when the cache for one table already exists
  229. if ($table
  230. && isset($this->_table_cache[$one_database][$table])
  231. ) {
  232. unset($this->_table_cache[$one_database][$table]);
  233. }
  234. $this->_table_cache[$one_database]
  235. = $this->_table_cache[$one_database] + $tables[$one_database];
  236. } else {
  237. $this->_table_cache[$one_database] = $tables[$one_database];
  238. }
  239. }
  240. }
  241. /**
  242. * Stores query data into session data for debugging purposes
  243. *
  244. * @param string $query Query text
  245. * @param integer $link link type
  246. * @param object|boolean $result Query result
  247. * @param integer $time Time to execute query
  248. *
  249. * @return void
  250. */
  251. private function _dbgQuery($query, $link, $result, $time)
  252. {
  253. $dbgInfo = array();
  254. $error_message = $this->getError($link);
  255. if ($result == false && is_string($error_message)) {
  256. $dbgInfo['error']
  257. = '<span style="color:red">'
  258. . htmlspecialchars($error_message) . '</span>';
  259. }
  260. $dbgInfo['query'] = htmlspecialchars($query);
  261. $dbgInfo['time'] = $time;
  262. // Get and slightly format backtrace, this is used
  263. // in the javascript console.
  264. // Strip call to _dbgQuery
  265. $dbgInfo['trace'] = Error::processBacktrace(
  266. array_slice(debug_backtrace(), 1)
  267. );
  268. $dbgInfo['hash'] = md5($query);
  269. $_SESSION['debug']['queries'][] = $dbgInfo;
  270. }
  271. /**
  272. * runs a query and returns the result
  273. *
  274. * @param string $query query to run
  275. * @param integer $link link type
  276. * @param integer $options query options
  277. * @param bool $cache_affected_rows whether to cache affected row
  278. *
  279. * @return mixed
  280. */
  281. public function tryQuery($query, $link = DatabaseInterface::CONNECT_USER, $options = 0,
  282. $cache_affected_rows = true
  283. ) {
  284. $debug = $GLOBALS['cfg']['DBG']['sql'];
  285. if (! isset($this->_links[$link])) {
  286. return false;
  287. }
  288. if ($debug) {
  289. $time = microtime(true);
  290. }
  291. $result = $this->_extension->realQuery($query, $this->_links[$link], $options);
  292. if ($cache_affected_rows) {
  293. $GLOBALS['cached_affected_rows'] = $this->affectedRows($link, false);
  294. }
  295. if ($debug) {
  296. $time = microtime(true) - $time;
  297. $this->_dbgQuery($query, $link, $result, $time);
  298. if ($GLOBALS['cfg']['DBG']['sqllog']) {
  299. if ($options & DatabaseInterface::QUERY_STORE == DatabaseInterface::QUERY_STORE) {
  300. $tmp = $this->_extension->realQuery('
  301. SHOW COUNT(*) WARNINGS', $this->_links[$link], DatabaseInterface::QUERY_STORE
  302. );
  303. $warnings = $this->fetchRow($tmp);
  304. } else {
  305. $warnings = 0;
  306. }
  307. openlog('phpMyAdmin', LOG_NDELAY | LOG_PID, LOG_USER);
  308. syslog(
  309. LOG_INFO,
  310. 'SQL[' . basename($_SERVER['SCRIPT_NAME']) . ']: '
  311. . sprintf('%0.3f', $time) . '(W:' . $warnings[0] . ') > ' . $query
  312. );
  313. closelog();
  314. }
  315. }
  316. if ($result !== false && Tracker::isActive()) {
  317. Tracker::handleQuery($query);
  318. }
  319. return $result;
  320. }
  321. /**
  322. * Run multi query statement and return results
  323. *
  324. * @param string $multi_query multi query statement to execute
  325. * @param mysqli $link mysqli object
  326. *
  327. * @return mysqli_result collection | boolean(false)
  328. */
  329. public function tryMultiQuery($multi_query = '', $link = DatabaseInterface::CONNECT_USER)
  330. {
  331. if (! isset($this->_links[$link])) {
  332. return false;
  333. }
  334. return $this->_extension->realMultiQuery($this->_links[$link], $multi_query);
  335. }
  336. /**
  337. * returns array with table names for given db
  338. *
  339. * @param string $database name of database
  340. * @param mixed $link mysql link resource|object
  341. *
  342. * @return array tables names
  343. */
  344. public function getTables($database, $link = DatabaseInterface::CONNECT_USER)
  345. {
  346. $tables = $this->fetchResult(
  347. 'SHOW TABLES FROM ' . Util::backquote($database) . ';',
  348. null,
  349. 0,
  350. $link,
  351. self::QUERY_STORE
  352. );
  353. if ($GLOBALS['cfg']['NaturalOrder']) {
  354. usort($tables, 'strnatcasecmp');
  355. }
  356. return $tables;
  357. }
  358. /**
  359. * returns
  360. *
  361. * @param string $database name of database
  362. * @param array $tables list of tables to search for for relations
  363. * @param int $link mysql link resource|object
  364. *
  365. * @return array array of found foreign keys
  366. */
  367. public function getForeignKeyConstrains($database, array $tables, $link = DatabaseInterface::CONNECT_USER)
  368. {
  369. $tablesListForQuery = '';
  370. foreach($tables as $table){
  371. $tablesListForQuery .= "'" . $this->escapeString($table) . "',";
  372. }
  373. $tablesListForQuery = rtrim($tablesListForQuery, ',');
  374. $foreignKeyConstrains = $this->fetchResult(
  375. "SELECT"
  376. . " TABLE_NAME,"
  377. . " COLUMN_NAME,"
  378. . " REFERENCED_TABLE_NAME,"
  379. . " REFERENCED_COLUMN_NAME"
  380. . " FROM information_schema.key_column_usage"
  381. . " WHERE referenced_table_name IS NOT NULL"
  382. . " AND TABLE_SCHEMA = '" . $this->escapeString($database) . "'"
  383. . " AND TABLE_NAME IN (" . $tablesListForQuery . ")"
  384. . " AND REFERENCED_TABLE_NAME IN (" . $tablesListForQuery . ");",
  385. null,
  386. null,
  387. $link,
  388. self::QUERY_STORE
  389. );
  390. return $foreignKeyConstrains;
  391. }
  392. /**
  393. * returns a segment of the SQL WHERE clause regarding table name and type
  394. *
  395. * @param array|string $table table(s)
  396. * @param boolean $tbl_is_group $table is a table group
  397. * @param string $table_type whether table or view
  398. *
  399. * @return string a segment of the WHERE clause
  400. */
  401. private function _getTableCondition($table, $tbl_is_group, $table_type)
  402. {
  403. // get table information from information_schema
  404. if ($table) {
  405. if (is_array($table)) {
  406. $sql_where_table = 'AND t.`TABLE_NAME` '
  407. . Util::getCollateForIS() . ' IN (\''
  408. . implode(
  409. '\', \'',
  410. array_map(
  411. array($this, 'escapeString'),
  412. $table
  413. )
  414. )
  415. . '\')';
  416. } elseif (true === $tbl_is_group) {
  417. $sql_where_table = 'AND t.`TABLE_NAME` LIKE \''
  418. . Util::escapeMysqlWildcards(
  419. $GLOBALS['dbi']->escapeString($table)
  420. )
  421. . '%\'';
  422. } else {
  423. $sql_where_table = 'AND t.`TABLE_NAME` '
  424. . Util::getCollateForIS() . ' = \''
  425. . $GLOBALS['dbi']->escapeString($table) . '\'';
  426. }
  427. } else {
  428. $sql_where_table = '';
  429. }
  430. if ($table_type) {
  431. if ($table_type == 'view') {
  432. $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE TABLE'";
  433. } elseif ($table_type == 'table') {
  434. $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE TABLE'";
  435. }
  436. }
  437. return $sql_where_table;
  438. }
  439. /**
  440. * returns the beginning of the SQL statement to fetch the list of tables
  441. *
  442. * @param string[] $this_databases databases to list
  443. * @param string $sql_where_table additional condition
  444. *
  445. * @return string the SQL statement
  446. */
  447. private function _getSqlForTablesFull($this_databases, $sql_where_table)
  448. {
  449. $sql = '
  450. SELECT *,
  451. `TABLE_SCHEMA` AS `Db`,
  452. `TABLE_NAME` AS `Name`,
  453. `TABLE_TYPE` AS `TABLE_TYPE`,
  454. `ENGINE` AS `Engine`,
  455. `ENGINE` AS `Type`,
  456. `VERSION` AS `Version`,
  457. `ROW_FORMAT` AS `Row_format`,
  458. `TABLE_ROWS` AS `Rows`,
  459. `AVG_ROW_LENGTH` AS `Avg_row_length`,
  460. `DATA_LENGTH` AS `Data_length`,
  461. `MAX_DATA_LENGTH` AS `Max_data_length`,
  462. `INDEX_LENGTH` AS `Index_length`,
  463. `DATA_FREE` AS `Data_free`,
  464. `AUTO_INCREMENT` AS `Auto_increment`,
  465. `CREATE_TIME` AS `Create_time`,
  466. `UPDATE_TIME` AS `Update_time`,
  467. `CHECK_TIME` AS `Check_time`,
  468. `TABLE_COLLATION` AS `Collation`,
  469. `CHECKSUM` AS `Checksum`,
  470. `CREATE_OPTIONS` AS `Create_options`,
  471. `TABLE_COMMENT` AS `Comment`
  472. FROM `information_schema`.`TABLES` t
  473. WHERE `TABLE_SCHEMA` ' . Util::getCollateForIS() . '
  474. IN (\'' . implode("', '", $this_databases) . '\')
  475. ' . $sql_where_table;
  476. return $sql;
  477. }
  478. /**
  479. * returns array of all tables in given db or dbs
  480. * this function expects unquoted names:
  481. * RIGHT: my_database
  482. * WRONG: `my_database`
  483. * WRONG: my\_database
  484. * if $tbl_is_group is true, $table is used as filter for table names
  485. *
  486. * <code>
  487. * $GLOBALS['dbi']->getTablesFull('my_database');
  488. * $GLOBALS['dbi']->getTablesFull('my_database', 'my_table'));
  489. * $GLOBALS['dbi']->getTablesFull('my_database', 'my_tables_', true));
  490. * </code>
  491. *
  492. * @param string $database database
  493. * @param string|array $table table name(s)
  494. * @param boolean $tbl_is_group $table is a table group
  495. * @param integer $limit_offset zero-based offset for the count
  496. * @param boolean|integer $limit_count number of tables to return
  497. * @param string $sort_by table attribute to sort by
  498. * @param string $sort_order direction to sort (ASC or DESC)
  499. * @param string $table_type whether table or view
  500. * @param integer $link link type
  501. *
  502. * @todo move into Table
  503. *
  504. * @return array list of tables in given db(s)
  505. */
  506. public function getTablesFull($database, $table = '',
  507. $tbl_is_group = false, $limit_offset = 0,
  508. $limit_count = false, $sort_by = 'Name', $sort_order = 'ASC',
  509. $table_type = null, $link = DatabaseInterface::CONNECT_USER
  510. ) {
  511. if (true === $limit_count) {
  512. $limit_count = $GLOBALS['cfg']['MaxTableList'];
  513. }
  514. // prepare and check parameters
  515. if (! is_array($database)) {
  516. $databases = array($database);
  517. } else {
  518. $databases = $database;
  519. }
  520. $tables = array();
  521. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  522. $sql_where_table = $this->_getTableCondition(
  523. $table, $tbl_is_group, $table_type
  524. );
  525. // for PMA bc:
  526. // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME`
  527. //
  528. // on non-Windows servers,
  529. // added BINARY in the WHERE clause to force a case sensitive
  530. // comparison (if we are looking for the db Aa we don't want
  531. // to find the db aa)
  532. $this_databases = array_map(
  533. array($this, 'escapeString'),
  534. $databases
  535. );
  536. $sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table);
  537. // Sort the tables
  538. $sql .= " ORDER BY $sort_by $sort_order";
  539. if ($limit_count) {
  540. $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
  541. }
  542. $tables = $this->fetchResult(
  543. $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link
  544. );
  545. if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
  546. // here, the array's first key is by schema name
  547. foreach ($tables as $one_database_name => $one_database_tables) {
  548. uksort($one_database_tables, 'strnatcasecmp');
  549. if ($sort_order == 'DESC') {
  550. $one_database_tables = array_reverse($one_database_tables);
  551. }
  552. $tables[$one_database_name] = $one_database_tables;
  553. }
  554. } elseif ($sort_by == 'Data_length') {
  555. // Size = Data_length + Index_length
  556. foreach ($tables as $one_database_name => $one_database_tables) {
  557. uasort(
  558. $one_database_tables,
  559. function ($a, $b) {
  560. $aLength = $a['Data_length'] + $a['Index_length'];
  561. $bLength = $b['Data_length'] + $b['Index_length'];
  562. return ($aLength == $bLength)
  563. ? 0
  564. : ($aLength < $bLength) ? -1 : 1;
  565. }
  566. );
  567. if ($sort_order == 'DESC') {
  568. $one_database_tables = array_reverse($one_database_tables);
  569. }
  570. $tables[$one_database_name] = $one_database_tables;
  571. }
  572. }
  573. } // end (get information from table schema)
  574. // If permissions are wrong on even one database directory,
  575. // information_schema does not return any table info for any database
  576. // this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002
  577. if (empty($tables)) {
  578. foreach ($databases as $each_database) {
  579. if ($table || (true === $tbl_is_group) || ! empty($table_type)) {
  580. $sql = 'SHOW TABLE STATUS FROM '
  581. . Util::backquote($each_database)
  582. . ' WHERE';
  583. $needAnd = false;
  584. if ($table || (true === $tbl_is_group)) {
  585. if (is_array($table)) {
  586. $sql .= ' `Name` IN (\''
  587. . implode(
  588. '\', \'',
  589. array_map(
  590. array($this, 'escapeString'),
  591. $table,
  592. $link
  593. )
  594. ) . '\')';
  595. } else {
  596. $sql .= " `Name` LIKE '"
  597. . Util::escapeMysqlWildcards(
  598. $this->escapeString($table, $link)
  599. )
  600. . "%'";
  601. }
  602. $needAnd = true;
  603. }
  604. if (! empty($table_type)) {
  605. if ($needAnd) {
  606. $sql .= " AND";
  607. }
  608. if ($table_type == 'view') {
  609. $sql .= " `Comment` = 'VIEW'";
  610. } elseif ($table_type == 'table') {
  611. $sql .= " `Comment` != 'VIEW'";
  612. }
  613. }
  614. } else {
  615. $sql = 'SHOW TABLE STATUS FROM '
  616. . Util::backquote($each_database);
  617. }
  618. $each_tables = $this->fetchResult($sql, 'Name', null, $link);
  619. // Sort naturally if the config allows it and we're sorting
  620. // the Name column.
  621. if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
  622. uksort($each_tables, 'strnatcasecmp');
  623. if ($sort_order == 'DESC') {
  624. $each_tables = array_reverse($each_tables);
  625. }
  626. } else {
  627. // Prepare to sort by creating array of the selected sort
  628. // value to pass to array_multisort
  629. // Size = Data_length + Index_length
  630. if ($sort_by == 'Data_length') {
  631. foreach ($each_tables as $table_name => $table_data) {
  632. ${$sort_by}[$table_name] = strtolower(
  633. $table_data['Data_length']
  634. + $table_data['Index_length']
  635. );
  636. }
  637. } else {
  638. foreach ($each_tables as $table_name => $table_data) {
  639. ${$sort_by}[$table_name]
  640. = strtolower($table_data[$sort_by]);
  641. }
  642. }
  643. if (! empty($$sort_by)) {
  644. if ($sort_order == 'DESC') {
  645. array_multisort($$sort_by, SORT_DESC, $each_tables);
  646. } else {
  647. array_multisort($$sort_by, SORT_ASC, $each_tables);
  648. }
  649. }
  650. // cleanup the temporary sort array
  651. unset($$sort_by);
  652. }
  653. if ($limit_count) {
  654. $each_tables = array_slice(
  655. $each_tables, $limit_offset, $limit_count
  656. );
  657. }
  658. foreach ($each_tables as $table_name => $each_table) {
  659. if (! isset($each_tables[$table_name]['Type'])
  660. && isset($each_tables[$table_name]['Engine'])
  661. ) {
  662. // pma BC, same parts of PMA still uses 'Type'
  663. $each_tables[$table_name]['Type']
  664. =& $each_tables[$table_name]['Engine'];
  665. } elseif (! isset($each_tables[$table_name]['Engine'])
  666. && isset($each_tables[$table_name]['Type'])
  667. ) {
  668. // old MySQL reports Type, newer MySQL reports Engine
  669. $each_tables[$table_name]['Engine']
  670. =& $each_tables[$table_name]['Type'];
  671. }
  672. // Compatibility with INFORMATION_SCHEMA output
  673. $each_tables[$table_name]['TABLE_SCHEMA']
  674. = $each_database;
  675. $each_tables[$table_name]['TABLE_NAME']
  676. =& $each_tables[$table_name]['Name'];
  677. $each_tables[$table_name]['ENGINE']
  678. =& $each_tables[$table_name]['Engine'];
  679. $each_tables[$table_name]['VERSION']
  680. =& $each_tables[$table_name]['Version'];
  681. $each_tables[$table_name]['ROW_FORMAT']
  682. =& $each_tables[$table_name]['Row_format'];
  683. $each_tables[$table_name]['TABLE_ROWS']
  684. =& $each_tables[$table_name]['Rows'];
  685. $each_tables[$table_name]['AVG_ROW_LENGTH']
  686. =& $each_tables[$table_name]['Avg_row_length'];
  687. $each_tables[$table_name]['DATA_LENGTH']
  688. =& $each_tables[$table_name]['Data_length'];
  689. $each_tables[$table_name]['MAX_DATA_LENGTH']
  690. =& $each_tables[$table_name]['Max_data_length'];
  691. $each_tables[$table_name]['INDEX_LENGTH']
  692. =& $each_tables[$table_name]['Index_length'];
  693. $each_tables[$table_name]['DATA_FREE']
  694. =& $each_tables[$table_name]['Data_free'];
  695. $each_tables[$table_name]['AUTO_INCREMENT']
  696. =& $each_tables[$table_name]['Auto_increment'];
  697. $each_tables[$table_name]['CREATE_TIME']
  698. =& $each_tables[$table_name]['Create_time'];
  699. $each_tables[$table_name]['UPDATE_TIME']
  700. =& $each_tables[$table_name]['Update_time'];
  701. $each_tables[$table_name]['CHECK_TIME']
  702. =& $each_tables[$table_name]['Check_time'];
  703. $each_tables[$table_name]['TABLE_COLLATION']
  704. =& $each_tables[$table_name]['Collation'];
  705. $each_tables[$table_name]['CHECKSUM']
  706. =& $each_tables[$table_name]['Checksum'];
  707. $each_tables[$table_name]['CREATE_OPTIONS']
  708. =& $each_tables[$table_name]['Create_options'];
  709. $each_tables[$table_name]['TABLE_COMMENT']
  710. =& $each_tables[$table_name]['Comment'];
  711. if (strtoupper($each_tables[$table_name]['Comment']) === 'VIEW'
  712. && $each_tables[$table_name]['Engine'] == null
  713. ) {
  714. $each_tables[$table_name]['TABLE_TYPE'] = 'VIEW';
  715. } elseif ($each_database == 'information_schema') {
  716. $each_tables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW';
  717. } else {
  718. /**
  719. * @todo difference between 'TEMPORARY' and 'BASE TABLE'
  720. * but how to detect?
  721. */
  722. $each_tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE';
  723. }
  724. }
  725. $tables[$each_database] = $each_tables;
  726. }
  727. }
  728. // cache table data
  729. // so Table does not require to issue SHOW TABLE STATUS again
  730. $this->_cacheTableData($tables, $table);
  731. if (is_array($database)) {
  732. return $tables;
  733. }
  734. if (isset($tables[$database])) {
  735. return $tables[$database];
  736. }
  737. if (isset($tables[mb_strtolower($database)])) {
  738. // on windows with lower_case_table_names = 1
  739. // MySQL returns
  740. // with SHOW DATABASES or information_schema.SCHEMATA: `Test`
  741. // but information_schema.TABLES gives `test`
  742. // see https://github.com/phpmyadmin/phpmyadmin/issues/8402
  743. return $tables[mb_strtolower($database)];
  744. }
  745. return $tables;
  746. }
  747. /**
  748. * Get VIEWs in a particular database
  749. *
  750. * @param string $db Database name to look in
  751. *
  752. * @return array $views Set of VIEWs inside the database
  753. */
  754. public function getVirtualTables($db)
  755. {
  756. $tables_full = $this->getTablesFull($db);
  757. $views = array();
  758. foreach ($tables_full as $table=>$tmp) {
  759. $_table = $this->getTable($db, $table);
  760. if ($_table->isView()) {
  761. $views[] = $table;
  762. }
  763. }
  764. return $views;
  765. }
  766. /**
  767. * returns array with databases containing extended infos about them
  768. *
  769. * @param string $database database
  770. * @param boolean $force_stats retrieve stats also for MySQL < 5
  771. * @param integer $link link type
  772. * @param string $sort_by column to order by
  773. * @param string $sort_order ASC or DESC
  774. * @param integer $limit_offset starting offset for LIMIT
  775. * @param bool|int $limit_count row count for LIMIT or true
  776. * for $GLOBALS['cfg']['MaxDbList']
  777. *
  778. * @todo move into ListDatabase?
  779. *
  780. * @return array $databases
  781. */
  782. public function getDatabasesFull($database = null, $force_stats = false,
  783. $link = DatabaseInterface::CONNECT_USER, $sort_by = 'SCHEMA_NAME', $sort_order = 'ASC',
  784. $limit_offset = 0, $limit_count = false
  785. ) {
  786. $sort_order = strtoupper($sort_order);
  787. if (true === $limit_count) {
  788. $limit_count = $GLOBALS['cfg']['MaxDbList'];
  789. }
  790. $apply_limit_and_order_manual = true;
  791. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  792. /**
  793. * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT
  794. * cause MySQL does not support natural ordering,
  795. * we have to do it afterward
  796. */
  797. $limit = '';
  798. if (! $GLOBALS['cfg']['NaturalOrder']) {
  799. if ($limit_count) {
  800. $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
  801. }
  802. $apply_limit_and_order_manual = false;
  803. }
  804. // get table information from information_schema
  805. if (! empty($database)) {
  806. $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \''
  807. . $this->escapeString($database, $link) . '\'';
  808. } else {
  809. $sql_where_schema = '';
  810. }
  811. $sql = 'SELECT *,
  812. CAST(BIN_NAME AS CHAR CHARACTER SET utf8) AS SCHEMA_NAME
  813. FROM (';
  814. $sql .= 'SELECT
  815. BINARY s.SCHEMA_NAME AS BIN_NAME,
  816. s.DEFAULT_COLLATION_NAME';
  817. if ($force_stats) {
  818. $sql .= ',
  819. COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,
  820. SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS,
  821. SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH,
  822. SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH,
  823. SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH,
  824. SUM(t.DATA_LENGTH + t.INDEX_LENGTH)
  825. AS SCHEMA_LENGTH,
  826. SUM(IF(t.ENGINE <> \'InnoDB\', t.DATA_FREE, 0))
  827. AS SCHEMA_DATA_FREE';
  828. }
  829. $sql .= '
  830. FROM `information_schema`.SCHEMATA s ';
  831. if ($force_stats) {
  832. $sql .= '
  833. LEFT JOIN `information_schema`.TABLES t
  834. ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME';
  835. }
  836. $sql .= $sql_where_schema . '
  837. GROUP BY BINARY s.SCHEMA_NAME, s.DEFAULT_COLLATION_NAME
  838. ORDER BY ';
  839. if ($sort_by == 'SCHEMA_NAME'
  840. || $sort_by == 'DEFAULT_COLLATION_NAME'
  841. ) {
  842. $sql .= 'BINARY ';
  843. }
  844. $sql .= Util::backquote($sort_by)
  845. . ' ' . $sort_order
  846. . $limit;
  847. $sql .= ') a';
  848. $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link);
  849. $mysql_error = $this->getError($link);
  850. if (! count($databases) && $GLOBALS['errno']) {
  851. Util::mysqlDie($mysql_error, $sql);
  852. }
  853. // display only databases also in official database list
  854. // f.e. to apply hide_db and only_db
  855. $drops = array_diff(
  856. array_keys($databases), (array) $GLOBALS['dblist']->databases
  857. );
  858. foreach ($drops as $drop) {
  859. unset($databases[$drop]);
  860. }
  861. } else {
  862. $databases = array();
  863. foreach ($GLOBALS['dblist']->databases as $database_name) {
  864. // Compatibility with INFORMATION_SCHEMA output
  865. $databases[$database_name]['SCHEMA_NAME'] = $database_name;
  866. $databases[$database_name]['DEFAULT_COLLATION_NAME']
  867. = $this->getDbCollation($database_name);
  868. if (!$force_stats) {
  869. continue;
  870. }
  871. // get additional info about tables
  872. $databases[$database_name]['SCHEMA_TABLES'] = 0;
  873. $databases[$database_name]['SCHEMA_TABLE_ROWS'] = 0;
  874. $databases[$database_name]['SCHEMA_DATA_LENGTH'] = 0;
  875. $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] = 0;
  876. $databases[$database_name]['SCHEMA_INDEX_LENGTH'] = 0;
  877. $databases[$database_name]['SCHEMA_LENGTH'] = 0;
  878. $databases[$database_name]['SCHEMA_DATA_FREE'] = 0;
  879. $res = $this->query(
  880. 'SHOW TABLE STATUS FROM '
  881. . Util::backquote($database_name) . ';'
  882. );
  883. if ($res === false) {
  884. unset($res);
  885. continue;
  886. }
  887. while ($row = $this->fetchAssoc($res)) {
  888. $databases[$database_name]['SCHEMA_TABLES']++;
  889. $databases[$database_name]['SCHEMA_TABLE_ROWS']
  890. += $row['Rows'];
  891. $databases[$database_name]['SCHEMA_DATA_LENGTH']
  892. += $row['Data_length'];
  893. $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH']
  894. += $row['Max_data_length'];
  895. $databases[$database_name]['SCHEMA_INDEX_LENGTH']
  896. += $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'];
  902. }
  903. $databases[$database_name]['SCHEMA_LENGTH']
  904. += $row['Data_length'] + $row['Index_length'];
  905. }
  906. $this->freeResult($res);
  907. unset($res);
  908. }
  909. }
  910. /**
  911. * apply limit and order manually now
  912. * (caused by older MySQL < 5 or $GLOBALS['cfg']['NaturalOrder'])
  913. */
  914. if ($apply_limit_and_order_manual) {
  915. $GLOBALS['callback_sort_order'] = $sort_order;
  916. $GLOBALS['callback_sort_by'] = $sort_by;
  917. usort(
  918. $databases,
  919. array(self::class, '_usortComparisonCallback')
  920. );
  921. unset($GLOBALS['callback_sort_order'], $GLOBALS['callback_sort_by']);
  922. /**
  923. * now apply limit
  924. */
  925. if ($limit_count) {
  926. $databases = array_slice($databases, $limit_offset, $limit_count);
  927. }
  928. }
  929. return $databases;
  930. }
  931. /**
  932. * usort comparison callback
  933. *
  934. * @param string $a first argument to sort
  935. * @param string $b second argument to sort
  936. *
  937. * @return integer a value representing whether $a should be before $b in the
  938. * sorted array or not
  939. *
  940. * @access private
  941. */
  942. private static function _usortComparisonCallback($a, $b)
  943. {
  944. if ($GLOBALS['cfg']['NaturalOrder']) {
  945. $sorter = 'strnatcasecmp';
  946. } else {
  947. $sorter = 'strcasecmp';
  948. }
  949. /* No sorting when key is not present */
  950. if (! isset($a[$GLOBALS['callback_sort_by']])
  951. || ! isset($b[$GLOBALS['callback_sort_by']])
  952. ) {
  953. return 0;
  954. }
  955. // produces f.e.:
  956. // return -1 * strnatcasecmp($a["SCHEMA_TABLES"], $b["SCHEMA_TABLES"])
  957. return ($GLOBALS['callback_sort_order'] == 'ASC' ? 1 : -1) * $sorter(
  958. $a[$GLOBALS['callback_sort_by']], $b[$GLOBALS['callback_sort_by']]
  959. );
  960. } // end of the '_usortComparisonCallback()' method
  961. /**
  962. * returns detailed array with all columns for sql
  963. *
  964. * @param string $sql_query target SQL query to get columns
  965. * @param array $view_columns alias for columns
  966. *
  967. * @return array
  968. */
  969. public function getColumnMapFromSql($sql_query, array $view_columns = array())
  970. {
  971. $result = $this->tryQuery($sql_query);
  972. if ($result === false) {
  973. return array();
  974. }
  975. $meta = $this->getFieldsMeta(
  976. $result
  977. );
  978. $nbFields = count($meta);
  979. if ($nbFields <= 0) {
  980. return array();
  981. }
  982. $column_map = array();
  983. $nbColumns = count($view_columns);
  984. for ($i=0; $i < $nbFields; $i++) {
  985. $map = array();
  986. $map['table_name'] = $meta[$i]->table;
  987. $map['refering_column'] = $meta[$i]->name;
  988. if ($nbColumns > 1) {
  989. $map['real_column'] = $view_columns[$i];
  990. }
  991. $column_map[] = $map;
  992. }
  993. return $column_map;
  994. }
  995. /**
  996. * returns detailed array with all columns for given table in database,
  997. * or all tables/databases
  998. *
  999. * @param string $database name of database
  1000. * @param string $table name of table to retrieve columns from
  1001. * @param string $column name of specific column
  1002. * @param mixed $link mysql link resource
  1003. *
  1004. * @return array
  1005. */
  1006. public function getColumnsFull($database = null, $table = null,
  1007. $column = null, $link = DatabaseInterface::CONNECT_USER
  1008. ) {
  1009. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  1010. $sql_wheres = array();
  1011. $array_keys = array();
  1012. // get columns information from information_schema
  1013. if (null !== $database) {
  1014. $sql_wheres[] = '`TABLE_SCHEMA` = \''
  1015. . $this->escapeString($database, $link) . '\' ';
  1016. } else {
  1017. $array_keys[] = 'TABLE_SCHEMA';
  1018. }
  1019. if (null !== $table) {
  1020. $sql_wheres[] = '`TABLE_NAME` = \''
  1021. . $this->escapeString($table, $link) . '\' ';
  1022. } else {
  1023. $array_keys[] = 'TABLE_NAME';
  1024. }
  1025. if (null !== $column) {
  1026. $sql_wheres[] = '`COLUMN_NAME` = \''
  1027. . $this->escapeString($column, $link) . '\' ';
  1028. } else {
  1029. $array_keys[] = 'COLUMN_NAME';
  1030. }
  1031. // for PMA bc:
  1032. // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]`
  1033. $sql = '
  1034. SELECT *,
  1035. `COLUMN_NAME` AS `Field`,
  1036. `COLUMN_TYPE` AS `Type`,
  1037. `COLLATION_NAME` AS `Collation`,
  1038. `IS_NULLABLE` AS `Null`,
  1039. `COLUMN_KEY` AS `Key`,
  1040. `COLUMN_DEFAULT` AS `Default`,
  1041. `EXTRA` AS `Extra`,
  1042. `PRIVILEGES` AS `Privileges`,
  1043. `COLUMN_COMMENT` AS `Comment`
  1044. FROM `information_schema`.`COLUMNS`';
  1045. if (count($sql_wheres)) {
  1046. $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres);
  1047. }
  1048. return $this->fetchResult($sql, $array_keys, null, $link);
  1049. }
  1050. $columns = array();
  1051. if (null === $database) {
  1052. foreach ($GLOBALS['dblist']->databases as $database) {
  1053. $columns[$database] = $this->getColumnsFull(
  1054. $database, null, null, $link
  1055. );
  1056. }
  1057. return $columns;
  1058. } elseif (null === $table) {
  1059. $tables = $this->getTables($database);
  1060. foreach ($tables as $table) {
  1061. $columns[$table] = $this->getColumnsFull(
  1062. $database, $table, null, $link
  1063. );
  1064. }
  1065. return $columns;
  1066. }
  1067. $sql = 'SHOW FULL COLUMNS FROM '
  1068. . Util::backquote($database) . '.' . Util::backquote($table);
  1069. if (null !== $column) {
  1070. $sql .= " LIKE '" . $this->escapeString($column, $link) . "'";
  1071. }
  1072. $columns = $this->fetchResult($sql, 'Field', null, $link);
  1073. $ordinal_position = 1;
  1074. foreach ($columns as $column_name => $each_column) {
  1075. // Compatibility with INFORMATION_SCHEMA output
  1076. $columns[$column_name]['COLUMN_NAME']
  1077. =& $columns[$column_name]['Field'];
  1078. $columns[$column_name]['COLUMN_TYPE']
  1079. =& $columns[$column_name]['Type'];
  1080. $columns[$column_name]['COLLATION_NAME']
  1081. =& $columns[$column_name]['Collation'];
  1082. $columns[$column_name]['IS_NULLABLE']
  1083. =& $columns[$column_name]['Null'];
  1084. $columns[$column_name]['COLUMN_KEY']
  1085. =& $columns[$column_name]['Key'];
  1086. $columns[$column_name]['COLUMN_DEFAULT']
  1087. =& $columns[$column_name]['Default'];
  1088. $columns[$column_name]['EXTRA']
  1089. =& $columns[$column_name]['Extra'];
  1090. $columns[$column_name]['PRIVILEGES']
  1091. =& $columns[$column_name]['Privileges'];
  1092. $columns[$column_name]['COLUMN_COMMENT']
  1093. =& $columns[$column_name]['Comment'];
  1094. $columns[$column_name]['TABLE_CATALOG'] = null;
  1095. $columns[$column_name]['TABLE_SCHEMA'] = $database;
  1096. $columns[$column_name]['TABLE_NAME'] = $table;
  1097. $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position;
  1098. $columns[$column_name]['DATA_TYPE']
  1099. = substr(
  1100. $columns[$column_name]['COLUMN_TYPE'],
  1101. 0,
  1102. strpos($columns[$column_name]['COLUMN_TYPE'], '(')
  1103. );
  1104. /**
  1105. * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE
  1106. */
  1107. $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null;
  1108. /**
  1109. * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH
  1110. */
  1111. $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null;
  1112. $columns[$column_name]['NUMERIC_PRECISION'] = null;
  1113. $columns[$column_name]['NUMERIC_SCALE'] = null;
  1114. $columns[$column_name]['CHARACTER_SET_NAME']
  1115. = substr(
  1116. $columns[$column_name]['COLLATION_NAME'],
  1117. 0,
  1118. strpos($columns[$column_name]['COLLATION_NAME'], '_')
  1119. );
  1120. $ordinal_position++;
  1121. }
  1122. if (null !== $column) {
  1123. return reset($columns);
  1124. }
  1125. return $columns;
  1126. }
  1127. /**
  1128. * Returns SQL query for fetching columns for a table
  1129. *
  1130. * The 'Key' column is not calculated properly, use $GLOBALS['dbi']->getColumns()
  1131. * to get correct values.
  1132. *
  1133. * @param string $database name of database
  1134. * @param string $table name of table to retrieve columns from
  1135. * @param string $column name of column, null to show all columns
  1136. * @param boolean $full whether to return full info or only column names
  1137. *
  1138. * @see getColumns()
  1139. *
  1140. * @return string
  1141. */
  1142. public function getColumnsSql($database, $table, $column = null, $full = false)
  1143. {
  1144. $sql = 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM '
  1145. . Util::backquote($database) . '.' . Util::backquote($table)
  1146. . (($column !== null) ? "LIKE '"
  1147. . $GLOBALS['dbi']->escapeString($column) . "'" : '');
  1148. return $sql;
  1149. }
  1150. /**
  1151. * Returns descriptions of columns in given table (all or given by $column)
  1152. *
  1153. * @param string $database name of database
  1154. * @param string $table name of table to retrieve columns from
  1155. * @param string $column name of column, null to show all columns
  1156. * @param boolean $full whether to return full info or only column names
  1157. * @param integer $link link type
  1158. *
  1159. * @return array array indexed by column names or,
  1160. * if $column is given, flat array description
  1161. */
  1162. public function getColumns($database, $table, $column = null, $full = false,
  1163. $link = DatabaseInterface::CONNECT_USER
  1164. ) {
  1165. $sql = $this->getColumnsSql($database, $table, $column, $full);
  1166. $fields = $this->fetchResult($sql, 'Field', null, $link);
  1167. if (! is_array($fields) || count($fields) == 0) {
  1168. return array();
  1169. }
  1170. // Check if column is a part of multiple-column index and set its 'Key'.
  1171. $indexes = Index::getFromTable($table, $database);
  1172. foreach ($fields as $field => $field_data) {
  1173. if (!empty($field_data['Key'])) {
  1174. continue;
  1175. }
  1176. foreach ($indexes as $index) {
  1177. /** @var Index $index */
  1178. if (!$index->hasColumn($field)) {
  1179. continue;
  1180. }
  1181. $index_columns = $index->getColumns();
  1182. if ($index_columns[$field]->getSeqInIndex() > 1) {
  1183. if ($index->isUnique()) {
  1184. $fields[$field]['Key'] = 'UNI';
  1185. } else {
  1186. $fields[$field]['Key'] = 'MUL';
  1187. }
  1188. }
  1189. }
  1190. }
  1191. return ($column != null) ? array_shift($fields) : $fields;
  1192. }
  1193. /**
  1194. * Returns all column names in given table
  1195. *
  1196. * @param string $database name of database
  1197. * @param string $table name of table to retrieve columns from
  1198. * @param mixed $link mysql link resource
  1199. *
  1200. * @return null|array
  1201. */
  1202. public function getColumnNames($database, $table, $link = DatabaseInterface::CONNECT_USER)
  1203. {
  1204. $sql = $this->getColumnsSql($database, $table);
  1205. // We only need the 'Field' column which contains the table's column names
  1206. $fields = array_keys($this->fetchResult($sql, 'Field', null, $link));
  1207. if (! is_array($fields) || count($fields) == 0) {
  1208. return null;
  1209. }
  1210. return $fields;
  1211. }
  1212. /**
  1213. * Returns SQL for fetching information on table indexes (SHOW INDEXES)
  1214. *
  1215. * @param string $database name of database
  1216. * @param string $table name of the table whose indexes are to be retrieved
  1217. * @param string $where additional conditions for WHERE
  1218. *
  1219. * @return string SQL for getting indexes
  1220. */
  1221. public function getTableIndexesSql($database, $table, $where = null)
  1222. {
  1223. $sql = 'SHOW INDEXES FROM ' . Util::backquote($database) . '.'
  1224. . Util::backquote($table);
  1225. if ($where) {
  1226. $sql .= ' WHERE (' . $where . ')';
  1227. }
  1228. return $sql;
  1229. }
  1230. /**
  1231. * Returns indexes of a table
  1232. *
  1233. * @param string $database name of database
  1234. * @param string $table name of the table whose indexes are to be retrieved
  1235. * @param mixed $link mysql link resource
  1236. *
  1237. * @return array $indexes
  1238. */
  1239. public function getTableIndexes($database, $table, $link = DatabaseInterface::CONNECT_USER)
  1240. {
  1241. $sql = $this->getTableIndexesSql($database, $table);
  1242. $indexes = $this->fetchResult($sql, null, null, $link);
  1243. if (! is_array($indexes) || count($indexes) < 1) {
  1244. return array();
  1245. }
  1246. return $indexes;
  1247. }
  1248. /**
  1249. * returns value of given mysql server variable
  1250. *
  1251. * @param string $var mysql server variable name
  1252. * @param int $type DatabaseInterface::GETVAR_SESSION |
  1253. * DatabaseInterface::GETVAR_GLOBAL
  1254. * @param mixed $link mysql link resource|object
  1255. *
  1256. * @return mixed value for mysql server variable
  1257. */
  1258. public function getVariable(
  1259. $var, $type = self::GETVAR_SESSION, $link = DatabaseInterface::CONNECT_USER
  1260. ) {
  1261. switch ($type) {
  1262. case self::GETVAR_SESSION:
  1263. $modifier = ' SESSION';
  1264. break;
  1265. case self::GETVAR_GLOBAL:
  1266. $modifier = ' GLOBAL';
  1267. break;
  1268. default:
  1269. $modifier = '';
  1270. }
  1271. return $this->fetchValue(
  1272. 'SHOW' . $modifier . ' VARIABLES LIKE \'' . $var . '\';', 0, 1, $link
  1273. );
  1274. }
  1275. /**
  1276. * Sets new value for a variable if it is different from the current value
  1277. *
  1278. * @param string $var variable name
  1279. * @param string $value value to set
  1280. * @param mixed $link mysql link resource|object
  1281. *
  1282. * @return bool whether query was a successful
  1283. */
  1284. public function setVariable($var, $value, $link = DatabaseInterface::CONNECT_USER)
  1285. {
  1286. $current_value = $this->getVariable(
  1287. $var, self::GETVAR_SESSION, $link
  1288. );
  1289. if ($current_value == $value) {
  1290. return true;
  1291. }
  1292. return $this->query("SET " . $var . " = " . $value . ';', $link);
  1293. }
  1294. /**
  1295. * Convert version string to integer.
  1296. *
  1297. * @param string $version MySQL server version
  1298. *
  1299. * @return int
  1300. */
  1301. public static function versionToInt($version)
  1302. {
  1303. $match = explode('.', $version);
  1304. return (int) sprintf('%d%02d%02d', $match[0], $match[1], intval($match[2]));
  1305. }
  1306. /**
  1307. * Function called just after a connection to the MySQL database server has
  1308. * been established. It sets the connection collation, and determines the
  1309. * version of MySQL which is running.
  1310. *
  1311. * @return void
  1312. */
  1313. public function postConnect()
  1314. {
  1315. $version = $this->fetchSingleRow(
  1316. 'SELECT @@version, @@version_comment',
  1317. 'ASSOC',
  1318. DatabaseInterface::CONNECT_USER
  1319. );
  1320. if ($version) {
  1321. $this->_version_int = self::versionToInt($version['@@version']);
  1322. $this->_version_str = $version['@@version'];
  1323. $this->_version_comment = $version['@@version_comment'];
  1324. if (stripos($version['@@version'], 'mariadb') !== false) {
  1325. $this->_is_mariadb = true;
  1326. }
  1327. if (stripos($version['@@version_comment'], 'percona') !== false) {
  1328. $this->_is_percona = true;
  1329. }
  1330. }
  1331. if ($this->_version_int > 50503) {
  1332. $default_charset = 'utf8mb4';
  1333. $default_collation = 'utf8mb4_general_ci';
  1334. } else {
  1335. $default_charset = 'utf8';
  1336. $default_collation = 'utf8_general_ci';
  1337. }
  1338. $GLOBALS['collation_connection'] = $default_collation;
  1339. $GLOBALS['charset_connection'] = $default_charset;
  1340. $this->query(
  1341. "SET NAMES '$default_charset' COLLATE '$default_collation';",
  1342. DatabaseInterface::CONNECT_USER,
  1343. self::QUERY_STORE
  1344. );
  1345. /* Locale for messages */
  1346. $locale = LanguageManager::getInstance()->getCurrentLanguage()->getMySQLLocale();
  1347. if (! empty($locale)) {
  1348. $this->query(
  1349. "SET lc_messages = '" . $locale . "';",
  1350. DatabaseInterface::CONNECT_USER,
  1351. self::QUERY_STORE
  1352. );
  1353. }
  1354. // Set timezone for the session, if required.
  1355. if ($GLOBALS['cfg']['Server']['SessionTimeZone'] != '') {
  1356. $sql_query_tz = 'SET ' . Util::backquote('time_zone') . ' = '
  1357. . '\''
  1358. . $this->escapeString($GLOBALS['cfg']['Server']['SessionTimeZone'])
  1359. . '\'';
  1360. if (! $this->tryQuery($sql_query_tz)) {
  1361. $error_message_tz = sprintf(
  1362. __(
  1363. 'Unable to use timezone "%1$s" for server %2$d. '
  1364. . 'Please check your configuration setting for '
  1365. . '[em]$cfg[\'Servers\'][%3$d][\'SessionTimeZone\'][/em]. '
  1366. . 'phpMyAdmin is currently using the default time zone '
  1367. . 'of the database server.'
  1368. ),
  1369. $GLOBALS['cfg']['Server']['SessionTimeZone'],
  1370. $GLOBALS['server'],
  1371. $GLOBALS['server']
  1372. );
  1373. trigger_error($error_message_tz, E_USER_WARNING);
  1374. }
  1375. }
  1376. /* Loads closest context to this version. */
  1377. \PhpMyAdmin\SqlParser\Context::loadClosest(
  1378. ($this->_is_mariadb ? 'MariaDb' : 'MySql') . $this->_version_int
  1379. );
  1380. /**
  1381. * the DatabaseList class as a stub for the ListDatabase class
  1382. */
  1383. $GLOBALS['dblist'] = new DatabaseList();
  1384. }
  1385. /**
  1386. * Sets collation connection for user link
  1387. *
  1388. * @param string $collation collation to set
  1389. */
  1390. public function setCollation($collation)
  1391. {
  1392. $charset = $GLOBALS['charset_connection'];
  1393. /* Automatically adjust collation if not supported by server */
  1394. if ($charset == 'utf8' && strncmp('utf8mb4_', $collation, 8) == 0) {
  1395. $collation = 'utf8_' . substr($collation, 8);
  1396. }
  1397. $result = $this->tryQuery(
  1398. "SET collation_connection = '"
  1399. . $this->escapeString($collation, DatabaseInterface::CONNECT_USER)
  1400. . "';",
  1401. DatabaseInterface::CONNECT_USER,
  1402. self::QUERY_STORE
  1403. );
  1404. if ($result === false) {
  1405. trigger_error(
  1406. __('Failed to set configured collation connection!'),
  1407. E_USER_WARNING
  1408. );
  1409. } else {
  1410. $GLOBALS['collation_connection'] = $collation;
  1411. }
  1412. }
  1413. /**
  1414. * Function called just after a connection to the MySQL database server has
  1415. * been established. It sets the connection collation, and determines the
  1416. * version of MySQL which is running.
  1417. *
  1418. * @param integer $link link type
  1419. *
  1420. * @return void
  1421. */
  1422. public function postConnectControl()
  1423. {
  1424. // If Zero configuration mode enabled, check PMA tables in current db.
  1425. if ($GLOBALS['cfg']['ZeroConf'] == true) {
  1426. /**
  1427. * the DatabaseList class as a stub for the ListDatabase class
  1428. */
  1429. $GLOBALS['dblist'] = new DatabaseList();
  1430. if (strlen($GLOBALS['db'])) {
  1431. $cfgRelation = $this->relation->getRelationsParam();
  1432. if (empty($cfgRelation['db'])) {
  1433. $this->relation->fixPmaTables($GLOBALS['db'], false);
  1434. }
  1435. }
  1436. $cfgRelation = $this->relation->getRelationsParam();
  1437. if (empty($cfgRelation['db'])) {
  1438. if ($GLOBALS['dblist']->databases->exists('phpmyadmin')) {
  1439. $this->relation->fixPmaTables('phpmyadmin', false);
  1440. }
  1441. }
  1442. }
  1443. }
  1444. /**
  1445. * returns a single value from the given result or query,
  1446. * if the query or the result has more than one row or field
  1447. * the first field of the first row is returned
  1448. *
  1449. * <code>
  1450. * $sql = 'SELECT `name` FROM `user` WHERE `id` = 123';
  1451. * $user_name = $GLOBALS['dbi']->fetchValue($sql);
  1452. * // produces
  1453. * // $user_name = 'John Doe'
  1454. * </code>
  1455. *
  1456. * @param string $query The query to execute
  1457. * @param integer $row_number row to fetch the value from,
  1458. * starting at 0, with 0 being default
  1459. * @param integer|string $field field to fetch the value from,
  1460. * starting at 0, with 0 being default
  1461. * @param integer $link link type
  1462. *
  1463. * @return mixed value of first field in first row from result
  1464. * or false if not found
  1465. */
  1466. public function fetchValue($query, $row_number = 0, $field = 0, $link = DatabaseInterface::CONNECT_USER)
  1467. {
  1468. $value = false;
  1469. $result = $this->tryQuery(
  1470. $query,
  1471. $link,
  1472. self::QUERY_STORE,
  1473. false
  1474. );
  1475. if ($result === false) {
  1476. return false;
  1477. }
  1478. // return false if result is empty or false
  1479. // or requested row is larger than rows in result
  1480. if ($this->numRows($result) < ($row_number + 1)) {
  1481. return $value;
  1482. }
  1483. // if $field is an integer use non associative mysql fetch function
  1484. if (is_int($field)) {
  1485. $fetch_function = 'fetchRow';
  1486. } else {
  1487. $fetch_function = 'fetchAssoc';
  1488. }
  1489. // get requested row
  1490. for ($i = 0; $i <= $row_number; $i++) {
  1491. $row = $this->$fetch_function($result);
  1492. }
  1493. $this->freeResult($result);
  1494. // return requested field
  1495. if (isset($row[$field])) {
  1496. $value = $row[$field];
  1497. }
  1498. return $value;
  1499. }
  1500. /**
  1501. * returns only the first row from the result
  1502. *
  1503. * <code>
  1504. * $sql = 'SELECT * FROM `user` WHERE `id` = 123';
  1505. * $user = $GLOBALS['dbi']->fetchSingleRow($sql);
  1506. * // produces
  1507. * // $user = array('id' => 123, 'name' => 'John Doe')
  1508. * </code>
  1509. *
  1510. * @param string $query The query to execute
  1511. * @param string $type NUM|ASSOC|BOTH returned array should either numeric
  1512. * associative or both
  1513. * @param integer $link link type
  1514. *
  1515. * @return array|boolean first row from result
  1516. * or false if result is empty
  1517. */
  1518. public function fetchSingleRow($query, $type = 'ASSOC', $link = DatabaseInterface::CONNECT_USER)
  1519. {
  1520. $result = $this->tryQuery(
  1521. $query,
  1522. $link,
  1523. self::QUERY_STORE,
  1524. false
  1525. );
  1526. if ($result === false) {
  1527. return false;
  1528. }
  1529. // return false if result is empty or false
  1530. if (! $this->numRows($result)) {
  1531. return false;
  1532. }
  1533. switch ($type) {
  1534. case 'NUM' :
  1535. $fetch_function = 'fetchRow';
  1536. break;
  1537. case 'ASSOC' :
  1538. $fetch_function = 'fetchAssoc';
  1539. break;
  1540. case 'BOTH' :
  1541. default :
  1542. $fetch_function = 'fetchArray';
  1543. break;
  1544. }
  1545. $row = $this->$fetch_function($result);
  1546. $this->freeResult($result);
  1547. return $row;
  1548. }
  1549. /**
  1550. * Returns row or element of a row
  1551. *
  1552. * @param array $row Row to process
  1553. * @param string|null $value Which column to return
  1554. *
  1555. * @return mixed
  1556. */
  1557. private function _fetchValue(array $row, $value)
  1558. {
  1559. if (is_null($value)) {
  1560. return $row;
  1561. }
  1562. return $row[$value];
  1563. }
  1564. /**
  1565. * returns all rows in the resultset in one array
  1566. *
  1567. * <code>
  1568. * $sql = 'SELECT * FROM `user`';
  1569. * $users = $GLOBALS['dbi']->fetchResult($sql);
  1570. * // produces
  1571. * // $users[] = array('id' => 123, 'name' => 'John Doe')
  1572. *
  1573. * $sql = 'SELECT `id`, `name` FROM `user`';
  1574. * $users = $GLOBALS['dbi']->fetchResult($sql, 'id');
  1575. * // produces
  1576. * // $users['123'] = array('id' => 123, 'name' => 'John Doe')
  1577. *
  1578. * $sql = 'SELECT `id`, `name` FROM `user`';
  1579. * $users = $GLOBALS['dbi']->fetchResult($sql, 0);
  1580. * // produces
  1581. * // $users['123'] = array(0 => 123, 1 => 'John Doe')
  1582. *
  1583. * $sql = 'SELECT `id`, `name` FROM `user`';
  1584. * $users = $GLOBALS['dbi']->fetchResult($sql, 'id', 'name');
  1585. * // or
  1586. * $users = $GLOBALS['dbi']->fetchResult($sql, 0, 1);
  1587. * // produces
  1588. * // $users['123'] = 'John Doe'
  1589. *
  1590. * $sql = 'SELECT `name` FROM `user`';
  1591. * $users = $GLOBALS['dbi']->fetchResult($sql);
  1592. * // produces
  1593. * // $users[] = 'John Doe'
  1594. *
  1595. * $sql = 'SELECT `group`, `name` FROM `user`'
  1596. * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', null), 'name');
  1597. * // produces
  1598. * // $users['admin'][] = 'John Doe'
  1599. *
  1600. * $sql = 'SELECT `group`, `name` FROM `user`'
  1601. * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', 'name'), 'id');
  1602. * // produces
  1603. * // $users['admin']['John Doe'] = '123'
  1604. * </code>
  1605. *
  1606. * @param string $query query to execute
  1607. * @param string|integer|array $key field-name or offset
  1608. * used as key for array
  1609. * or array of those
  1610. * @param string|integer $value value-name or offset
  1611. * used as value for array
  1612. * @param integer $link link type
  1613. * @param integer $options query options
  1614. *
  1615. * @return array resultrows or values indexed by $key
  1616. */
  1617. public function fetchResult($query, $key = null, $value = null,
  1618. $link = DatabaseInterface::CONNECT_USER, $options = 0
  1619. ) {
  1620. $resultrows = array();
  1621. $result = $this->tryQuery($query, $link, $options, false);
  1622. // return empty array if result is empty or false
  1623. if ($result === false) {
  1624. return $resultrows;
  1625. }
  1626. $fetch_function = 'fetchAssoc';
  1627. // no nested array if only one field is in result
  1628. if (null === $key && 1 === $this->numFields($result)) {
  1629. $value = 0;
  1630. $fetch_function = 'fetchRow';
  1631. }
  1632. // if $key is an integer use non associative mysql fetch function
  1633. if (is_int($key)) {
  1634. $fetch_function = 'fetchRow';
  1635. }
  1636. if (null === $key) {
  1637. while ($row = $this->$fetch_function($result)) {
  1638. $resultrows[] = $this->_fetchValue($row, $value);
  1639. }
  1640. } else {
  1641. if (is_array($key)) {
  1642. while ($row = $this->$fetch_function($result)) {
  1643. $result_target =& $resultrows;
  1644. foreach ($key as $key_index) {
  1645. if (null === $key_index) {
  1646. $result_target =& $result_target[];
  1647. continue;
  1648. }
  1649. if (! isset($result_target[$row[$key_index]])) {
  1650. $result_target[$row[$key_index]] = array();
  1651. }
  1652. $result_target =& $result_target[$row[$key_index]];
  1653. }
  1654. $result_target = $this->_fetchValue($row, $value);
  1655. }
  1656. } else {
  1657. while ($row = $this->$fetch_function($result)) {
  1658. $resultrows[$row[$key]] = $this->_fetchValue($row, $value);
  1659. }
  1660. }
  1661. }
  1662. $this->freeResult($result);
  1663. return $resultrows;
  1664. }
  1665. /**
  1666. * Get supported SQL compatibility modes
  1667. *
  1668. * @return array supported SQL compatibility modes
  1669. */
  1670. public function getCompatibilities()
  1671. {
  1672. $compats = array('NONE');
  1673. $compats[] = 'ANSI';
  1674. $compats[] = 'DB2';
  1675. $compats[] = 'MAXDB';
  1676. $compats[] = 'MYSQL323';
  1677. $compats[] = 'MYSQL40';
  1678. $compats[] = 'MSSQL';
  1679. $compats[] = 'ORACLE';
  1680. // removed; in MySQL 5.0.33, this produces exports that
  1681. // can't be read by POSTGRESQL (see our bug #1596328)
  1682. //$compats[] = 'POSTGRESQL';
  1683. $compats[] = 'TRADITIONAL';
  1684. return $compats;
  1685. }
  1686. /**
  1687. * returns warnings for last query
  1688. *
  1689. * @param integer $link link type
  1690. *
  1691. * @return array warnings
  1692. */
  1693. public function getWarnings($link = DatabaseInterface::CONNECT_USER)
  1694. {
  1695. return $this->fetchResult('SHOW WARNINGS', null, null, $link);
  1696. }
  1697. /**
  1698. * returns an array of PROCEDURE or FUNCTION names for a db
  1699. *
  1700. * @param string $db db name
  1701. * @param string $which PROCEDURE | FUNCTION
  1702. * @param integer $link link type
  1703. *
  1704. * @return array the procedure names or function names
  1705. */
  1706. public function getProceduresOrFunctions($db, $which, $link = DatabaseInterface::CONNECT_USER)
  1707. {
  1708. $shows = $this->fetchResult(
  1709. 'SHOW ' . $which . ' STATUS;', null, null, $link
  1710. );
  1711. $result = array();
  1712. foreach ($shows as $one_show) {
  1713. if ($one_show['Db'] == $db && $one_show['Type'] == $which) {
  1714. $result[] = $one_show['Name'];
  1715. }
  1716. }
  1717. return($result);
  1718. }
  1719. /**
  1720. * returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW
  1721. *
  1722. * @param string $db db name
  1723. * @param string $which PROCEDURE | FUNCTION | EVENT | VIEW
  1724. * @param string $name the procedure|function|event|view name
  1725. * @param integer $link link type
  1726. *
  1727. * @return string the definition
  1728. */
  1729. public function getDefinition($db, $which, $name, $link = DatabaseInterface::CONNECT_USER)
  1730. {
  1731. $returned_field = array(
  1732. 'PROCEDURE' => 'Create Procedure',
  1733. 'FUNCTION' => 'Create Function',
  1734. 'EVENT' => 'Create Event',
  1735. 'VIEW' => 'Create View'
  1736. );
  1737. $query = 'SHOW CREATE ' . $which . ' '
  1738. . Util::backquote($db) . '.'
  1739. . Util::backquote($name);
  1740. return($this->fetchValue($query, 0, $returned_field[$which], $link));
  1741. }
  1742. /**
  1743. * returns details about the PROCEDUREs or FUNCTIONs for a specific database
  1744. * or details about a specific routine
  1745. *
  1746. * @param string $db db name
  1747. * @param string $which PROCEDURE | FUNCTION or null for both
  1748. * @param string $name name of the routine (to fetch a specific routine)
  1749. *
  1750. * @return array information about ROCEDUREs or FUNCTIONs
  1751. */
  1752. public function getRoutines($db, $which = null, $name = '')
  1753. {
  1754. $routines = array();
  1755. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  1756. $query = "SELECT"
  1757. . " `ROUTINE_SCHEMA` AS `Db`,"
  1758. . " `SPECIFIC_NAME` AS `Name`,"
  1759. . " `ROUTINE_TYPE` AS `Type`,"
  1760. . " `DEFINER` AS `Definer`,"
  1761. . " `LAST_ALTERED` AS `Modified`,"
  1762. . " `CREATED` AS `Created`,"
  1763. . " `SECURITY_TYPE` AS `Security_type`,"
  1764. . " `ROUTINE_COMMENT` AS `Comment`,"
  1765. . " `CHARACTER_SET_CLIENT` AS `character_set_client`,"
  1766. . " `COLLATION_CONNECTION` AS `collation_connection`,"
  1767. . " `DATABASE_COLLATION` AS `Database Collation`,"
  1768. . " `DTD_IDENTIFIER`"
  1769. . " FROM `information_schema`.`ROUTINES`"
  1770. . " WHERE `ROUTINE_SCHEMA` " . Util::getCollateForIS()
  1771. . " = '" . $GLOBALS['dbi']->escapeString($db) . "'";
  1772. if (Core::isValid($which, array('FUNCTION','PROCEDURE'))) {
  1773. $query .= " AND `ROUTINE_TYPE` = '" . $which . "'";
  1774. }
  1775. if (! empty($name)) {
  1776. $query .= " AND `SPECIFIC_NAME`"
  1777. . " = '" . $GLOBALS['dbi']->escapeString($name) . "'";
  1778. }
  1779. $result = $this->fetchResult($query);
  1780. if (!empty($result)) {
  1781. $routines = $result;
  1782. }
  1783. } else {
  1784. if ($which == 'FUNCTION' || $which == null) {
  1785. $query = "SHOW FUNCTION STATUS"
  1786. . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'";
  1787. if (! empty($name)) {
  1788. $query .= " AND `Name` = '"
  1789. . $GLOBALS['dbi']->escapeString($name) . "'";
  1790. }
  1791. $result = $this->fetchResult($query);
  1792. if (!empty($result)) {
  1793. $routines = array_merge($routines, $result);
  1794. }
  1795. }
  1796. if ($which == 'PROCEDURE' || $which == null) {
  1797. $query = "SHOW PROCEDURE STATUS"
  1798. . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'";
  1799. if (! empty($name)) {
  1800. $query .= " AND `Name` = '"
  1801. . $GLOBALS['dbi']->escapeString($name) . "'";
  1802. }
  1803. $result = $this->fetchResult($query);
  1804. if (!empty($result)) {
  1805. $routines = array_merge($routines, $result);
  1806. }
  1807. }
  1808. }
  1809. $ret = array();
  1810. foreach ($routines as $routine) {
  1811. $one_result = array();
  1812. $one_result['db'] = $routine['Db'];
  1813. $one_result['name'] = $routine['Name'];
  1814. $one_result['type'] = $routine['Type'];
  1815. $one_result['definer'] = $routine['Definer'];
  1816. $one_result['returns'] = isset($routine['DTD_IDENTIFIER'])
  1817. ? $routine['DTD_IDENTIFIER'] : "";
  1818. $ret[] = $one_result;
  1819. }
  1820. // Sort results by name
  1821. $name = array();
  1822. foreach ($ret as $value) {
  1823. $name[] = $value['name'];
  1824. }
  1825. array_multisort($name, SORT_ASC, $ret);
  1826. return($ret);
  1827. }
  1828. /**
  1829. * returns details about the EVENTs for a specific database
  1830. *
  1831. * @param string $db db name
  1832. * @param string $name event name
  1833. *
  1834. * @return array information about EVENTs
  1835. */
  1836. public function getEvents($db, $name = '')
  1837. {
  1838. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  1839. $query = "SELECT"
  1840. . " `EVENT_SCHEMA` AS `Db`,"
  1841. . " `EVENT_NAME` AS `Name`,"
  1842. . " `DEFINER` AS `Definer`,"
  1843. . " `TIME_ZONE` AS `Time zone`,"
  1844. . " `EVENT_TYPE` AS `Type`,"
  1845. . " `EXECUTE_AT` AS `Execute at`,"
  1846. . " `INTERVAL_VALUE` AS `Interval value`,"
  1847. . " `INTERVAL_FIELD` AS `Interval field`,"
  1848. . " `STARTS` AS `Starts`,"
  1849. . " `ENDS` AS `Ends`,"
  1850. . " `STATUS` AS `Status`,"
  1851. . " `ORIGINATOR` AS `Originator`,"
  1852. . " `CHARACTER_SET_CLIENT` AS `character_set_client`,"
  1853. . " `COLLATION_CONNECTION` AS `collation_connection`, "
  1854. . "`DATABASE_COLLATION` AS `Database Collation`"
  1855. . " FROM `information_schema`.`EVENTS`"
  1856. . " WHERE `EVENT_SCHEMA` " . Util::getCollateForIS()
  1857. . " = '" . $GLOBALS['dbi']->escapeString($db) . "'";
  1858. if (! empty($name)) {
  1859. $query .= " AND `EVENT_NAME`"
  1860. . " = '" . $GLOBALS['dbi']->escapeString($name) . "'";
  1861. }
  1862. } else {
  1863. $query = "SHOW EVENTS FROM " . Util::backquote($db);
  1864. if (! empty($name)) {
  1865. $query .= " AND `Name` = '"
  1866. . $GLOBALS['dbi']->escapeString($name) . "'";
  1867. }
  1868. }
  1869. $result = array();
  1870. if ($events = $this->fetchResult($query)) {
  1871. foreach ($events as $event) {
  1872. $one_result = array();
  1873. $one_result['name'] = $event['Name'];
  1874. $one_result['type'] = $event['Type'];
  1875. $one_result['status'] = $event['Status'];
  1876. $result[] = $one_result;
  1877. }
  1878. }
  1879. // Sort results by name
  1880. $name = array();
  1881. foreach ($result as $value) {
  1882. $name[] = $value['name'];
  1883. }
  1884. array_multisort($name, SORT_ASC, $result);
  1885. return $result;
  1886. }
  1887. /**
  1888. * returns details about the TRIGGERs for a specific table or database
  1889. *
  1890. * @param string $db db name
  1891. * @param string $table table name
  1892. * @param string $delimiter the delimiter to use (may be empty)
  1893. *
  1894. * @return array information about triggers (may be empty)
  1895. */
  1896. public function getTriggers($db, $table = '', $delimiter = '//')
  1897. {
  1898. $result = array();
  1899. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  1900. $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION'
  1901. . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT'
  1902. . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER'
  1903. . ' FROM information_schema.TRIGGERS'
  1904. . ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '='
  1905. . ' \'' . $GLOBALS['dbi']->escapeString($db) . '\'';
  1906. if (! empty($table)) {
  1907. $query .= " AND EVENT_OBJECT_TABLE " . Util::getCollateForIS()
  1908. . " = '" . $GLOBALS['dbi']->escapeString($table) . "';";
  1909. }
  1910. } else {
  1911. $query = "SHOW TRIGGERS FROM " . Util::backquote($db);
  1912. if (! empty($table)) {
  1913. $query .= " LIKE '" . $GLOBALS['dbi']->escapeString($table) . "';";
  1914. }
  1915. }
  1916. if ($triggers = $this->fetchResult($query)) {
  1917. foreach ($triggers as $trigger) {
  1918. if ($GLOBALS['cfg']['Server']['DisableIS']) {
  1919. $trigger['TRIGGER_NAME'] = $trigger['Trigger'];
  1920. $trigger['ACTION_TIMING'] = $trigger['Timing'];
  1921. $trigger['EVENT_MANIPULATION'] = $trigger['Event'];
  1922. $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table'];
  1923. $trigger['ACTION_STATEMENT'] = $trigger['Statement'];
  1924. $trigger['DEFINER'] = $trigger['Definer'];
  1925. }
  1926. $one_result = array();
  1927. $one_result['name'] = $trigger['TRIGGER_NAME'];
  1928. $one_result['table'] = $trigger['EVENT_OBJECT_TABLE'];
  1929. $one_result['action_timing'] = $trigger['ACTION_TIMING'];
  1930. $one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION'];
  1931. $one_result['definition'] = $trigger['ACTION_STATEMENT'];
  1932. $one_result['definer'] = $trigger['DEFINER'];
  1933. // do not prepend the schema name; this way, importing the
  1934. // definition into another schema will work
  1935. $one_result['full_trigger_name'] = Util::backquote(
  1936. $trigger['TRIGGER_NAME']
  1937. );
  1938. $one_result['drop'] = 'DROP TRIGGER IF EXISTS '
  1939. . $one_result['full_trigger_name'];
  1940. $one_result['create'] = 'CREATE TRIGGER '
  1941. . $one_result['full_trigger_name'] . ' '
  1942. . $trigger['ACTION_TIMING'] . ' '
  1943. . $trigger['EVENT_MANIPULATION']
  1944. . ' ON ' . Util::backquote($trigger['EVENT_OBJECT_TABLE'])
  1945. . "\n" . ' FOR EACH ROW '
  1946. . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n";
  1947. $result[] = $one_result;
  1948. }
  1949. }
  1950. // Sort results by name
  1951. $name = array();
  1952. foreach ($result as $value) {
  1953. $name[] = $value['name'];
  1954. }
  1955. array_multisort($name, SORT_ASC, $result);
  1956. return($result);
  1957. }
  1958. /**
  1959. * Formats database error message in a friendly way.
  1960. * This is needed because some errors messages cannot
  1961. * be obtained by mysql_error().
  1962. *
  1963. * @param int $error_number Error code
  1964. * @param string $error_message Error message as returned by server
  1965. *
  1966. * @return string HML text with error details
  1967. */
  1968. public static function formatError($error_number, $error_message)
  1969. {
  1970. $error_message = htmlspecialchars($error_message);
  1971. $error = '#' . ((string) $error_number);
  1972. $separator = ' &mdash; ';
  1973. if ($error_number == 2002) {
  1974. $error .= ' - ' . $error_message;
  1975. $error .= $separator;
  1976. $error .= __(
  1977. 'The server is not responding (or the local server\'s socket'
  1978. . ' is not correctly configured).'
  1979. );
  1980. } elseif ($error_number == 2003) {
  1981. $error .= ' - ' . $error_message;
  1982. $error .= $separator . __('The server is not responding.');
  1983. } elseif ($error_number == 1698 ) {
  1984. $error .= ' - ' . $error_message;
  1985. $error .= $separator . '<a href="logout.php' . Url::getCommon() . '">';
  1986. $error .= __('Logout and try as another user.') . '</a>';
  1987. } elseif ($error_number == 1005) {
  1988. if (strpos($error_message, 'errno: 13') !== false) {
  1989. $error .= ' - ' . $error_message;
  1990. $error .= $separator
  1991. . __(
  1992. 'Please check privileges of directory containing database.'
  1993. );
  1994. } else {
  1995. /* InnoDB constraints, see
  1996. * https://dev.mysql.com/doc/refman/5.0/en/
  1997. * innodb-foreign-key-constraints.html
  1998. */
  1999. $error .= ' - ' . $error_message .
  2000. ' (<a href="server_engines.php' .
  2001. Url::getCommon(
  2002. array('engine' => 'InnoDB', 'page' => 'Status')
  2003. ) . '">' . __('Details…') . '</a>)';
  2004. }
  2005. } else {
  2006. $error .= ' - ' . $error_message;
  2007. }
  2008. return $error;
  2009. }
  2010. /**
  2011. * gets the current user with host
  2012. *
  2013. * @return string the current user i.e. user@host
  2014. */
  2015. public function getCurrentUser()
  2016. {
  2017. if (Util::cacheExists('mysql_cur_user')) {
  2018. return Util::cacheGet('mysql_cur_user');
  2019. }
  2020. $user = $this->fetchValue('SELECT CURRENT_USER();');
  2021. if ($user !== false) {
  2022. Util::cacheSet('mysql_cur_user', $user);
  2023. return $user;
  2024. }
  2025. return '@';
  2026. }
  2027. /**
  2028. * Checks if current user is superuser
  2029. *
  2030. * @return bool Whether user is a superuser
  2031. */
  2032. public function isSuperuser()
  2033. {
  2034. return self::isUserType('super');
  2035. }
  2036. /**
  2037. * Checks if current user has global create user/grant privilege
  2038. * or is a superuser (i.e. SELECT on mysql.users)
  2039. * while caching the result in session.
  2040. *
  2041. * @param string $type type of user to check for
  2042. * i.e. 'create', 'grant', 'super'
  2043. *
  2044. * @return bool Whether user is a given type of user
  2045. */
  2046. public function isUserType($type)
  2047. {
  2048. if (Util::cacheExists('is_' . $type . 'user')) {
  2049. return Util::cacheGet('is_' . $type . 'user');
  2050. }
  2051. // when connection failed we don't have a $userlink
  2052. if (! isset($this->_links[DatabaseInterface::CONNECT_USER])) {
  2053. return false;
  2054. }
  2055. // checking if user is logged in
  2056. if ($type === 'logged') {
  2057. return true;
  2058. }
  2059. if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') {
  2060. // Prepare query for each user type check
  2061. $query = '';
  2062. if ($type === 'super') {
  2063. $query = 'SELECT 1 FROM mysql.user LIMIT 1';
  2064. } elseif ($type === 'create') {
  2065. list($user, $host) = $this->getCurrentUserAndHost();
  2066. $query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` "
  2067. . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND "
  2068. . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
  2069. } elseif ($type === 'grant') {
  2070. list($user, $host) = $this->getCurrentUserAndHost();
  2071. $query = "SELECT 1 FROM ("
  2072. . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
  2073. . "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION "
  2074. . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
  2075. . "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION "
  2076. . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
  2077. . "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION "
  2078. . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM "
  2079. . "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t "
  2080. . "WHERE `IS_GRANTABLE` = 'YES' AND "
  2081. . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
  2082. }
  2083. $is = false;
  2084. $result = $this->tryQuery(
  2085. $query,
  2086. self::CONNECT_USER,
  2087. self::QUERY_STORE
  2088. );
  2089. if ($result) {
  2090. $is = (bool) $this->numRows($result);
  2091. }
  2092. $this->freeResult($result);
  2093. } else {
  2094. $is = false;
  2095. $grants = $this->fetchResult(
  2096. "SHOW GRANTS FOR CURRENT_USER();",
  2097. null,
  2098. null,
  2099. self::CONNECT_USER,
  2100. self::QUERY_STORE
  2101. );
  2102. if ($grants) {
  2103. foreach ($grants as $grant) {
  2104. if ($type === 'create') {
  2105. if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false
  2106. || strpos($grant, "CREATE USER") !== false
  2107. ) {
  2108. $is = true;
  2109. break;
  2110. }
  2111. } elseif ($type === 'grant') {
  2112. if (strpos($grant, "WITH GRANT OPTION") !== false) {
  2113. $is = true;
  2114. break;
  2115. }
  2116. }
  2117. }
  2118. }
  2119. }
  2120. Util::cacheSet('is_' . $type . 'user', $is);
  2121. return $is;
  2122. }
  2123. /**
  2124. * Get the current user and host
  2125. *
  2126. * @return array array of username and hostname
  2127. */
  2128. public function getCurrentUserAndHost()
  2129. {
  2130. if (count($this->_current_user) == 0) {
  2131. $user = $this->getCurrentUser();
  2132. $this->_current_user = explode("@", $user);
  2133. }
  2134. return $this->_current_user;
  2135. }
  2136. /**
  2137. * Returns value for lower_case_table_names variable
  2138. *
  2139. * @return string
  2140. */
  2141. public function getLowerCaseNames()
  2142. {
  2143. if (is_null($this->_lower_case_table_names)) {
  2144. $this->_lower_case_table_names = $this->fetchValue(
  2145. "SELECT @@lower_case_table_names"
  2146. );
  2147. }
  2148. return $this->_lower_case_table_names;
  2149. }
  2150. /**
  2151. * Get the list of system schemas
  2152. *
  2153. * @return array list of system schemas
  2154. */
  2155. public function getSystemSchemas()
  2156. {
  2157. $schemas = array(
  2158. 'information_schema', 'performance_schema', 'mysql', 'sys'
  2159. );
  2160. $systemSchemas = array();
  2161. foreach ($schemas as $schema) {
  2162. if ($this->isSystemSchema($schema, true)) {
  2163. $systemSchemas[] = $schema;
  2164. }
  2165. }
  2166. return $systemSchemas;
  2167. }
  2168. /**
  2169. * Checks whether given schema is a system schema
  2170. *
  2171. * @param string $schema_name Name of schema (database) to test
  2172. * @param bool $testForMysqlSchema Whether 'mysql' schema should
  2173. * be treated the same as IS and DD
  2174. *
  2175. * @return bool
  2176. */
  2177. public function isSystemSchema($schema_name, $testForMysqlSchema = false)
  2178. {
  2179. $schema_name = strtolower($schema_name);
  2180. return $schema_name == 'information_schema'
  2181. || $schema_name == 'performance_schema'
  2182. || ($schema_name == 'mysql' && $testForMysqlSchema)
  2183. || $schema_name == 'sys';
  2184. }
  2185. /**
  2186. * Return connection parameters for the database server
  2187. *
  2188. * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL
  2189. * or CONNECT_AUXILIARY.
  2190. * @param array|null $server Server information like host/port/socket/persistent
  2191. *
  2192. * @return array user, host and server settings array
  2193. */
  2194. public function getConnectionParams($mode, $server = null)
  2195. {
  2196. global $cfg;
  2197. $user = null;
  2198. $password = null;
  2199. if ($mode == DatabaseInterface::CONNECT_USER) {
  2200. $user = $cfg['Server']['user'];
  2201. $password = $cfg['Server']['password'];
  2202. $server = $cfg['Server'];
  2203. } elseif ($mode == DatabaseInterface::CONNECT_CONTROL) {
  2204. $user = $cfg['Server']['controluser'];
  2205. $password = $cfg['Server']['controlpass'];
  2206. $server = array();
  2207. if (! empty($cfg['Server']['controlhost'])) {
  2208. $server['host'] = $cfg['Server']['controlhost'];
  2209. } else {
  2210. $server['host'] = $cfg['Server']['host'];
  2211. }
  2212. // Share the settings if the host is same
  2213. if ($server['host'] == $cfg['Server']['host']) {
  2214. $shared = array(
  2215. 'port', 'socket', 'compress',
  2216. 'ssl', 'ssl_key', 'ssl_cert', 'ssl_ca',
  2217. 'ssl_ca_path', 'ssl_ciphers', 'ssl_verify',
  2218. );
  2219. foreach ($shared as $item) {
  2220. if (isset($cfg['Server'][$item])) {
  2221. $server[$item] = $cfg['Server'][$item];
  2222. }
  2223. }
  2224. }
  2225. // Set configured port
  2226. if (! empty($cfg['Server']['controlport'])) {
  2227. $server['port'] = $cfg['Server']['controlport'];
  2228. }
  2229. // Set any configuration with control_ prefix
  2230. foreach ($cfg['Server'] as $key => $val) {
  2231. if (substr($key, 0, 8) === 'control_') {
  2232. $server[substr($key, 8)] = $val;
  2233. }
  2234. }
  2235. } else {
  2236. if (is_null($server)) {
  2237. return array(null, null, null);
  2238. }
  2239. if (isset($server['user'])) {
  2240. $user = $server['user'];
  2241. }
  2242. if (isset($server['password'])) {
  2243. $password = $server['password'];
  2244. }
  2245. }
  2246. // Perform sanity checks on some variables
  2247. if (empty($server['port'])) {
  2248. $server['port'] = 0;
  2249. } else {
  2250. $server['port'] = intval($server['port']);
  2251. }
  2252. if (empty($server['socket'])) {
  2253. $server['socket'] = null;
  2254. }
  2255. if (empty($server['host'])) {
  2256. $server['host'] = 'localhost';
  2257. }
  2258. if (!isset($server['ssl'])) {
  2259. $server['ssl'] = false;
  2260. }
  2261. if (!isset($server['compress'])) {
  2262. $server['compress'] = false;
  2263. }
  2264. return array($user, $password, $server);
  2265. }
  2266. /**
  2267. * connects to the database server
  2268. *
  2269. * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL
  2270. * or CONNECT_AUXILIARY.
  2271. * @param array|null $server Server information like host/port/socket/persistent
  2272. * @param integer $target How to store connection link, defaults to $mode
  2273. *
  2274. * @return mixed false on error or a connection object on success
  2275. */
  2276. public function connect($mode, $server = null, $target = null)
  2277. {
  2278. list($user, $password, $server) = $this->getConnectionParams($mode, $server);
  2279. if (is_null($target)) {
  2280. $target = $mode;
  2281. }
  2282. if (is_null($user) || is_null($password)) {
  2283. trigger_error(
  2284. __('Missing connection parameters!'),
  2285. E_USER_WARNING
  2286. );
  2287. return false;
  2288. }
  2289. // Do not show location and backtrace for connection errors
  2290. $GLOBALS['error_handler']->setHideLocation(true);
  2291. $result = $this->_extension->connect(
  2292. $user, $password, $server
  2293. );
  2294. $GLOBALS['error_handler']->setHideLocation(false);
  2295. if ($result) {
  2296. $this->_links[$target] = $result;
  2297. /* Run post connect for user connections */
  2298. if ($target == DatabaseInterface::CONNECT_USER) {
  2299. $this->postConnect();
  2300. } elseif ($target == DatabaseInterface::CONNECT_CONTROL) {
  2301. $this->postConnectControl();
  2302. }
  2303. return $result;
  2304. }
  2305. if ($mode == DatabaseInterface::CONNECT_CONTROL) {
  2306. trigger_error(
  2307. __(
  2308. 'Connection for controluser as defined in your '
  2309. . 'configuration failed.'
  2310. ),
  2311. E_USER_WARNING
  2312. );
  2313. return false;
  2314. } elseif ($mode == DatabaseInterface::CONNECT_AUXILIARY) {
  2315. // Do not go back to main login if connection failed
  2316. // (currently used only in unit testing)
  2317. return false;
  2318. }
  2319. return $result;
  2320. }
  2321. /**
  2322. * selects given database
  2323. *
  2324. * @param string $dbname database name to select
  2325. * @param integer $link link type
  2326. *
  2327. * @return boolean
  2328. */
  2329. public function selectDb($dbname, $link = DatabaseInterface::CONNECT_USER)
  2330. {
  2331. if (! isset($this->_links[$link])) {
  2332. return false;
  2333. }
  2334. return $this->_extension->selectDb($dbname, $this->_links[$link]);
  2335. }
  2336. /**
  2337. * returns array of rows with associative and numeric keys from $result
  2338. *
  2339. * @param object $result result set identifier
  2340. *
  2341. * @return array
  2342. */
  2343. public function fetchArray($result)
  2344. {
  2345. return $this->_extension->fetchArray($result);
  2346. }
  2347. /**
  2348. * returns array of rows with associative keys from $result
  2349. *
  2350. * @param object $result result set identifier
  2351. *
  2352. * @return array
  2353. */
  2354. public function fetchAssoc($result)
  2355. {
  2356. return $this->_extension->fetchAssoc($result);
  2357. }
  2358. /**
  2359. * returns array of rows with numeric keys from $result
  2360. *
  2361. * @param object $result result set identifier
  2362. *
  2363. * @return array
  2364. */
  2365. public function fetchRow($result)
  2366. {
  2367. return $this->_extension->fetchRow($result);
  2368. }
  2369. /**
  2370. * Adjusts the result pointer to an arbitrary row in the result
  2371. *
  2372. * @param object $result database result
  2373. * @param integer $offset offset to seek
  2374. *
  2375. * @return bool true on success, false on failure
  2376. */
  2377. public function dataSeek($result, $offset)
  2378. {
  2379. return $this->_extension->dataSeek($result, $offset);
  2380. }
  2381. /**
  2382. * Frees memory associated with the result
  2383. *
  2384. * @param object $result database result
  2385. *
  2386. * @return void
  2387. */
  2388. public function freeResult($result)
  2389. {
  2390. $this->_extension->freeResult($result);
  2391. }
  2392. /**
  2393. * Check if there are any more query results from a multi query
  2394. *
  2395. * @param integer $link link type
  2396. *
  2397. * @return bool true or false
  2398. */
  2399. public function moreResults($link = DatabaseInterface::CONNECT_USER)
  2400. {
  2401. if (! isset($this->_links[$link])) {
  2402. return false;
  2403. }
  2404. return $this->_extension->moreResults($this->_links[$link]);
  2405. }
  2406. /**
  2407. * Prepare next result from multi_query
  2408. *
  2409. * @param integer $link link type
  2410. *
  2411. * @return bool true or false
  2412. */
  2413. public function nextResult($link = DatabaseInterface::CONNECT_USER)
  2414. {
  2415. if (! isset($this->_links[$link])) {
  2416. return false;
  2417. }
  2418. return $this->_extension->nextResult($this->_links[$link]);
  2419. }
  2420. /**
  2421. * Store the result returned from multi query
  2422. *
  2423. * @param integer $link link type
  2424. *
  2425. * @return mixed false when empty results / result set when not empty
  2426. */
  2427. public function storeResult($link = DatabaseInterface::CONNECT_USER)
  2428. {
  2429. if (! isset($this->_links[$link])) {
  2430. return false;
  2431. }
  2432. return $this->_extension->storeResult($this->_links[$link]);
  2433. }
  2434. /**
  2435. * Returns a string representing the type of connection used
  2436. *
  2437. * @param integer $link link type
  2438. *
  2439. * @return string type of connection used
  2440. */
  2441. public function getHostInfo($link = DatabaseInterface::CONNECT_USER)
  2442. {
  2443. if (! isset($this->_links[$link])) {
  2444. return false;
  2445. }
  2446. return $this->_extension->getHostInfo($this->_links[$link]);
  2447. }
  2448. /**
  2449. * Returns the version of the MySQL protocol used
  2450. *
  2451. * @param integer $link link type
  2452. *
  2453. * @return integer version of the MySQL protocol used
  2454. */
  2455. public function getProtoInfo($link = DatabaseInterface::CONNECT_USER)
  2456. {
  2457. if (! isset($this->_links[$link])) {
  2458. return false;
  2459. }
  2460. return $this->_extension->getProtoInfo($this->_links[$link]);
  2461. }
  2462. /**
  2463. * returns a string that represents the client library version
  2464. *
  2465. * @return string MySQL client library version
  2466. */
  2467. public function getClientInfo()
  2468. {
  2469. return $this->_extension->getClientInfo();
  2470. }
  2471. /**
  2472. * returns last error message or false if no errors occurred
  2473. *
  2474. * @param integer $link link type
  2475. *
  2476. * @return string|bool $error or false
  2477. */
  2478. public function getError($link = DatabaseInterface::CONNECT_USER)
  2479. {
  2480. if (! isset($this->_links[$link])) {
  2481. return false;
  2482. }
  2483. return $this->_extension->getError($this->_links[$link]);
  2484. }
  2485. /**
  2486. * returns the number of rows returned by last query
  2487. *
  2488. * @param object $result result set identifier
  2489. *
  2490. * @return string|int
  2491. */
  2492. public function numRows($result)
  2493. {
  2494. return $this->_extension->numRows($result);
  2495. }
  2496. /**
  2497. * returns last inserted auto_increment id for given $link
  2498. * or $GLOBALS['userlink']
  2499. *
  2500. * @param integer $link link type
  2501. *
  2502. * @return int|boolean
  2503. */
  2504. public function insertId($link = DatabaseInterface::CONNECT_USER)
  2505. {
  2506. // If the primary key is BIGINT we get an incorrect result
  2507. // (sometimes negative, sometimes positive)
  2508. // and in the present function we don't know if the PK is BIGINT
  2509. // so better play safe and use LAST_INSERT_ID()
  2510. //
  2511. // When no controluser is defined, using mysqli_insert_id($link)
  2512. // does not always return the last insert id due to a mixup with
  2513. // the tracking mechanism, but this works:
  2514. return $this->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link);
  2515. }
  2516. /**
  2517. * returns the number of rows affected by last query
  2518. *
  2519. * @param integer $link link type
  2520. * @param bool $get_from_cache whether to retrieve from cache
  2521. *
  2522. * @return int|boolean
  2523. */
  2524. public function affectedRows($link = DatabaseInterface::CONNECT_USER, $get_from_cache = true)
  2525. {
  2526. if (! isset($this->_links[$link])) {
  2527. return false;
  2528. }
  2529. if ($get_from_cache) {
  2530. return $GLOBALS['cached_affected_rows'];
  2531. }
  2532. return $this->_extension->affectedRows($this->_links[$link]);
  2533. }
  2534. /**
  2535. * returns metainfo for fields in $result
  2536. *
  2537. * @param object $result result set identifier
  2538. *
  2539. * @return array meta info for fields in $result
  2540. */
  2541. public function getFieldsMeta($result)
  2542. {
  2543. $result = $this->_extension->getFieldsMeta($result);
  2544. if ($this->getLowerCaseNames() === '2') {
  2545. /**
  2546. * Fixup orgtable for lower_case_table_names = 2
  2547. *
  2548. * In this setup MySQL server reports table name lower case
  2549. * but we still need to operate on original case to properly
  2550. * match existing strings
  2551. */
  2552. foreach ($result as $value) {
  2553. if (strlen($value->orgtable) !== 0 &&
  2554. mb_strtolower($value->orgtable) === mb_strtolower($value->table)) {
  2555. $value->orgtable = $value->table;
  2556. }
  2557. }
  2558. }
  2559. return $result;
  2560. }
  2561. /**
  2562. * return number of fields in given $result
  2563. *
  2564. * @param object $result result set identifier
  2565. *
  2566. * @return int field count
  2567. */
  2568. public function numFields($result)
  2569. {
  2570. return $this->_extension->numFields($result);
  2571. }
  2572. /**
  2573. * returns the length of the given field $i in $result
  2574. *
  2575. * @param object $result result set identifier
  2576. * @param int $i field
  2577. *
  2578. * @return int length of field
  2579. */
  2580. public function fieldLen($result, $i)
  2581. {
  2582. return $this->_extension->fieldLen($result, $i);
  2583. }
  2584. /**
  2585. * returns name of $i. field in $result
  2586. *
  2587. * @param object $result result set identifier
  2588. * @param int $i field
  2589. *
  2590. * @return string name of $i. field in $result
  2591. */
  2592. public function fieldName($result, $i)
  2593. {
  2594. return $this->_extension->fieldName($result, $i);
  2595. }
  2596. /**
  2597. * returns concatenated string of human readable field flags
  2598. *
  2599. * @param object $result result set identifier
  2600. * @param int $i field
  2601. *
  2602. * @return string field flags
  2603. */
  2604. public function fieldFlags($result, $i)
  2605. {
  2606. return $this->_extension->fieldFlags($result, $i);
  2607. }
  2608. /**
  2609. * returns properly escaped string for use in MySQL queries
  2610. *
  2611. * @param string $str string to be escaped
  2612. * @param mixed $link optional database link to use
  2613. *
  2614. * @return string a MySQL escaped string
  2615. */
  2616. public function escapeString($str, $link = DatabaseInterface::CONNECT_USER)
  2617. {
  2618. if ($this->_extension === null || !isset($this->_links[$link])) {
  2619. return $str;
  2620. }
  2621. return $this->_extension->escapeString($this->_links[$link], $str);
  2622. }
  2623. /**
  2624. * Checks if this database server is running on Amazon RDS.
  2625. *
  2626. * @return boolean
  2627. */
  2628. public function isAmazonRds()
  2629. {
  2630. if (Util::cacheExists('is_amazon_rds')) {
  2631. return Util::cacheGet('is_amazon_rds');
  2632. }
  2633. $sql = 'SELECT @@basedir';
  2634. $result = $this->fetchValue($sql);
  2635. $rds = (substr($result, 0, 10) == '/rdsdbbin/');
  2636. Util::cacheSet('is_amazon_rds', $rds);
  2637. return $rds;
  2638. }
  2639. /**
  2640. * Gets SQL for killing a process.
  2641. *
  2642. * @param int $process Process ID
  2643. *
  2644. * @return string
  2645. */
  2646. public function getKillQuery($process)
  2647. {
  2648. if ($this->isAmazonRds()) {
  2649. return 'CALL mysql.rds_kill(' . $process . ');';
  2650. }
  2651. return 'KILL ' . $process . ';';
  2652. }
  2653. /**
  2654. * Get the phpmyadmin database manager
  2655. *
  2656. * @return SystemDatabase
  2657. */
  2658. public function getSystemDatabase()
  2659. {
  2660. return new SystemDatabase($this);
  2661. }
  2662. /**
  2663. * Get a table with database name and table name
  2664. *
  2665. * @param string $db_name DB name
  2666. * @param string $table_name Table name
  2667. *
  2668. * @return Table
  2669. */
  2670. public function getTable($db_name, $table_name)
  2671. {
  2672. return new Table($table_name, $db_name, $this);
  2673. }
  2674. /**
  2675. * returns collation of given db
  2676. *
  2677. * @param string $db name of db
  2678. *
  2679. * @return string collation of $db
  2680. */
  2681. public function getDbCollation($db)
  2682. {
  2683. if ($this->isSystemSchema($db)) {
  2684. // We don't have to check the collation of the virtual
  2685. // information_schema database: We know it!
  2686. return 'utf8_general_ci';
  2687. }
  2688. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  2689. // this is slow with thousands of databases
  2690. $sql = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA'
  2691. . ' WHERE SCHEMA_NAME = \'' . $this->escapeString($db)
  2692. . '\' LIMIT 1';
  2693. return $this->fetchValue($sql);
  2694. }
  2695. $this->selectDb($db);
  2696. $return = $this->fetchValue('SELECT @@collation_database');
  2697. if ($db !== $GLOBALS['db']) {
  2698. $this->selectDb($GLOBALS['db']);
  2699. }
  2700. return $return;
  2701. }
  2702. /**
  2703. * returns default server collation from show variables
  2704. *
  2705. * @return string $server_collation
  2706. */
  2707. function getServerCollation()
  2708. {
  2709. return $this->fetchValue('SELECT @@collation_server');
  2710. }
  2711. /**
  2712. * Server version as number
  2713. *
  2714. * @return integer
  2715. */
  2716. public function getVersion()
  2717. {
  2718. return $this->_version_int;
  2719. }
  2720. /**
  2721. * Server version
  2722. *
  2723. * @return string
  2724. */
  2725. public function getVersionString()
  2726. {
  2727. return $this->_version_str;
  2728. }
  2729. /**
  2730. * Server version comment
  2731. *
  2732. * @return string
  2733. */
  2734. public function getVersionComment()
  2735. {
  2736. return $this->_version_comment;
  2737. }
  2738. /**
  2739. * Whether connection is MariaDB
  2740. *
  2741. * @return boolean
  2742. */
  2743. public function isMariaDB()
  2744. {
  2745. return $this->_is_mariadb;
  2746. }
  2747. /**
  2748. * Whether connection is Percona
  2749. *
  2750. * @return boolean
  2751. */
  2752. public function isPercona()
  2753. {
  2754. return $this->_is_percona;
  2755. }
  2756. /**
  2757. * Load correct database driver
  2758. *
  2759. * @return void
  2760. */
  2761. public static function load()
  2762. {
  2763. if (defined('TESTSUITE')) {
  2764. /**
  2765. * For testsuite we use dummy driver which can fake some queries.
  2766. */
  2767. $extension = new DbiDummy();
  2768. } else {
  2769. /**
  2770. * First check for the mysqli extension, as it's the one recommended
  2771. * for the MySQL server's version that we support
  2772. * (if PHP 7+, it's the only one supported)
  2773. */
  2774. $extension = 'mysqli';
  2775. if (! self::checkDbExtension($extension)) {
  2776. $docurl = Util::getDocuLink('faq', 'faqmysql');
  2777. $doclink = sprintf(
  2778. __('See %sour documentation%s for more information.'),
  2779. '[a@' . $docurl . '@documentation]',
  2780. '[/a]'
  2781. );
  2782. if (PHP_VERSION_ID < 70000) {
  2783. $extension = 'mysql';
  2784. if (! self::checkDbExtension($extension)) {
  2785. // warn about both extensions missing and exit
  2786. Core::warnMissingExtension(
  2787. 'mysqli',
  2788. true,
  2789. $doclink
  2790. );
  2791. } elseif (empty($_SESSION['mysqlwarning'])) {
  2792. trigger_error(
  2793. __(
  2794. 'You are using the mysql extension which is deprecated in '
  2795. . 'phpMyAdmin. Please consider installing the mysqli '
  2796. . 'extension.'
  2797. ) . ' ' . $doclink,
  2798. E_USER_WARNING
  2799. );
  2800. // tell the user just once per session
  2801. $_SESSION['mysqlwarning'] = true;
  2802. }
  2803. } else {
  2804. // mysql extension is not part of PHP 7+, so warn and exit
  2805. Core::warnMissingExtension(
  2806. 'mysqli',
  2807. true,
  2808. $doclink
  2809. );
  2810. }
  2811. }
  2812. /**
  2813. * Including The DBI Plugin
  2814. */
  2815. switch($extension) {
  2816. case 'mysql' :
  2817. $extension = new DbiMysql();
  2818. break;
  2819. case 'mysqli' :
  2820. $extension = new DbiMysqli();
  2821. break;
  2822. }
  2823. }
  2824. $GLOBALS['dbi'] = new DatabaseInterface($extension);
  2825. $container = Container::getDefaultContainer();
  2826. $container->set('PMA_DatabaseInterface', $GLOBALS['dbi']);
  2827. $container->alias('dbi', 'PMA_DatabaseInterface');
  2828. }
  2829. }