PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/classes/StorageEngine.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 547 lines | 327 code | 82 blank | 138 comment | 31 complexity | 9cdc55750a86a4f0331013d40c8dd512 MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use PhpMyAdmin\Engines\Bdb;
  5. use PhpMyAdmin\Engines\Berkeleydb;
  6. use PhpMyAdmin\Engines\Binlog;
  7. use PhpMyAdmin\Engines\Innobase;
  8. use PhpMyAdmin\Engines\Innodb;
  9. use PhpMyAdmin\Engines\Memory;
  10. use PhpMyAdmin\Engines\Merge;
  11. use PhpMyAdmin\Engines\MrgMyisam;
  12. use PhpMyAdmin\Engines\Myisam;
  13. use PhpMyAdmin\Engines\Ndbcluster;
  14. use PhpMyAdmin\Engines\Pbxt;
  15. use PhpMyAdmin\Engines\PerformanceSchema;
  16. use PhpMyAdmin\Html\Generator;
  17. use PhpMyAdmin\Utils\SessionCache;
  18. use function __;
  19. use function array_key_exists;
  20. use function array_keys;
  21. use function explode;
  22. use function htmlspecialchars;
  23. use function in_array;
  24. use function json_decode;
  25. use function mb_stripos;
  26. use function mb_strtolower;
  27. use function sprintf;
  28. use function strlen;
  29. use function strncmp;
  30. /**
  31. * Library for extracting information about the available storage engines
  32. */
  33. class StorageEngine
  34. {
  35. protected const SUPPORT_NO = 0;
  36. protected const SUPPORT_DISABLED = 1;
  37. protected const SUPPORT_YES = 2;
  38. protected const SUPPORT_DEFAULT = 3;
  39. protected const DETAILS_TYPE_PLAINTEXT = 0;
  40. protected const DETAILS_TYPE_SIZE = 1;
  41. protected const DETAILS_TYPE_NUMERIC = 2; // Has no effect yet...
  42. protected const DETAILS_TYPE_BOOLEAN = 3; // 'ON' or 'OFF'
  43. /** @var string engine name */
  44. public $engine = 'dummy';
  45. /** @var string engine title/description */
  46. public $title = 'PMA Dummy Engine Class';
  47. /** @var string engine lang description */
  48. public $comment = 'If you read this text inside phpMyAdmin, something went wrong...';
  49. /**
  50. * Engine supported by current server.
  51. *
  52. * @var int
  53. * @psalm-var self::SUPPORT_NO|self::SUPPORT_DISABLED|self::SUPPORT_YES|self::SUPPORT_DEFAULT
  54. */
  55. public $support = self::SUPPORT_NO;
  56. /**
  57. * @param string $engine The engine ID
  58. */
  59. public function __construct($engine)
  60. {
  61. $storage_engines = self::getStorageEngines();
  62. if (empty($storage_engines[$engine])) {
  63. return;
  64. }
  65. $this->engine = $engine;
  66. $this->title = $storage_engines[$engine]['Engine'];
  67. $this->comment = ($storage_engines[$engine]['Comment'] ?? '');
  68. switch ($storage_engines[$engine]['Support']) {
  69. case 'DEFAULT':
  70. $this->support = self::SUPPORT_DEFAULT;
  71. break;
  72. case 'YES':
  73. $this->support = self::SUPPORT_YES;
  74. break;
  75. case 'DISABLED':
  76. $this->support = self::SUPPORT_DISABLED;
  77. break;
  78. case 'NO':
  79. default:
  80. $this->support = self::SUPPORT_NO;
  81. }
  82. }
  83. /**
  84. * Returns array of storage engines
  85. *
  86. * @return array[] array of storage engines
  87. *
  88. * @static
  89. * @staticvar array $storage_engines storage engines
  90. * @access public
  91. */
  92. public static function getStorageEngines()
  93. {
  94. global $dbi;
  95. static $storage_engines = null;
  96. if ($storage_engines == null) {
  97. $storage_engines = $dbi->fetchResult('SHOW STORAGE ENGINES', 'Engine');
  98. if ($dbi->getVersion() >= 50708) {
  99. $disabled = (string) SessionCache::get(
  100. 'disabled_storage_engines',
  101. /** @return mixed|false */
  102. static function () use ($dbi) {
  103. return $dbi->fetchValue(
  104. 'SELECT @@disabled_storage_engines'
  105. );
  106. }
  107. );
  108. foreach (explode(',', $disabled) as $engine) {
  109. if (! isset($storage_engines[$engine])) {
  110. continue;
  111. }
  112. $storage_engines[$engine]['Support'] = 'DISABLED';
  113. }
  114. }
  115. }
  116. return $storage_engines;
  117. }
  118. /**
  119. * Returns if Mroonga is available to be used
  120. *
  121. * This is public to be used in the StructureComtroller, the first release
  122. * of this function was looking Mroonga in the engines list but this second
  123. * method checks too that mroonga is installed successfully
  124. */
  125. public static function hasMroongaEngine(): bool
  126. {
  127. global $dbi;
  128. $cacheKey = 'storage-engine.mroonga.has.mroonga_command';
  129. if (Cache::has($cacheKey)) {
  130. return (bool) Cache::get($cacheKey, false);
  131. }
  132. $supportsMroonga = $dbi->tryQuery('SELECT mroonga_command(\'object_list\');') !== false;
  133. Cache::set($cacheKey, $supportsMroonga);
  134. return $supportsMroonga;
  135. }
  136. /**
  137. * Get the lengths of a table of database
  138. *
  139. * @param string $dbName DB name
  140. * @param string $tableName Table name
  141. *
  142. * @return int[]
  143. */
  144. public static function getMroongaLengths(string $dbName, string $tableName): array
  145. {
  146. global $dbi;
  147. $cacheKey = 'storage-engine.mroonga.object_list.' . $dbName;
  148. $dbi->selectDb($dbName);// Needed for mroonga_command calls
  149. if (! Cache::has($cacheKey)) {
  150. $result = $dbi->fetchSingleRow('SELECT mroonga_command(\'object_list\');', 'NUM');
  151. $objectList = (array) json_decode($result[0] ?? '', true);
  152. foreach ($objectList as $mroongaName => $mroongaData) {
  153. /**
  154. * We only need the objects of table or column types, more info:
  155. * - https://groonga.org/docs/reference/commands/object_list.html#object-type
  156. * - https://groonga.org/docs/reference/commands/object_inspect.html#table-type-id
  157. * - https://groonga.org/docs/reference/commands/object_inspect.html#column-type-raw-id
  158. */
  159. if (in_array($mroongaData['type']['id'], [48, 49, 50, 51, 64, 65, 72])) {
  160. continue;
  161. }
  162. unset($objectList[$mroongaName]);
  163. }
  164. // At this point, we can remove all the data because only need the mroongaName values
  165. Cache::set($cacheKey, array_keys($objectList));
  166. }
  167. /** @var string[] $objectList */
  168. $objectList = Cache::get($cacheKey, []);
  169. $dataLength = 0;
  170. $indexLength = 0;
  171. foreach ($objectList as $mroongaName) {
  172. if (strncmp($tableName, $mroongaName, strlen($tableName)) !== 0) {
  173. continue;
  174. }
  175. $result = $dbi->fetchSingleRow('SELECT mroonga_command(\'object_inspect ' . $mroongaName . '\');', 'NUM');
  176. $decodedData = json_decode($result[0] ?? '', true);
  177. if ($decodedData === null) {
  178. // Invalid for some strange reason, maybe query failed
  179. continue;
  180. }
  181. $indexPrefix = $tableName . '#' . $tableName;
  182. if (strncmp($indexPrefix, $mroongaName, strlen($indexPrefix)) === 0) {
  183. $indexLength += $decodedData['disk_usage'];
  184. continue;
  185. }
  186. $dataLength += $decodedData['disk_usage'];
  187. }
  188. return [$dataLength, $indexLength];
  189. }
  190. /**
  191. * @return array<int|string, array<string, mixed>>
  192. */
  193. public static function getArray(): array
  194. {
  195. $engines = [];
  196. foreach (self::getStorageEngines() as $details) {
  197. // Don't show PERFORMANCE_SCHEMA engine (MySQL 5.5)
  198. if (
  199. $details['Support'] === 'NO'
  200. || $details['Support'] === 'DISABLED'
  201. || $details['Engine'] === 'PERFORMANCE_SCHEMA'
  202. ) {
  203. continue;
  204. }
  205. $engines[$details['Engine']] = [
  206. 'name' => $details['Engine'],
  207. 'comment' => $details['Comment'],
  208. 'is_default' => $details['Support'] === 'DEFAULT',
  209. ];
  210. }
  211. return $engines;
  212. }
  213. /**
  214. * Loads the corresponding engine plugin, if available.
  215. *
  216. * @param string $engine The engine ID
  217. *
  218. * @return StorageEngine The engine plugin
  219. *
  220. * @static
  221. */
  222. public static function getEngine($engine)
  223. {
  224. switch (mb_strtolower($engine)) {
  225. case 'bdb':
  226. return new Bdb($engine);
  227. case 'berkeleydb':
  228. return new Berkeleydb($engine);
  229. case 'binlog':
  230. return new Binlog($engine);
  231. case 'innobase':
  232. return new Innobase($engine);
  233. case 'innodb':
  234. return new Innodb($engine);
  235. case 'memory':
  236. return new Memory($engine);
  237. case 'merge':
  238. return new Merge($engine);
  239. case 'mrg_myisam':
  240. return new MrgMyisam($engine);
  241. case 'myisam':
  242. return new Myisam($engine);
  243. case 'ndbcluster':
  244. return new Ndbcluster($engine);
  245. case 'pbxt':
  246. return new Pbxt($engine);
  247. case 'performance_schema':
  248. return new PerformanceSchema($engine);
  249. default:
  250. return new StorageEngine($engine);
  251. }
  252. }
  253. /**
  254. * Returns true if given engine name is supported/valid, otherwise false
  255. *
  256. * @param string $engine name of engine
  257. *
  258. * @static
  259. */
  260. public static function isValid($engine): bool
  261. {
  262. if ($engine === 'PBMS') {
  263. return true;
  264. }
  265. $storage_engines = self::getStorageEngines();
  266. return isset($storage_engines[$engine]);
  267. }
  268. /**
  269. * Returns as HTML table of the engine's server variables
  270. *
  271. * @return string The table that was generated based on the retrieved
  272. * information
  273. */
  274. public function getHtmlVariables()
  275. {
  276. $ret = '';
  277. foreach ($this->getVariablesStatus() as $details) {
  278. $ret .= '<tr>' . "\n"
  279. . ' <td>' . "\n";
  280. if (! empty($details['desc'])) {
  281. $ret .= ' '
  282. . Generator::showHint($details['desc'])
  283. . "\n";
  284. }
  285. $ret .= ' </td>' . "\n"
  286. . ' <th scope="row">' . htmlspecialchars($details['title']) . '</th>'
  287. . "\n"
  288. . ' <td class="font-monospace text-end">';
  289. switch ($details['type']) {
  290. case self::DETAILS_TYPE_SIZE:
  291. $parsed_size = $this->resolveTypeSize($details['value']);
  292. if ($parsed_size !== null) {
  293. $ret .= $parsed_size[0] . '&nbsp;' . $parsed_size[1];
  294. }
  295. break;
  296. case self::DETAILS_TYPE_NUMERIC:
  297. $ret .= Util::formatNumber($details['value']) . ' ';
  298. break;
  299. default:
  300. $ret .= htmlspecialchars($details['value']) . ' ';
  301. }
  302. $ret .= '</td>' . "\n"
  303. . '</tr>' . "\n";
  304. }
  305. if (! $ret) {
  306. $ret = '<p>' . "\n"
  307. . ' '
  308. . __('There is no detailed status information available for this storage engine.')
  309. . "\n"
  310. . '</p>' . "\n";
  311. } else {
  312. $ret = '<table class="table table-light table-striped table-hover w-auto">'
  313. . "\n" . $ret . '</table>' . "\n";
  314. }
  315. return $ret;
  316. }
  317. /**
  318. * Returns the engine specific handling for
  319. * DETAILS_TYPE_SIZE type variables.
  320. *
  321. * This function should be overridden when
  322. * DETAILS_TYPE_SIZE type needs to be
  323. * handled differently for a particular engine.
  324. *
  325. * @param int $value Value to format
  326. *
  327. * @return array|null the formatted value and its unit
  328. */
  329. public function resolveTypeSize($value): ?array
  330. {
  331. return Util::formatByteDown($value);
  332. }
  333. /**
  334. * Returns array with detailed info about engine specific server variables
  335. *
  336. * @return array array with detailed info about specific engine server variables
  337. */
  338. public function getVariablesStatus()
  339. {
  340. global $dbi;
  341. $variables = $this->getVariables();
  342. $like = $this->getVariablesLikePattern();
  343. if ($like) {
  344. $like = " LIKE '" . $like . "' ";
  345. } else {
  346. $like = '';
  347. }
  348. $mysql_vars = [];
  349. $sql_query = 'SHOW GLOBAL VARIABLES ' . $like . ';';
  350. $res = $dbi->query($sql_query);
  351. while ($row = $dbi->fetchAssoc($res)) {
  352. if (isset($variables[$row['Variable_name']])) {
  353. $mysql_vars[$row['Variable_name']] = $variables[$row['Variable_name']];
  354. } elseif (! $like && mb_stripos($row['Variable_name'], $this->engine) !== 0) {
  355. continue;
  356. }
  357. $mysql_vars[$row['Variable_name']]['value'] = $row['Value'];
  358. if (empty($mysql_vars[$row['Variable_name']]['title'])) {
  359. $mysql_vars[$row['Variable_name']]['title'] = $row['Variable_name'];
  360. }
  361. if (isset($mysql_vars[$row['Variable_name']]['type'])) {
  362. continue;
  363. }
  364. $mysql_vars[$row['Variable_name']]['type'] = self::DETAILS_TYPE_PLAINTEXT;
  365. }
  366. $dbi->freeResult($res);
  367. return $mysql_vars;
  368. }
  369. /**
  370. * Reveals the engine's title
  371. *
  372. * @return string The title
  373. */
  374. public function getTitle()
  375. {
  376. return $this->title;
  377. }
  378. /**
  379. * Fetches the server's comment about this engine
  380. *
  381. * @return string The comment
  382. */
  383. public function getComment()
  384. {
  385. return $this->comment;
  386. }
  387. /**
  388. * Information message on whether this storage engine is supported
  389. *
  390. * @return string The localized message.
  391. */
  392. public function getSupportInformationMessage()
  393. {
  394. switch ($this->support) {
  395. case self::SUPPORT_DEFAULT:
  396. $message = __('%s is the default storage engine on this MySQL server.');
  397. break;
  398. case self::SUPPORT_YES:
  399. $message = __('%s is available on this MySQL server.');
  400. break;
  401. case self::SUPPORT_DISABLED:
  402. $message = __('%s has been disabled for this MySQL server.');
  403. break;
  404. case self::SUPPORT_NO:
  405. default:
  406. $message = __('This MySQL server does not support the %s storage engine.');
  407. }
  408. return sprintf($message, htmlspecialchars($this->title));
  409. }
  410. /**
  411. * Generates a list of MySQL variables that provide information about this
  412. * engine. This function should be overridden when extending this class
  413. * for a particular engine.
  414. *
  415. * @return array The list of variables.
  416. */
  417. public function getVariables()
  418. {
  419. return [];
  420. }
  421. /**
  422. * Returns string with filename for the MySQL helppage
  423. * about this storage engine
  424. *
  425. * @return string MySQL help page filename
  426. */
  427. public function getMysqlHelpPage()
  428. {
  429. return $this->engine . '-storage-engine';
  430. }
  431. /**
  432. * Returns the pattern to be used in the query for SQL variables
  433. * related to the storage engine
  434. *
  435. * @return string SQL query LIKE pattern
  436. */
  437. public function getVariablesLikePattern()
  438. {
  439. return '';
  440. }
  441. /**
  442. * Returns a list of available information pages with labels
  443. *
  444. * @return string[] The list
  445. */
  446. public function getInfoPages()
  447. {
  448. return [];
  449. }
  450. /**
  451. * Generates the requested information page
  452. *
  453. * @param string $id page id
  454. *
  455. * @return string html output
  456. */
  457. public function getPage($id)
  458. {
  459. if (! array_key_exists($id, $this->getInfoPages())) {
  460. return '';
  461. }
  462. $id = 'getPage' . $id;
  463. return $this->$id();
  464. }
  465. }