Compatibility.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Query;
  4. use PhpMyAdmin\DatabaseInterface;
  5. use PhpMyAdmin\Dbal\DbalInterface;
  6. use PhpMyAdmin\Util;
  7. use function in_array;
  8. use function is_string;
  9. use function strlen;
  10. use function strpos;
  11. use function strtoupper;
  12. use function substr;
  13. /**
  14. * Handles data compatibility from SQL query results
  15. */
  16. class Compatibility
  17. {
  18. /** @return mixed[][] */
  19. public static function getISCompatForGetTablesFull(array $eachTables, string $eachDatabase): array
  20. {
  21. foreach ($eachTables as $table_name => $_) {
  22. if (! isset($eachTables[$table_name]['Type']) && isset($eachTables[$table_name]['Engine'])) {
  23. // pma BC, same parts of PMA still uses 'Type'
  24. $eachTables[$table_name]['Type'] =& $eachTables[$table_name]['Engine'];
  25. } elseif (! isset($eachTables[$table_name]['Engine']) && isset($eachTables[$table_name]['Type'])) {
  26. // old MySQL reports Type, newer MySQL reports Engine
  27. $eachTables[$table_name]['Engine'] =& $eachTables[$table_name]['Type'];
  28. }
  29. // Compatibility with INFORMATION_SCHEMA output
  30. $eachTables[$table_name]['TABLE_SCHEMA'] = $eachDatabase;
  31. $eachTables[$table_name]['TABLE_NAME'] =& $eachTables[$table_name]['Name'];
  32. $eachTables[$table_name]['ENGINE'] =& $eachTables[$table_name]['Engine'];
  33. $eachTables[$table_name]['VERSION'] =& $eachTables[$table_name]['Version'];
  34. $eachTables[$table_name]['ROW_FORMAT'] =& $eachTables[$table_name]['Row_format'];
  35. $eachTables[$table_name]['TABLE_ROWS'] =& $eachTables[$table_name]['Rows'];
  36. $eachTables[$table_name]['AVG_ROW_LENGTH'] =& $eachTables[$table_name]['Avg_row_length'];
  37. $eachTables[$table_name]['DATA_LENGTH'] =& $eachTables[$table_name]['Data_length'];
  38. $eachTables[$table_name]['MAX_DATA_LENGTH'] =& $eachTables[$table_name]['Max_data_length'];
  39. $eachTables[$table_name]['INDEX_LENGTH'] =& $eachTables[$table_name]['Index_length'];
  40. $eachTables[$table_name]['DATA_FREE'] =& $eachTables[$table_name]['Data_free'];
  41. $eachTables[$table_name]['AUTO_INCREMENT'] =& $eachTables[$table_name]['Auto_increment'];
  42. $eachTables[$table_name]['CREATE_TIME'] =& $eachTables[$table_name]['Create_time'];
  43. $eachTables[$table_name]['UPDATE_TIME'] =& $eachTables[$table_name]['Update_time'];
  44. $eachTables[$table_name]['CHECK_TIME'] =& $eachTables[$table_name]['Check_time'];
  45. $eachTables[$table_name]['TABLE_COLLATION'] =& $eachTables[$table_name]['Collation'];
  46. $eachTables[$table_name]['CHECKSUM'] =& $eachTables[$table_name]['Checksum'];
  47. $eachTables[$table_name]['CREATE_OPTIONS'] =& $eachTables[$table_name]['Create_options'];
  48. $eachTables[$table_name]['TABLE_COMMENT'] =& $eachTables[$table_name]['Comment'];
  49. if (
  50. strtoupper($eachTables[$table_name]['Comment'] ?? '') === 'VIEW'
  51. && $eachTables[$table_name]['Engine'] == null
  52. ) {
  53. $eachTables[$table_name]['TABLE_TYPE'] = 'VIEW';
  54. } elseif ($eachDatabase === 'information_schema') {
  55. $eachTables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW';
  56. } else {
  57. /**
  58. * @todo difference between 'TEMPORARY' and 'BASE TABLE'
  59. * but how to detect?
  60. */
  61. $eachTables[$table_name]['TABLE_TYPE'] = 'BASE TABLE';
  62. }
  63. }
  64. return $eachTables;
  65. }
  66. public static function getISCompatForGetColumnsFull(array $columns, string $database, string $table): array
  67. {
  68. $ordinal_position = 1;
  69. foreach ($columns as $column_name => $_) {
  70. // Compatibility with INFORMATION_SCHEMA output
  71. $columns[$column_name]['COLUMN_NAME'] =& $columns[$column_name]['Field'];
  72. $columns[$column_name]['COLUMN_TYPE'] =& $columns[$column_name]['Type'];
  73. $columns[$column_name]['COLLATION_NAME'] =& $columns[$column_name]['Collation'];
  74. $columns[$column_name]['IS_NULLABLE'] =& $columns[$column_name]['Null'];
  75. $columns[$column_name]['COLUMN_KEY'] =& $columns[$column_name]['Key'];
  76. $columns[$column_name]['COLUMN_DEFAULT'] =& $columns[$column_name]['Default'];
  77. $columns[$column_name]['EXTRA'] =& $columns[$column_name]['Extra'];
  78. $columns[$column_name]['PRIVILEGES'] =& $columns[$column_name]['Privileges'];
  79. $columns[$column_name]['COLUMN_COMMENT'] =& $columns[$column_name]['Comment'];
  80. $columns[$column_name]['TABLE_CATALOG'] = null;
  81. $columns[$column_name]['TABLE_SCHEMA'] = $database;
  82. $columns[$column_name]['TABLE_NAME'] = $table;
  83. $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position;
  84. $colType = $columns[$column_name]['COLUMN_TYPE'];
  85. $colType = is_string($colType) ? $colType : '';
  86. $colTypePosComa = strpos($colType, '(');
  87. $colTypePosComa = $colTypePosComa !== false ? $colTypePosComa : strlen($colType);
  88. $columns[$column_name]['DATA_TYPE'] = substr($colType, 0, $colTypePosComa);
  89. /**
  90. * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE
  91. */
  92. $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null;
  93. /**
  94. * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH
  95. */
  96. $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null;
  97. $columns[$column_name]['NUMERIC_PRECISION'] = null;
  98. $columns[$column_name]['NUMERIC_SCALE'] = null;
  99. $colCollation = $columns[$column_name]['COLLATION_NAME'];
  100. $colCollation = is_string($colCollation) ? $colCollation : '';
  101. $colCollationPosUnderscore = strpos($colCollation, '_');
  102. $colCollationPosUnderscore = $colCollationPosUnderscore !== false
  103. ? $colCollationPosUnderscore
  104. : strlen($colCollation);
  105. $columns[$column_name]['CHARACTER_SET_NAME'] = substr($colCollation, 0, $colCollationPosUnderscore);
  106. $ordinal_position++;
  107. }
  108. return $columns;
  109. }
  110. public static function isMySqlOrPerconaDb(): bool
  111. {
  112. $serverType = Util::getServerType();
  113. return $serverType === 'MySQL' || $serverType === 'Percona Server';
  114. }
  115. public static function isMariaDb(): bool
  116. {
  117. $serverType = Util::getServerType();
  118. return $serverType === 'MariaDB';
  119. }
  120. public static function isCompatibleRenameIndex(int $serverVersion): bool
  121. {
  122. if (self::isMySqlOrPerconaDb()) {
  123. return $serverVersion >= 50700;
  124. }
  125. // @see https://mariadb.com/kb/en/alter-table/#rename-indexkey
  126. if (self::isMariaDb()) {
  127. return $serverVersion >= 100502;
  128. }
  129. return false;
  130. }
  131. public static function isIntegersLengthRestricted(DatabaseInterface $dbi): bool
  132. {
  133. // MySQL made restrictions on the integer types' length from versions >= 8.0.18
  134. // See: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-19.html
  135. $serverType = Util::getServerType();
  136. $serverVersion = $dbi->getVersion();
  137. return $serverType === 'MySQL' && $serverVersion >= 80018;
  138. }
  139. public static function supportsReferencesPrivilege(DatabaseInterface $dbi): bool
  140. {
  141. // See: https://mariadb.com/kb/en/grant/#table-privileges
  142. // Unused
  143. if ($dbi->isMariaDB()) {
  144. return false;
  145. }
  146. // https://dev.mysql.com/doc/refman/5.6/en/privileges-provided.html#priv_references
  147. // This privilege is unused before MySQL 5.6.22.
  148. // As of 5.6.22, creation of a foreign key constraint
  149. // requires at least one of the SELECT, INSERT, UPDATE, DELETE,
  150. // or REFERENCES privileges for the parent table.
  151. return $dbi->getVersion() >= 50622;
  152. }
  153. public static function isIntegersSupportLength(string $type, string $length, DatabaseInterface $dbi): bool
  154. {
  155. // MySQL Removed the Integer types' length from versions >= 8.0.18
  156. // except TINYINT(1).
  157. // See: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-19.html
  158. $integerTypes = ['SMALLINT', 'MEDIUMINT', 'INT', 'BIGINT'];
  159. $typeLengthNotAllowed = in_array($type, $integerTypes) || $type === 'TINYINT' && $length !== '1';
  160. return ! (self::isIntegersLengthRestricted($dbi) && $typeLengthNotAllowed);
  161. }
  162. /**
  163. * Returns whether the database server supports virtual columns
  164. */
  165. public static function isVirtualColumnsSupported(int $serverVersion): bool
  166. {
  167. // @see: https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html
  168. if (self::isMySqlOrPerconaDb()) {
  169. return $serverVersion >= 50706;
  170. }
  171. // @see https://mariadb.com/kb/en/changes-improvements-in-mariadb-52/#new-features
  172. if (self::isMariaDb()) {
  173. return $serverVersion >= 50200;
  174. }
  175. return false;
  176. }
  177. /**
  178. * Check whether the database supports UUID data type
  179. * true if uuid is supported
  180. */
  181. public static function isUUIDSupported(DatabaseInterface $dbi): bool
  182. {
  183. // @see: https://mariadb.com/kb/en/mariadb-1070-release-notes/#uuid
  184. return $dbi->isMariaDB() && $dbi->getVersion() >= 100700; // 10.7.0
  185. }
  186. /**
  187. * Returns whether the database server supports virtual columns
  188. */
  189. public static function supportsStoredKeywordForVirtualColumns(int $serverVersion): bool
  190. {
  191. // @see: https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html
  192. if (self::isMySqlOrPerconaDb()) {
  193. return $serverVersion >= 50706;
  194. }
  195. // @see https://mariadb.com/kb/en/generated-columns/#mysql-compatibility-support
  196. if (self::isMariaDb()) {
  197. return $serverVersion >= 100201;
  198. }
  199. return false;
  200. }
  201. /**
  202. * Returns whether the database server supports compressed columns
  203. */
  204. public static function supportsCompressedColumns(int $serverVersion): bool
  205. {
  206. // @see https://mariadb.com/kb/en/innodb-page-compression/#comment_1992
  207. // Comment: Page compression is only available in MariaDB >= 10.1. [...]
  208. if (self::isMariaDb()) {
  209. return $serverVersion >= 100100;
  210. }
  211. return false;
  212. }
  213. /**
  214. * @see https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html#mysqld-5-7-6-account-management
  215. * @see https://mariadb.com/kb/en/mariadb-1042-release-notes/#notable-changes
  216. *
  217. * @psalm-pure
  218. */
  219. public static function hasAccountLocking(bool $isMariaDb, int $version): bool
  220. {
  221. return $isMariaDb && $version >= 100402 || ! $isMariaDb && $version >= 50706;
  222. }
  223. /** @return non-empty-string */
  224. public static function getShowBinLogStatusStmt(DbalInterface $dbal): string
  225. {
  226. if ($dbal->isMySql() && $dbal->getVersion() >= 80200) {
  227. return 'SHOW BINARY LOG STATUS';
  228. }
  229. if ($dbal->isMariaDB() && $dbal->getVersion() >= 100502) {
  230. return 'SHOW BINLOG STATUS';
  231. }
  232. return 'SHOW MASTER STATUS';
  233. }
  234. }