You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
15 KiB
408 lines
15 KiB
2 years ago
|
<?php
|
||
|
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
namespace PhpMyAdmin\Query;
|
||
|
|
||
|
use PhpMyAdmin\Util;
|
||
|
|
||
|
use function count;
|
||
|
use function implode;
|
||
|
use function is_array;
|
||
|
use function sprintf;
|
||
|
|
||
|
/**
|
||
|
* Handles generating SQL queries
|
||
|
*/
|
||
|
class Generator
|
||
|
{
|
||
|
/**
|
||
|
* returns a segment of the SQL WHERE clause regarding table name and type
|
||
|
*
|
||
|
* @param array|string $escapedTableOrTables table(s)
|
||
|
* @param bool $tblIsGroup $table is a table group
|
||
|
* @param string $tableType whether table or view
|
||
|
*
|
||
|
* @return string a segment of the WHERE clause
|
||
|
*/
|
||
|
public static function getTableCondition(
|
||
|
$escapedTableOrTables,
|
||
|
bool $tblIsGroup,
|
||
|
?string $tableType
|
||
|
): string {
|
||
|
// get table information from information_schema
|
||
|
if ($escapedTableOrTables) {
|
||
|
if (is_array($escapedTableOrTables)) {
|
||
|
$sqlWhereTable = 'AND t.`TABLE_NAME` '
|
||
|
. Util::getCollateForIS() . ' IN (\''
|
||
|
. implode('\', \'', $escapedTableOrTables)
|
||
|
. '\')';
|
||
|
} elseif ($tblIsGroup === true) {
|
||
|
$sqlWhereTable = 'AND t.`TABLE_NAME` LIKE \''
|
||
|
. Util::escapeMysqlWildcards($escapedTableOrTables)
|
||
|
. '%\'';
|
||
|
} else {
|
||
|
$sqlWhereTable = 'AND t.`TABLE_NAME` '
|
||
|
. Util::getCollateForIS() . ' = \''
|
||
|
. $escapedTableOrTables . '\'';
|
||
|
}
|
||
|
} else {
|
||
|
$sqlWhereTable = '';
|
||
|
}
|
||
|
|
||
|
if ($tableType) {
|
||
|
if ($tableType === 'view') {
|
||
|
$sqlWhereTable .= " AND t.`TABLE_TYPE` NOT IN ('BASE TABLE', 'SYSTEM VERSIONED')";
|
||
|
} elseif ($tableType === 'table') {
|
||
|
$sqlWhereTable .= " AND t.`TABLE_TYPE` IN ('BASE TABLE', 'SYSTEM VERSIONED')";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $sqlWhereTable;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* returns the beginning of the SQL statement to fetch the list of tables
|
||
|
*
|
||
|
* @param string[] $thisDatabases databases to list
|
||
|
* @param string $sqlWhereTable additional condition
|
||
|
*
|
||
|
* @return string the SQL statement
|
||
|
*/
|
||
|
public static function getSqlForTablesFull(array $thisDatabases, string $sqlWhereTable): string
|
||
|
{
|
||
|
return 'SELECT *,'
|
||
|
. ' `TABLE_SCHEMA` AS `Db`,'
|
||
|
. ' `TABLE_NAME` AS `Name`,'
|
||
|
. ' `TABLE_TYPE` AS `TABLE_TYPE`,'
|
||
|
. ' `ENGINE` AS `Engine`,'
|
||
|
. ' `ENGINE` AS `Type`,'
|
||
|
. ' `VERSION` AS `Version`,'
|
||
|
. ' `ROW_FORMAT` AS `Row_format`,'
|
||
|
. ' `TABLE_ROWS` AS `Rows`,'
|
||
|
. ' `AVG_ROW_LENGTH` AS `Avg_row_length`,'
|
||
|
. ' `DATA_LENGTH` AS `Data_length`,'
|
||
|
. ' `MAX_DATA_LENGTH` AS `Max_data_length`,'
|
||
|
. ' `INDEX_LENGTH` AS `Index_length`,'
|
||
|
. ' `DATA_FREE` AS `Data_free`,'
|
||
|
. ' `AUTO_INCREMENT` AS `Auto_increment`,'
|
||
|
. ' `CREATE_TIME` AS `Create_time`,'
|
||
|
. ' `UPDATE_TIME` AS `Update_time`,'
|
||
|
. ' `CHECK_TIME` AS `Check_time`,'
|
||
|
. ' `TABLE_COLLATION` AS `Collation`,'
|
||
|
. ' `CHECKSUM` AS `Checksum`,'
|
||
|
. ' `CREATE_OPTIONS` AS `Create_options`,'
|
||
|
. ' `TABLE_COMMENT` AS `Comment`'
|
||
|
. ' FROM `information_schema`.`TABLES` t'
|
||
|
. ' WHERE `TABLE_SCHEMA` ' . Util::getCollateForIS()
|
||
|
. ' IN (\'' . implode("', '", $thisDatabases) . '\')'
|
||
|
. ' ' . $sqlWhereTable;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns SQL for fetching information on table indexes (SHOW INDEXES)
|
||
|
*
|
||
|
* @param string $database name of database
|
||
|
* @param string $table name of the table whose indexes are to be retrieved
|
||
|
* @param string $where additional conditions for WHERE
|
||
|
*
|
||
|
* @return string SQL for getting indexes
|
||
|
*/
|
||
|
public static function getTableIndexesSql(
|
||
|
string $database,
|
||
|
string $table,
|
||
|
?string $where = null
|
||
|
): string {
|
||
|
$sql = 'SHOW INDEXES FROM ' . Util::backquote($database) . '.'
|
||
|
. Util::backquote($table);
|
||
|
if ($where) {
|
||
|
$sql .= ' WHERE (' . $where . ')';
|
||
|
}
|
||
|
|
||
|
return $sql;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns SQL query for fetching columns for a table
|
||
|
*
|
||
|
* @param string $database name of database
|
||
|
* @param string $table name of table to retrieve columns from
|
||
|
* @param string|null $escapedColumn name of column, null to show all columns
|
||
|
* @param bool $full whether to return full info or only column names
|
||
|
*/
|
||
|
public static function getColumnsSql(
|
||
|
string $database,
|
||
|
string $table,
|
||
|
?string $escapedColumn = null,
|
||
|
bool $full = false
|
||
|
): string {
|
||
|
return 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM '
|
||
|
. Util::backquote($database) . '.' . Util::backquote($table)
|
||
|
. ($escapedColumn !== null ? " LIKE '"
|
||
|
. $escapedColumn . "'" : '');
|
||
|
}
|
||
|
|
||
|
public static function getInformationSchemaRoutinesRequest(
|
||
|
string $escapedDb,
|
||
|
?string $routineType,
|
||
|
?string $escapedRoutineName
|
||
|
): string {
|
||
|
$query = 'SELECT'
|
||
|
. ' `ROUTINE_SCHEMA` AS `Db`,'
|
||
|
. ' `SPECIFIC_NAME` AS `Name`,'
|
||
|
. ' `ROUTINE_TYPE` AS `Type`,'
|
||
|
. ' `DEFINER` AS `Definer`,'
|
||
|
. ' `LAST_ALTERED` AS `Modified`,'
|
||
|
. ' `CREATED` AS `Created`,'
|
||
|
. ' `SECURITY_TYPE` AS `Security_type`,'
|
||
|
. ' `ROUTINE_COMMENT` AS `Comment`,'
|
||
|
. ' `CHARACTER_SET_CLIENT` AS `character_set_client`,'
|
||
|
. ' `COLLATION_CONNECTION` AS `collation_connection`,'
|
||
|
. ' `DATABASE_COLLATION` AS `Database Collation`,'
|
||
|
. ' `DTD_IDENTIFIER`'
|
||
|
. ' FROM `information_schema`.`ROUTINES`'
|
||
|
. ' WHERE `ROUTINE_SCHEMA` ' . Util::getCollateForIS()
|
||
|
. " = '" . $escapedDb . "'";
|
||
|
if ($routineType !== null) {
|
||
|
$query .= " AND `ROUTINE_TYPE` = '" . $routineType . "'";
|
||
|
}
|
||
|
|
||
|
if ($escapedRoutineName !== null) {
|
||
|
$query .= ' AND `SPECIFIC_NAME`'
|
||
|
. " = '" . $escapedRoutineName . "'";
|
||
|
}
|
||
|
|
||
|
return $query;
|
||
|
}
|
||
|
|
||
|
public static function getInformationSchemaEventsRequest(string $escapedDb, ?string $escapedEventName): string
|
||
|
{
|
||
|
$query = 'SELECT'
|
||
|
. ' `EVENT_SCHEMA` AS `Db`,'
|
||
|
. ' `EVENT_NAME` AS `Name`,'
|
||
|
. ' `DEFINER` AS `Definer`,'
|
||
|
. ' `TIME_ZONE` AS `Time zone`,'
|
||
|
. ' `EVENT_TYPE` AS `Type`,'
|
||
|
. ' `EXECUTE_AT` AS `Execute at`,'
|
||
|
. ' `INTERVAL_VALUE` AS `Interval value`,'
|
||
|
. ' `INTERVAL_FIELD` AS `Interval field`,'
|
||
|
. ' `STARTS` AS `Starts`,'
|
||
|
. ' `ENDS` AS `Ends`,'
|
||
|
. ' `STATUS` AS `Status`,'
|
||
|
. ' `ORIGINATOR` AS `Originator`,'
|
||
|
. ' `CHARACTER_SET_CLIENT` AS `character_set_client`,'
|
||
|
. ' `COLLATION_CONNECTION` AS `collation_connection`, '
|
||
|
. '`DATABASE_COLLATION` AS `Database Collation`'
|
||
|
. ' FROM `information_schema`.`EVENTS`'
|
||
|
. ' WHERE `EVENT_SCHEMA` ' . Util::getCollateForIS()
|
||
|
. " = '" . $escapedDb . "'";
|
||
|
if ($escapedEventName !== null) {
|
||
|
$query .= ' AND `EVENT_NAME`'
|
||
|
. " = '" . $escapedEventName . "'";
|
||
|
}
|
||
|
|
||
|
return $query;
|
||
|
}
|
||
|
|
||
|
public static function getInformationSchemaTriggersRequest(string $escapedDb, ?string $escapedTable): string
|
||
|
{
|
||
|
$query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION'
|
||
|
. ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT'
|
||
|
. ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER'
|
||
|
. ' FROM information_schema.TRIGGERS'
|
||
|
. ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '='
|
||
|
. ' \'' . $escapedDb . '\'';
|
||
|
|
||
|
if ($escapedTable !== null) {
|
||
|
$query .= ' AND EVENT_OBJECT_TABLE ' . Util::getCollateForIS()
|
||
|
. " = '" . $escapedTable . "';";
|
||
|
}
|
||
|
|
||
|
return $query;
|
||
|
}
|
||
|
|
||
|
public static function getInformationSchemaDataForCreateRequest(string $user, string $host): string
|
||
|
{
|
||
|
return 'SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` '
|
||
|
. "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND "
|
||
|
. "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
|
||
|
}
|
||
|
|
||
|
public static function getInformationSchemaDataForGranteeRequest(string $user, string $host): string
|
||
|
{
|
||
|
return 'SELECT 1 FROM ('
|
||
|
. 'SELECT `GRANTEE`, `IS_GRANTABLE` FROM '
|
||
|
. '`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION '
|
||
|
. 'SELECT `GRANTEE`, `IS_GRANTABLE` FROM '
|
||
|
. '`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION '
|
||
|
. 'SELECT `GRANTEE`, `IS_GRANTABLE` FROM '
|
||
|
. '`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION '
|
||
|
. 'SELECT `GRANTEE`, `IS_GRANTABLE` FROM '
|
||
|
. '`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t '
|
||
|
. "WHERE `IS_GRANTABLE` = 'YES' AND "
|
||
|
. "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1";
|
||
|
}
|
||
|
|
||
|
public static function getInformationSchemaForeignKeyConstraintsRequest(
|
||
|
string $escapedDatabase,
|
||
|
string $tablesListForQueryCsv
|
||
|
): string {
|
||
|
return 'SELECT'
|
||
|
. ' TABLE_NAME,'
|
||
|
. ' COLUMN_NAME,'
|
||
|
. ' REFERENCED_TABLE_NAME,'
|
||
|
. ' REFERENCED_COLUMN_NAME'
|
||
|
. ' FROM information_schema.key_column_usage'
|
||
|
. ' WHERE referenced_table_name IS NOT NULL'
|
||
|
. " AND TABLE_SCHEMA = '" . $escapedDatabase . "'"
|
||
|
. ' AND TABLE_NAME IN (' . $tablesListForQueryCsv . ')'
|
||
|
. ' AND REFERENCED_TABLE_NAME IN (' . $tablesListForQueryCsv . ');';
|
||
|
}
|
||
|
|
||
|
public static function getInformationSchemaDatabasesFullRequest(
|
||
|
bool $forceStats,
|
||
|
string $sqlWhereSchema,
|
||
|
string $sortBy,
|
||
|
string $sortOrder,
|
||
|
string $limit
|
||
|
): string {
|
||
|
$sql = 'SELECT *, CAST(BIN_NAME AS CHAR CHARACTER SET utf8) AS SCHEMA_NAME FROM (';
|
||
|
$sql .= 'SELECT BINARY s.SCHEMA_NAME AS BIN_NAME, s.DEFAULT_COLLATION_NAME';
|
||
|
if ($forceStats) {
|
||
|
$sql .= ','
|
||
|
. ' COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,'
|
||
|
. ' SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS,'
|
||
|
. ' SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH,'
|
||
|
. ' SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH,'
|
||
|
. ' SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH,'
|
||
|
. ' SUM(t.DATA_LENGTH + t.INDEX_LENGTH) AS SCHEMA_LENGTH,'
|
||
|
. ' SUM(IF(t.ENGINE <> \'InnoDB\', t.DATA_FREE, 0)) AS SCHEMA_DATA_FREE';
|
||
|
}
|
||
|
|
||
|
$sql .= ' FROM `information_schema`.SCHEMATA s ';
|
||
|
if ($forceStats) {
|
||
|
$sql .= ' LEFT JOIN `information_schema`.TABLES t ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME';
|
||
|
}
|
||
|
|
||
|
$sql .= $sqlWhereSchema
|
||
|
. ' GROUP BY BINARY s.SCHEMA_NAME, s.DEFAULT_COLLATION_NAME'
|
||
|
. ' ORDER BY ';
|
||
|
if ($sortBy === 'SCHEMA_NAME' || $sortBy === 'DEFAULT_COLLATION_NAME') {
|
||
|
$sql .= 'BINARY ';
|
||
|
}
|
||
|
|
||
|
$sql .= Util::backquote($sortBy)
|
||
|
. ' ' . $sortOrder
|
||
|
. $limit;
|
||
|
$sql .= ') a';
|
||
|
|
||
|
return $sql;
|
||
|
}
|
||
|
|
||
|
public static function getInformationSchemaColumnsFullRequest(
|
||
|
?string $escapedDatabase,
|
||
|
?string $escapedTable,
|
||
|
?string $escapedColumn
|
||
|
): array {
|
||
|
$sqlWheres = [];
|
||
|
$arrayKeys = [];
|
||
|
|
||
|
// get columns information from information_schema
|
||
|
if ($escapedDatabase !== null) {
|
||
|
$sqlWheres[] = '`TABLE_SCHEMA` = \''
|
||
|
. $escapedDatabase . '\' ';
|
||
|
} else {
|
||
|
$arrayKeys[] = 'TABLE_SCHEMA';
|
||
|
}
|
||
|
|
||
|
if ($escapedTable !== null) {
|
||
|
$sqlWheres[] = '`TABLE_NAME` = \''
|
||
|
. $escapedTable . '\' ';
|
||
|
} else {
|
||
|
$arrayKeys[] = 'TABLE_NAME';
|
||
|
}
|
||
|
|
||
|
if ($escapedColumn !== null) {
|
||
|
$sqlWheres[] = '`COLUMN_NAME` = \''
|
||
|
. $escapedColumn . '\' ';
|
||
|
} else {
|
||
|
$arrayKeys[] = 'COLUMN_NAME';
|
||
|
}
|
||
|
|
||
|
// for PMA bc:
|
||
|
// `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]`
|
||
|
$sql = 'SELECT *,'
|
||
|
. ' `COLUMN_NAME` AS `Field`,'
|
||
|
. ' `COLUMN_TYPE` AS `Type`,'
|
||
|
. ' `COLLATION_NAME` AS `Collation`,'
|
||
|
. ' `IS_NULLABLE` AS `Null`,'
|
||
|
. ' `COLUMN_KEY` AS `Key`,'
|
||
|
. ' `COLUMN_DEFAULT` AS `Default`,'
|
||
|
. ' `EXTRA` AS `Extra`,'
|
||
|
. ' `PRIVILEGES` AS `Privileges`,'
|
||
|
. ' `COLUMN_COMMENT` AS `Comment`'
|
||
|
. ' FROM `information_schema`.`COLUMNS`';
|
||
|
|
||
|
if (count($sqlWheres)) {
|
||
|
$sql .= "\n" . ' WHERE ' . implode(' AND ', $sqlWheres);
|
||
|
}
|
||
|
|
||
|
return [$sql, $arrayKeys];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Function to get sql query for renaming the index using SQL RENAME INDEX Syntax
|
||
|
*/
|
||
|
public static function getSqlQueryForIndexRename(
|
||
|
string $dbName,
|
||
|
string $tableName,
|
||
|
string $oldIndexName,
|
||
|
string $newIndexName
|
||
|
): string {
|
||
|
return sprintf(
|
||
|
'ALTER TABLE %s.%s RENAME INDEX %s TO %s;',
|
||
|
Util::backquote($dbName),
|
||
|
Util::backquote($tableName),
|
||
|
Util::backquote($oldIndexName),
|
||
|
Util::backquote($newIndexName)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Function to get sql query to re-order the table
|
||
|
*/
|
||
|
public static function getQueryForReorderingTable(
|
||
|
string $table,
|
||
|
string $orderField,
|
||
|
?string $order
|
||
|
): string {
|
||
|
return 'ALTER TABLE '
|
||
|
. Util::backquote($table)
|
||
|
. ' ORDER BY '
|
||
|
. Util::backquote($orderField)
|
||
|
. ($order === 'desc' ? ' DESC;' : ' ASC;');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Function to get sql query to partition the table
|
||
|
*
|
||
|
* @param string[] $partitionNames
|
||
|
*/
|
||
|
public static function getQueryForPartitioningTable(
|
||
|
string $table,
|
||
|
string $partitionOperation,
|
||
|
array $partitionNames
|
||
|
): string {
|
||
|
$sql_query = 'ALTER TABLE '
|
||
|
. Util::backquote($table) . ' '
|
||
|
. $partitionOperation
|
||
|
. ' PARTITION ';
|
||
|
|
||
|
if ($partitionOperation === 'COALESCE') {
|
||
|
return $sql_query . count($partitionNames);
|
||
|
}
|
||
|
|
||
|
return $sql_query . implode(', ', $partitionNames) . ';';
|
||
|
}
|
||
|
}
|