StructureController.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Controllers\Database;
  4. use PhpMyAdmin\Charsets;
  5. use PhpMyAdmin\CheckUserPrivileges;
  6. use PhpMyAdmin\Config\PageSettings;
  7. use PhpMyAdmin\ConfigStorage\Relation;
  8. use PhpMyAdmin\ConfigStorage\RelationCleanup;
  9. use PhpMyAdmin\DatabaseInterface;
  10. use PhpMyAdmin\FlashMessages;
  11. use PhpMyAdmin\Html\Generator;
  12. use PhpMyAdmin\Operations;
  13. use PhpMyAdmin\RecentFavoriteTable;
  14. use PhpMyAdmin\Replication;
  15. use PhpMyAdmin\ReplicationInfo;
  16. use PhpMyAdmin\ResponseRenderer;
  17. use PhpMyAdmin\Sanitize;
  18. use PhpMyAdmin\StorageEngine;
  19. use PhpMyAdmin\Template;
  20. use PhpMyAdmin\Tracker;
  21. use PhpMyAdmin\Url;
  22. use PhpMyAdmin\Util;
  23. use function __;
  24. use function array_search;
  25. use function ceil;
  26. use function count;
  27. use function htmlspecialchars;
  28. use function implode;
  29. use function in_array;
  30. use function is_string;
  31. use function max;
  32. use function mb_substr;
  33. use function md5;
  34. use function preg_match;
  35. use function preg_quote;
  36. use function sprintf;
  37. use function str_replace;
  38. use function strlen;
  39. use function strtotime;
  40. use function urlencode;
  41. /**
  42. * Handles database structure logic
  43. */
  44. class StructureController extends AbstractController
  45. {
  46. /** @var int Number of tables */
  47. protected $numTables;
  48. /** @var int Current position in the list */
  49. protected $position;
  50. /** @var bool DB is information_schema */
  51. protected $dbIsSystemSchema;
  52. /** @var int Number of tables */
  53. protected $totalNumTables;
  54. /** @var array Tables in the database */
  55. protected $tables;
  56. /** @var bool whether stats show or not */
  57. protected $isShowStats;
  58. /** @var Relation */
  59. private $relation;
  60. /** @var Replication */
  61. private $replication;
  62. /** @var RelationCleanup */
  63. private $relationCleanup;
  64. /** @var Operations */
  65. private $operations;
  66. /** @var ReplicationInfo */
  67. private $replicationInfo;
  68. /** @var DatabaseInterface */
  69. private $dbi;
  70. /** @var FlashMessages */
  71. private $flash;
  72. public function __construct(
  73. ResponseRenderer $response,
  74. Template $template,
  75. string $db,
  76. Relation $relation,
  77. Replication $replication,
  78. RelationCleanup $relationCleanup,
  79. Operations $operations,
  80. DatabaseInterface $dbi,
  81. FlashMessages $flash
  82. ) {
  83. parent::__construct($response, $template, $db);
  84. $this->relation = $relation;
  85. $this->replication = $replication;
  86. $this->relationCleanup = $relationCleanup;
  87. $this->operations = $operations;
  88. $this->dbi = $dbi;
  89. $this->flash = $flash;
  90. $this->replicationInfo = new ReplicationInfo($this->dbi);
  91. }
  92. /**
  93. * Retrieves database information for further use
  94. *
  95. * @param string $subPart Page part name
  96. */
  97. private function getDatabaseInfo(string $subPart): void
  98. {
  99. [
  100. $tables,
  101. $numTables,
  102. $totalNumTables,,
  103. $isShowStats,
  104. $dbIsSystemSchema,,,
  105. $position,
  106. ] = Util::getDbInfo($this->db, $subPart);
  107. $this->tables = $tables;
  108. $this->numTables = $numTables;
  109. $this->position = $position;
  110. $this->dbIsSystemSchema = $dbIsSystemSchema;
  111. $this->totalNumTables = $totalNumTables;
  112. $this->isShowStats = $isShowStats;
  113. }
  114. public function __invoke(): void
  115. {
  116. global $cfg, $db, $errorUrl;
  117. $parameters = [
  118. 'sort' => $_REQUEST['sort'] ?? null,
  119. 'sort_order' => $_REQUEST['sort_order'] ?? null,
  120. ];
  121. Util::checkParameters(['db']);
  122. $errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
  123. $errorUrl .= Url::getCommon(['db' => $db], '&');
  124. if (! $this->hasDatabase()) {
  125. return;
  126. }
  127. $this->addScriptFiles(['database/structure.js', 'table/change.js']);
  128. // Gets the database structure
  129. $this->getDatabaseInfo('_structure');
  130. // Checks if there are any tables to be shown on current page.
  131. // If there are no tables, the user is redirected to the last page
  132. // having any.
  133. if ($this->totalNumTables > 0 && $this->position > $this->totalNumTables) {
  134. $this->redirect('/database/structure', [
  135. 'db' => $this->db,
  136. 'pos' => max(0, $this->totalNumTables - $cfg['MaxTableList']),
  137. 'reload' => 1,
  138. ]);
  139. }
  140. $this->replicationInfo->load($_POST['primary_connection'] ?? null);
  141. $replicaInfo = $this->replicationInfo->getReplicaInfo();
  142. $pageSettings = new PageSettings('DbStructure');
  143. $this->response->addHTML($pageSettings->getErrorHTML());
  144. $this->response->addHTML($pageSettings->getHTML());
  145. if ($this->numTables > 0) {
  146. $urlParams = [
  147. 'pos' => $this->position,
  148. 'db' => $this->db,
  149. ];
  150. if (isset($parameters['sort'])) {
  151. $urlParams['sort'] = $parameters['sort'];
  152. }
  153. if (isset($parameters['sort_order'])) {
  154. $urlParams['sort_order'] = $parameters['sort_order'];
  155. }
  156. $listNavigator = Generator::getListNavigator(
  157. $this->totalNumTables,
  158. $this->position,
  159. $urlParams,
  160. Url::getFromRoute('/database/structure'),
  161. 'frame_content',
  162. $cfg['MaxTableList']
  163. );
  164. $tableList = $this->displayTableList($replicaInfo);
  165. }
  166. $createTable = '';
  167. if (empty($this->dbIsSystemSchema)) {
  168. $checkUserPrivileges = new CheckUserPrivileges($this->dbi);
  169. $checkUserPrivileges->getPrivileges();
  170. $createTable = $this->template->render('database/create_table', ['db' => $this->db]);
  171. }
  172. $this->render('database/structure/index', [
  173. 'database' => $this->db,
  174. 'has_tables' => $this->numTables > 0,
  175. 'list_navigator_html' => $listNavigator ?? '',
  176. 'table_list_html' => $tableList ?? '',
  177. 'is_system_schema' => ! empty($this->dbIsSystemSchema),
  178. 'create_table_html' => $createTable,
  179. ]);
  180. }
  181. /**
  182. * @param array $replicaInfo
  183. */
  184. protected function displayTableList($replicaInfo): string
  185. {
  186. $html = '';
  187. // filtering
  188. $html .= $this->template->render('filter', ['filter_value' => '']);
  189. $i = $sumEntries = 0;
  190. $overheadCheck = false;
  191. $createTimeAll = '';
  192. $updateTimeAll = '';
  193. $checkTimeAll = '';
  194. $numColumns = $GLOBALS['cfg']['PropertiesNumColumns'] > 1
  195. ? ceil($this->numTables / $GLOBALS['cfg']['PropertiesNumColumns']) + 1
  196. : 0;
  197. $rowCount = 0;
  198. $sumSize = 0;
  199. $overheadSize = 0;
  200. $hiddenFields = [];
  201. $overallApproxRows = false;
  202. $structureTableRows = [];
  203. $trackedTables = Tracker::getTrackedTables($GLOBALS['db']);
  204. foreach ($this->tables as $currentTable) {
  205. // Get valid statistics whatever is the table type
  206. $dropQuery = '';
  207. $dropMessage = '';
  208. $overhead = '';
  209. $inputClass = ['checkall'];
  210. // Sets parameters for links
  211. $tableUrlParams = [
  212. 'db' => $this->db,
  213. 'table' => $currentTable['TABLE_NAME'],
  214. ];
  215. // do not list the previous table's size info for a view
  216. [
  217. $currentTable,
  218. $formattedSize,
  219. $unit,
  220. $formattedOverhead,
  221. $overheadUnit,
  222. $overheadSize,
  223. $tableIsView,
  224. $sumSize,
  225. ] = $this->getStuffForEngineTypeTable($currentTable, $sumSize, $overheadSize);
  226. $curTable = $this->dbi
  227. ->getTable($this->db, $currentTable['TABLE_NAME']);
  228. if (! $curTable->isMerge()) {
  229. $sumEntries += $currentTable['TABLE_ROWS'];
  230. }
  231. $collationDefinition = '---';
  232. if (isset($currentTable['Collation'])) {
  233. $tableCollation = Charsets::findCollationByName(
  234. $this->dbi,
  235. $GLOBALS['cfg']['Server']['DisableIS'],
  236. $currentTable['Collation']
  237. );
  238. if ($tableCollation !== null) {
  239. $collationDefinition = $this->template->render('database/structure/collation_definition', [
  240. 'valueTitle' => $tableCollation->getDescription(),
  241. 'value' => $tableCollation->getName(),
  242. ]);
  243. }
  244. }
  245. if ($this->isShowStats) {
  246. $overhead = '-';
  247. if ($formattedOverhead != '') {
  248. $overhead = $this->template->render('database/structure/overhead', [
  249. 'table_url_params' => $tableUrlParams,
  250. 'formatted_overhead' => $formattedOverhead,
  251. 'overhead_unit' => $overheadUnit,
  252. ]);
  253. $overheadCheck = true;
  254. $inputClass[] = 'tbl-overhead';
  255. }
  256. }
  257. if ($GLOBALS['cfg']['ShowDbStructureCharset']) {
  258. $charset = '';
  259. if (isset($tableCollation)) {
  260. $charset = $tableCollation->getCharset();
  261. }
  262. }
  263. if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
  264. $createTime = $currentTable['Create_time'] ?? '';
  265. if ($createTime && (! $createTimeAll || $createTime < $createTimeAll)) {
  266. $createTimeAll = $createTime;
  267. }
  268. }
  269. if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
  270. $updateTime = $currentTable['Update_time'] ?? '';
  271. if ($updateTime && (! $updateTimeAll || $updateTime < $updateTimeAll)) {
  272. $updateTimeAll = $updateTime;
  273. }
  274. }
  275. if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
  276. $checkTime = $currentTable['Check_time'] ?? '';
  277. if ($checkTime && (! $checkTimeAll || $checkTime < $checkTimeAll)) {
  278. $checkTimeAll = $checkTime;
  279. }
  280. }
  281. $truename = $currentTable['TABLE_NAME'];
  282. $i++;
  283. $rowCount++;
  284. if ($tableIsView) {
  285. $hiddenFields[] = '<input type="hidden" name="views[]" value="'
  286. . htmlspecialchars($currentTable['TABLE_NAME']) . '">';
  287. }
  288. /*
  289. * Always activate links for Browse, Search and Empty, even if
  290. * the icons are greyed, because
  291. * 1. for views, we don't know the number of rows at this point
  292. * 2. for tables, another source could have populated them since the
  293. * page was generated
  294. *
  295. * I could have used the PHP ternary conditional operator but I find
  296. * the code easier to read without this operator.
  297. */
  298. $mayHaveRows = $currentTable['TABLE_ROWS'] > 0 || $tableIsView;
  299. if (! $this->dbIsSystemSchema) {
  300. $dropQuery = sprintf(
  301. 'DROP %s %s',
  302. $tableIsView ? 'VIEW' : 'TABLE',
  303. Util::backquote(
  304. $currentTable['TABLE_NAME']
  305. )
  306. );
  307. $dropMessage = sprintf(
  308. ($tableIsView ? __('View %s has been dropped.') : __('Table %s has been dropped.')),
  309. str_replace(
  310. ' ',
  311. '&nbsp;',
  312. htmlspecialchars($currentTable['TABLE_NAME'])
  313. )
  314. );
  315. }
  316. if ($numColumns > 0 && $this->numTables > $numColumns && ($rowCount % $numColumns) == 0) {
  317. $rowCount = 1;
  318. $html .= $this->template->render('database/structure/table_header', [
  319. 'db' => $this->db,
  320. 'db_is_system_schema' => $this->dbIsSystemSchema,
  321. 'replication' => $replicaInfo['status'],
  322. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  323. 'is_show_stats' => $this->isShowStats,
  324. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  325. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  326. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  327. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  328. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  329. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  330. 'structure_table_rows' => $structureTableRows,
  331. ]);
  332. $structureTableRows = [];
  333. }
  334. [$approxRows, $showSuperscript] = $this->isRowCountApproximated($currentTable, $tableIsView);
  335. [$do, $ignored] = $this->getReplicationStatus($replicaInfo, $truename);
  336. $structureTableRows[] = [
  337. 'table_name_hash' => md5($currentTable['TABLE_NAME']),
  338. 'db_table_name_hash' => md5($this->db . '.' . $currentTable['TABLE_NAME']),
  339. 'db' => $this->db,
  340. 'curr' => $i,
  341. 'input_class' => implode(' ', $inputClass),
  342. 'table_is_view' => $tableIsView,
  343. 'current_table' => $currentTable,
  344. 'may_have_rows' => $mayHaveRows,
  345. 'browse_table_label_title' => htmlspecialchars($currentTable['TABLE_COMMENT']),
  346. 'browse_table_label_truename' => $truename,
  347. 'empty_table_sql_query' => 'TRUNCATE ' . Util::backquote($currentTable['TABLE_NAME']),
  348. 'empty_table_message_to_show' => urlencode(
  349. sprintf(
  350. __('Table %s has been emptied.'),
  351. htmlspecialchars(
  352. $currentTable['TABLE_NAME']
  353. )
  354. )
  355. ),
  356. 'tracking_icon' => $this->getTrackingIcon($truename, $trackedTables[$truename] ?? null),
  357. 'server_replica_status' => $replicaInfo['status'],
  358. 'table_url_params' => $tableUrlParams,
  359. 'db_is_system_schema' => $this->dbIsSystemSchema,
  360. 'drop_query' => $dropQuery,
  361. 'drop_message' => $dropMessage,
  362. 'collation' => $collationDefinition,
  363. 'formatted_size' => $formattedSize,
  364. 'unit' => $unit,
  365. 'overhead' => $overhead,
  366. 'create_time' => isset($createTime) && $createTime
  367. ? Util::localisedDate(strtotime($createTime)) : '-',
  368. 'update_time' => isset($updateTime) && $updateTime
  369. ? Util::localisedDate(strtotime($updateTime)) : '-',
  370. 'check_time' => isset($checkTime) && $checkTime
  371. ? Util::localisedDate(strtotime($checkTime)) : '-',
  372. 'charset' => $charset ?? '',
  373. 'is_show_stats' => $this->isShowStats,
  374. 'ignored' => $ignored,
  375. 'do' => $do,
  376. 'approx_rows' => $approxRows,
  377. 'show_superscript' => $showSuperscript,
  378. 'already_favorite' => $this->checkFavoriteTable($currentTable['TABLE_NAME']),
  379. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  380. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  381. 'limit_chars' => $GLOBALS['cfg']['LimitChars'],
  382. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  383. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  384. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  385. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  386. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  387. ];
  388. $overallApproxRows = $overallApproxRows || $approxRows;
  389. }
  390. $databaseCollation = [];
  391. $databaseCharset = '';
  392. $collation = Charsets::findCollationByName(
  393. $this->dbi,
  394. $GLOBALS['cfg']['Server']['DisableIS'],
  395. $this->dbi->getDbCollation($this->db)
  396. );
  397. if ($collation !== null) {
  398. $databaseCollation = [
  399. 'name' => $collation->getName(),
  400. 'description' => $collation->getDescription(),
  401. ];
  402. $databaseCharset = $collation->getCharset();
  403. }
  404. $relationParameters = $this->relation->getRelationParameters();
  405. return $html . $this->template->render('database/structure/table_header', [
  406. 'db' => $this->db,
  407. 'db_is_system_schema' => $this->dbIsSystemSchema,
  408. 'replication' => $replicaInfo['status'],
  409. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  410. 'is_show_stats' => $this->isShowStats,
  411. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  412. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  413. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  414. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  415. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  416. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  417. 'structure_table_rows' => $structureTableRows,
  418. 'body_for_table_summary' => [
  419. 'num_tables' => $this->numTables,
  420. 'server_replica_status' => $replicaInfo['status'],
  421. 'db_is_system_schema' => $this->dbIsSystemSchema,
  422. 'sum_entries' => $sumEntries,
  423. 'database_collation' => $databaseCollation,
  424. 'is_show_stats' => $this->isShowStats,
  425. 'database_charset' => $databaseCharset,
  426. 'sum_size' => $sumSize,
  427. 'overhead_size' => $overheadSize,
  428. 'create_time_all' => $createTimeAll ? Util::localisedDate(strtotime($createTimeAll)) : '-',
  429. 'update_time_all' => $updateTimeAll ? Util::localisedDate(strtotime($updateTimeAll)) : '-',
  430. 'check_time_all' => $checkTimeAll ? Util::localisedDate(strtotime($checkTimeAll)) : '-',
  431. 'approx_rows' => $overallApproxRows,
  432. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  433. 'db' => $GLOBALS['db'],
  434. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  435. 'dbi' => $this->dbi,
  436. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  437. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  438. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  439. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  440. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  441. ],
  442. 'check_all_tables' => [
  443. 'text_dir' => $GLOBALS['text_dir'],
  444. 'overhead_check' => $overheadCheck,
  445. 'db_is_system_schema' => $this->dbIsSystemSchema,
  446. 'hidden_fields' => $hiddenFields,
  447. 'disable_multi_table' => $GLOBALS['cfg']['DisableMultiTableMaintenance'],
  448. 'central_columns_work' => $relationParameters->centralColumnsFeature !== null,
  449. ],
  450. ]);
  451. }
  452. /**
  453. * Returns the tracking icon if the table is tracked
  454. *
  455. * @param string $table table name
  456. * @param array|null $trackedTable
  457. *
  458. * @return string HTML for tracking icon
  459. */
  460. protected function getTrackingIcon(string $table, $trackedTable): string
  461. {
  462. $trackingIcon = '';
  463. if (Tracker::isActive()) {
  464. if ($trackedTable !== null) {
  465. $trackingIcon = $this->template->render('database/structure/tracking_icon', [
  466. 'db' => $this->db,
  467. 'table' => $table,
  468. 'is_tracked' => $trackedTable['active'],
  469. ]);
  470. }
  471. }
  472. return $trackingIcon;
  473. }
  474. /**
  475. * Returns whether the row count is approximated
  476. *
  477. * @param array $currentTable array containing details about the table
  478. * @param bool $tableIsView whether the table is a view
  479. *
  480. * @return array
  481. */
  482. protected function isRowCountApproximated(
  483. array $currentTable,
  484. bool $tableIsView
  485. ): array {
  486. $approxRows = false;
  487. $showSuperscript = '';
  488. // there is a null value in the ENGINE
  489. // - when the table needs to be repaired, or
  490. // - when it's a view
  491. // so ensure that we'll display "in use" below for a table
  492. // that needs to be repaired
  493. if (isset($currentTable['TABLE_ROWS']) && ($currentTable['ENGINE'] != null || $tableIsView)) {
  494. // InnoDB/TokuDB table: we did not get an accurate row count
  495. $approxRows = ! $tableIsView
  496. && in_array($currentTable['ENGINE'], ['InnoDB', 'TokuDB'])
  497. && ! $currentTable['COUNTED'];
  498. if ($tableIsView && $currentTable['TABLE_ROWS'] >= $GLOBALS['cfg']['MaxExactCountViews']) {
  499. $approxRows = true;
  500. $showSuperscript = Generator::showHint(
  501. Sanitize::sanitizeMessage(
  502. sprintf(
  503. __(
  504. 'This view has at least this number of rows. Please refer to %sdocumentation%s.'
  505. ),
  506. '[doc@cfg_MaxExactCountViews]',
  507. '[/doc]'
  508. )
  509. )
  510. );
  511. }
  512. }
  513. return [
  514. $approxRows,
  515. $showSuperscript,
  516. ];
  517. }
  518. /**
  519. * Returns the replication status of the table.
  520. *
  521. * @param array $replicaInfo
  522. * @param string $table table name
  523. *
  524. * @return array
  525. */
  526. protected function getReplicationStatus($replicaInfo, string $table): array
  527. {
  528. $do = $ignored = false;
  529. if ($replicaInfo['status']) {
  530. $nbServReplicaDoDb = count($replicaInfo['Do_DB']);
  531. $nbServReplicaIgnoreDb = count($replicaInfo['Ignore_DB']);
  532. $searchDoDBInTruename = array_search($table, $replicaInfo['Do_DB']);
  533. $searchDoDBInDB = array_search($this->db, $replicaInfo['Do_DB']);
  534. $do = (is_string($searchDoDBInTruename) && strlen($searchDoDBInTruename) > 0)
  535. || (is_string($searchDoDBInDB) && strlen($searchDoDBInDB) > 0)
  536. || ($nbServReplicaDoDb == 0 && $nbServReplicaIgnoreDb == 0)
  537. || $this->hasTable($replicaInfo['Wild_Do_Table'], $table);
  538. $searchDb = array_search($this->db, $replicaInfo['Ignore_DB']);
  539. $searchTable = array_search($table, $replicaInfo['Ignore_Table']);
  540. $ignored = (is_string($searchTable) && strlen($searchTable) > 0)
  541. || (is_string($searchDb) && strlen($searchDb) > 0)
  542. || $this->hasTable($replicaInfo['Wild_Ignore_Table'], $table);
  543. }
  544. return [
  545. $do,
  546. $ignored,
  547. ];
  548. }
  549. /**
  550. * Function to check if a table is already in favorite list.
  551. *
  552. * @param string $currentTable current table
  553. */
  554. protected function checkFavoriteTable(string $currentTable): bool
  555. {
  556. $recentFavoriteTables = RecentFavoriteTable::getInstance('favorite');
  557. foreach ($recentFavoriteTables->getTables() as $value) {
  558. if ($value['db'] == $this->db && $value['table'] == $currentTable) {
  559. return true;
  560. }
  561. }
  562. return false;
  563. }
  564. /**
  565. * Find table with truename
  566. *
  567. * @param array $db DB to look into
  568. * @param string $truename Table name
  569. */
  570. protected function hasTable(array $db, $truename): bool
  571. {
  572. foreach ($db as $dbTable) {
  573. if (
  574. $this->db == $this->replication->extractDbOrTable($dbTable)
  575. && preg_match(
  576. '@^' .
  577. preg_quote(mb_substr($this->replication->extractDbOrTable($dbTable, 'table'), 0, -1), '@') . '@',
  578. $truename
  579. )
  580. ) {
  581. return true;
  582. }
  583. }
  584. return false;
  585. }
  586. /**
  587. * Get the value set for ENGINE table,
  588. *
  589. * @internal param bool $table_is_view whether table is view or not
  590. *
  591. * @param array $currentTable current table
  592. * @param int $sumSize total table size
  593. * @param int $overheadSize overhead size
  594. *
  595. * @return array
  596. */
  597. protected function getStuffForEngineTypeTable(
  598. array $currentTable,
  599. $sumSize,
  600. $overheadSize
  601. ) {
  602. $formattedSize = '-';
  603. $unit = '';
  604. $formattedOverhead = '';
  605. $overheadUnit = '';
  606. $tableIsView = false;
  607. switch ($currentTable['ENGINE']) {
  608. // MyISAM, ISAM or Heap table: Row count, data size and index size
  609. // are accurate; data size is accurate for ARCHIVE
  610. case 'MyISAM':
  611. case 'ISAM':
  612. case 'HEAP':
  613. case 'MEMORY':
  614. case 'ARCHIVE':
  615. case 'Aria':
  616. case 'Maria':
  617. [
  618. $currentTable,
  619. $formattedSize,
  620. $unit,
  621. $formattedOverhead,
  622. $overheadUnit,
  623. $overheadSize,
  624. $sumSize,
  625. ] = $this->getValuesForAriaTable(
  626. $currentTable,
  627. $sumSize,
  628. $overheadSize,
  629. $formattedSize,
  630. $unit,
  631. $formattedOverhead,
  632. $overheadUnit
  633. );
  634. break;
  635. case 'InnoDB':
  636. case 'PBMS':
  637. case 'TokuDB':
  638. case 'ROCKSDB':
  639. // InnoDB table: Row count is not accurate but data and index sizes are.
  640. // PBMS table in Drizzle: TABLE_ROWS is taken from table cache,
  641. // so it may be unavailable
  642. [$currentTable, $formattedSize, $unit, $sumSize] = $this->getValuesForInnodbTable(
  643. $currentTable,
  644. $sumSize
  645. );
  646. break;
  647. // Mysql 5.0.x (and lower) uses MRG_MyISAM
  648. // and MySQL 5.1.x (and higher) uses MRG_MYISAM
  649. // Both are aliases for MERGE
  650. case 'MRG_MyISAM':
  651. case 'MRG_MYISAM':
  652. case 'MERGE':
  653. case 'BerkeleyDB':
  654. // Merge or BerkleyDB table: Only row count is accurate.
  655. if ($this->isShowStats) {
  656. $formattedSize = ' - ';
  657. $unit = '';
  658. }
  659. break;
  660. // for a view, the ENGINE is sometimes reported as null,
  661. // or on some servers it's reported as "SYSTEM VIEW"
  662. case null:
  663. case 'SYSTEM VIEW':
  664. // possibly a view, do nothing
  665. break;
  666. case 'Mroonga':
  667. // The idea is to show the size only if Mroonga is available,
  668. // in other case the old unknown message will appear
  669. if (StorageEngine::hasMroongaEngine()) {
  670. [$currentTable, $formattedSize, $unit, $sumSize] = $this->getValuesForMroongaTable(
  671. $currentTable,
  672. $sumSize
  673. );
  674. break;
  675. }
  676. // no break, go to default case
  677. default:
  678. // Unknown table type.
  679. if ($this->isShowStats) {
  680. $formattedSize = __('unknown');
  681. $unit = '';
  682. }
  683. }
  684. if ($currentTable['TABLE_TYPE'] === 'VIEW' || $currentTable['TABLE_TYPE'] === 'SYSTEM VIEW') {
  685. // countRecords() takes care of $cfg['MaxExactCountViews']
  686. $currentTable['TABLE_ROWS'] = $this->dbi
  687. ->getTable($this->db, $currentTable['TABLE_NAME'])
  688. ->countRecords(true);
  689. $tableIsView = true;
  690. }
  691. return [
  692. $currentTable,
  693. $formattedSize,
  694. $unit,
  695. $formattedOverhead,
  696. $overheadUnit,
  697. $overheadSize,
  698. $tableIsView,
  699. $sumSize,
  700. ];
  701. }
  702. /**
  703. * Get values for ARIA/MARIA tables
  704. *
  705. * @param array $currentTable current table
  706. * @param int $sumSize sum size
  707. * @param int $overheadSize overhead size
  708. * @param int $formattedSize formatted size
  709. * @param string $unit unit
  710. * @param int $formattedOverhead overhead formatted
  711. * @param string $overheadUnit overhead unit
  712. *
  713. * @return array
  714. */
  715. protected function getValuesForAriaTable(
  716. array $currentTable,
  717. $sumSize,
  718. $overheadSize,
  719. $formattedSize,
  720. $unit,
  721. $formattedOverhead,
  722. $overheadUnit
  723. ) {
  724. if ($this->dbIsSystemSchema) {
  725. $currentTable['Rows'] = $this->dbi
  726. ->getTable($this->db, $currentTable['Name'])
  727. ->countRecords();
  728. }
  729. if ($this->isShowStats) {
  730. /** @var int $tblsize */
  731. $tblsize = $currentTable['Data_length']
  732. + $currentTable['Index_length'];
  733. $sumSize += $tblsize;
  734. [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, $tblsize > 0 ? 1 : 0);
  735. if (isset($currentTable['Data_free']) && $currentTable['Data_free'] > 0) {
  736. [$formattedOverhead, $overheadUnit] = Util::formatByteDown(
  737. $currentTable['Data_free'],
  738. 3,
  739. ($currentTable['Data_free'] > 0 ? 1 : 0)
  740. );
  741. $overheadSize += $currentTable['Data_free'];
  742. }
  743. }
  744. return [
  745. $currentTable,
  746. $formattedSize,
  747. $unit,
  748. $formattedOverhead,
  749. $overheadUnit,
  750. $overheadSize,
  751. $sumSize,
  752. ];
  753. }
  754. /**
  755. * Get values for InnoDB table
  756. *
  757. * @param array $currentTable current table
  758. * @param int $sumSize sum size
  759. *
  760. * @return array
  761. */
  762. protected function getValuesForInnodbTable(
  763. array $currentTable,
  764. $sumSize
  765. ) {
  766. $formattedSize = $unit = '';
  767. if (
  768. (in_array($currentTable['ENGINE'], ['InnoDB', 'TokuDB'])
  769. && $currentTable['TABLE_ROWS'] < $GLOBALS['cfg']['MaxExactCount'])
  770. || ! isset($currentTable['TABLE_ROWS'])
  771. ) {
  772. $currentTable['COUNTED'] = true;
  773. $currentTable['TABLE_ROWS'] = $this->dbi
  774. ->getTable($this->db, $currentTable['TABLE_NAME'])
  775. ->countRecords(true);
  776. } else {
  777. $currentTable['COUNTED'] = false;
  778. }
  779. if ($this->isShowStats) {
  780. /** @var int $tblsize */
  781. $tblsize = $currentTable['Data_length']
  782. + $currentTable['Index_length'];
  783. $sumSize += $tblsize;
  784. [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, ($tblsize > 0 ? 1 : 0));
  785. }
  786. return [
  787. $currentTable,
  788. $formattedSize,
  789. $unit,
  790. $sumSize,
  791. ];
  792. }
  793. /**
  794. * Get values for Mroonga table
  795. *
  796. * @param array $currentTable current table
  797. * @param int $sumSize sum size
  798. *
  799. * @return array
  800. */
  801. protected function getValuesForMroongaTable(
  802. array $currentTable,
  803. int $sumSize
  804. ): array {
  805. $formattedSize = '';
  806. $unit = '';
  807. if ($this->isShowStats) {
  808. /** @var int $tblsize */
  809. $tblsize = $currentTable['Data_length'] + $currentTable['Index_length'];
  810. $sumSize += $tblsize;
  811. [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, ($tblsize > 0 ? 1 : 0));
  812. }
  813. return [
  814. $currentTable,
  815. $formattedSize,
  816. $unit,
  817. $sumSize,
  818. ];
  819. }
  820. }