DatabaseInterface.php 105 KB

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