diff --git a/ui/lib/apps/SlowQuery/pages/Detail/index.tsx b/ui/lib/apps/SlowQuery/pages/Detail/index.tsx index 358d23cfd0..8052a5957f 100644 --- a/ui/lib/apps/SlowQuery/pages/Detail/index.tsx +++ b/ui/lib/apps/SlowQuery/pages/Detail/index.tsx @@ -8,7 +8,7 @@ import { useLocalStorageState } from '@umijs/hooks' import client from '@lib/client' import { useClientRequest } from '@lib/utils/useClientRequest' import { buildQueryFn, parseQueryFn } from '@lib/utils/query' -import formatSql from '@lib/utils/formatSql' +import formatSql from '@lib/utils/sqlFormatter' import { AnimatedSkeleton, CardTabs, diff --git a/ui/lib/apps/Statement/pages/Detail/PlanDetail.tsx b/ui/lib/apps/Statement/pages/Detail/PlanDetail.tsx index b686c63345..bb80fce1b6 100644 --- a/ui/lib/apps/Statement/pages/Detail/PlanDetail.tsx +++ b/ui/lib/apps/Statement/pages/Detail/PlanDetail.tsx @@ -16,7 +16,7 @@ import { } from '@lib/components' import { useClientRequest } from '@lib/utils/useClientRequest' import client from '@lib/client' -import formatSql from '@lib/utils/formatSql' +import formatSql from '@lib/utils/sqlFormatter' import { IPageQuery } from '.' import TabBasic from './PlanDetailTabBasic' diff --git a/ui/lib/apps/Statement/pages/Detail/index.tsx b/ui/lib/apps/Statement/pages/Detail/index.tsx index 8f97175764..ca91263f02 100644 --- a/ui/lib/apps/Statement/pages/Detail/index.tsx +++ b/ui/lib/apps/Statement/pages/Detail/index.tsx @@ -20,7 +20,7 @@ import { TextWithInfo, } from '@lib/components' import CopyLink from '@lib/components/CopyLink' -import formatSql from '@lib/utils/formatSql' +import formatSql from '@lib/utils/sqlFormatter' import { buildQueryFn, parseQueryFn } from '@lib/utils/query' import { useClientRequest } from '@lib/utils/useClientRequest' diff --git a/ui/lib/components/HighlightSQL/index.tsx b/ui/lib/components/HighlightSQL/index.tsx index 687fbcf725..7e6b2b5e2a 100644 --- a/ui/lib/components/HighlightSQL/index.tsx +++ b/ui/lib/components/HighlightSQL/index.tsx @@ -5,7 +5,7 @@ import sql from 'react-syntax-highlighter/dist/esm/languages/hljs/sql' import lightTheme from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-light' import darkTheme from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-dark' import Pre from '../Pre' -import formatSql from '@lib/utils/formatSql' +import formatSql from '@lib/utils/sqlFormatter' import moize from 'moize' SyntaxHighlighter.registerLanguage('sql', sql) diff --git a/ui/lib/utils/formatSql.ts b/ui/lib/utils/formatSql.ts deleted file mode 100644 index 62bdb9a8e2..0000000000 --- a/ui/lib/utils/formatSql.ts +++ /dev/null @@ -1,5 +0,0 @@ -import sqlFormatter from 'sql-formatter-plus-plus' - -export default function formatSql(sql?: string): string { - return sqlFormatter.format(sql || '', { uppercase: true }) -} diff --git a/ui/lib/utils/sqlFormatter/TiDBSQLFormatter.ts b/ui/lib/utils/sqlFormatter/TiDBSQLFormatter.ts new file mode 100644 index 0000000000..e91656fe87 --- /dev/null +++ b/ui/lib/utils/sqlFormatter/TiDBSQLFormatter.ts @@ -0,0 +1,371 @@ +// This file is copied from 'sql-formatter-plus-plus/src/languages/StandardSqlFormatter.js'. +// And changed the following lines: +// `namedPlaceholderTypes: ['@', ':'],` => `namedPlaceholderTypes: [':'],` +// Add the following line which copied from Db2Formatter.js: +// `specialWordChars: ['@'],` + +import Formatter from 'sql-formatter-plus-plus/src/core/Formatter' +import Tokenizer from 'sql-formatter-plus-plus/src/core/Tokenizer' + +const reservedWords = [ + 'ACCESSIBLE', + 'ACTION', + 'AGAINST', + 'AGGREGATE', + 'ALGORITHM', + 'ALL', + 'ALTER', + 'ANALYSE', + 'ANALYZE', + 'AS', + 'ASC', + 'AUTOCOMMIT', + 'AUTO_INCREMENT', + 'BACKUP', + 'BEGIN', + 'BETWEEN', + 'BINLOG', + 'BOTH', + 'CASCADE', + 'CASE', + 'CHANGE', + 'CHANGED', + 'CHARACTER SET', + 'CHARSET', + 'CHECK', + 'CHECKSUM', + 'COLLATE', + 'COLLATION', + 'COLUMN', + 'COLUMNS', + 'COMMENT', + 'COMMIT', + 'COMMITTED', + 'COMPRESSED', + 'CONCURRENT', + 'CONSTRAINT', + 'CONTAINS', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT_TIMESTAMP', + 'DATABASE', + 'DATABASES', + 'DAY', + 'DAY_HOUR', + 'DAY_MINUTE', + 'DAY_SECOND', + 'DEFAULT', + 'DEFINER', + 'DELAYED', + 'DELETE', + 'DESC', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISTINCT', + 'DISTINCTROW', + 'DIV', + 'DO', + 'DROP', + 'DUMPFILE', + 'DUPLICATE', + 'DYNAMIC', + 'ELSE', + 'ENCLOSED', + 'END', + 'ENGINE', + 'ENGINES', + 'ENGINE_TYPE', + 'ESCAPE', + 'ESCAPED', + 'EVENTS', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXPLAIN', + 'EXTENDED', + 'FAST', + 'FETCH', + 'FIELDS', + 'FILE', + 'FIRST', + 'FIXED', + 'FLUSH', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FULL', + 'FULLTEXT', + 'FUNCTION', + 'GLOBAL', + 'GRANT', + 'GRANTS', + 'GROUP_CONCAT', + 'HEAP', + 'HIGH_PRIORITY', + 'HOSTS', + 'HOUR', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'IDENTIFIED', + 'IF', + 'IFNULL', + 'IGNORE', + 'IN', + 'INDEX', + 'INDEXES', + 'INFILE', + 'INSERT', + 'INSERT_ID', + 'INSERT_METHOD', + 'INTERVAL', + 'INTO', + 'INVOKER', + 'IS', + 'ISOLATION', + 'KEY', + 'KEYS', + 'KILL', + 'LAST_INSERT_ID', + 'LEADING', + 'LEVEL', + 'LIKE', + 'LINEAR', + 'LINES', + 'LOAD', + 'LOCAL', + 'LOCK', + 'LOCKS', + 'LOGS', + 'LOW_PRIORITY', + 'MARIA', + 'MASTER', + 'MASTER_CONNECT_RETRY', + 'MASTER_HOST', + 'MASTER_LOG_FILE', + 'MATCH', + 'MAX_CONNECTIONS_PER_HOUR', + 'MAX_QUERIES_PER_HOUR', + 'MAX_ROWS', + 'MAX_UPDATES_PER_HOUR', + 'MAX_USER_CONNECTIONS', + 'MEDIUM', + 'MERGE', + 'MINUTE', + 'MINUTE_SECOND', + 'MIN_ROWS', + 'MODE', + 'MODIFY', + 'MONTH', + 'MRG_MYISAM', + 'MYISAM', + 'NAMES', + 'NATURAL', + 'NOT', + 'NOW()', + 'NULL', + 'OFFSET', + 'ON DELETE', + 'ON UPDATE', + 'ON', + 'ONLY', + 'OPEN', + 'OPTIMIZE', + 'OPTION', + 'OPTIONALLY', + 'OUTFILE', + 'PACK_KEYS', + 'PAGE', + 'PARTIAL', + 'PARTITION', + 'PARTITIONS', + 'PASSWORD', + 'PRIMARY', + 'PRIVILEGES', + 'PROCEDURE', + 'PROCESS', + 'PROCESSLIST', + 'PURGE', + 'QUICK', + 'RAID0', + 'RAID_CHUNKS', + 'RAID_CHUNKSIZE', + 'RAID_TYPE', + 'RANGE', + 'READ', + 'READ_ONLY', + 'READ_WRITE', + 'REFERENCES', + 'REGEXP', + 'RELOAD', + 'RENAME', + 'REPAIR', + 'REPEATABLE', + 'REPLACE', + 'REPLICATION', + 'RESET', + 'RESTORE', + 'RESTRICT', + 'RETURN', + 'RETURNS', + 'REVOKE', + 'RLIKE', + 'ROLLBACK', + 'ROW', + 'ROWS', + 'ROW_FORMAT', + 'SECOND', + 'SECURITY', + 'SEPARATOR', + 'SERIALIZABLE', + 'SESSION', + 'SHARE', + 'SHOW', + 'SHUTDOWN', + 'SLAVE', + 'SONAME', + 'SOUNDS', + 'SQL', + 'SQL_AUTO_IS_NULL', + 'SQL_BIG_RESULT', + 'SQL_BIG_SELECTS', + 'SQL_BIG_TABLES', + 'SQL_BUFFER_RESULT', + 'SQL_CACHE', + 'SQL_CALC_FOUND_ROWS', + 'SQL_LOG_BIN', + 'SQL_LOG_OFF', + 'SQL_LOG_UPDATE', + 'SQL_LOW_PRIORITY_UPDATES', + 'SQL_MAX_JOIN_SIZE', + 'SQL_NO_CACHE', + 'SQL_QUOTE_SHOW_CREATE', + 'SQL_SAFE_UPDATES', + 'SQL_SELECT_LIMIT', + 'SQL_SLAVE_SKIP_COUNTER', + 'SQL_SMALL_RESULT', + 'SQL_WARNINGS', + 'START', + 'STARTING', + 'STATUS', + 'STOP', + 'STORAGE', + 'STRAIGHT_JOIN', + 'STRING', + 'STRIPED', + 'SUPER', + 'TABLE', + 'TABLES', + 'TEMPORARY', + 'TERMINATED', + 'THEN', + 'TO', + 'TRAILING', + 'TRANSACTIONAL', + 'TRUE', + 'TRUNCATE', + 'TYPE', + 'TYPES', + 'UNCOMMITTED', + 'UNIQUE', + 'UNLOCK', + 'UNSIGNED', + 'USAGE', + 'USE', + 'USING', + 'VARIABLES', + 'VIEW', + 'WHEN', + 'WITH', + 'WORK', + 'WRITE', + 'YEAR_MONTH', +] + +const reservedTopLevelWords = [ + 'ADD', + 'AFTER', + 'ALTER COLUMN', + 'ALTER TABLE', + 'DELETE FROM', + 'EXCEPT', + 'FETCH FIRST', + 'FROM', + 'GROUP BY', + 'GO', + 'HAVING', + 'INSERT INTO', + 'INSERT', + 'LIMIT', + 'MODIFY', + 'ORDER BY', + 'SELECT', + 'SET CURRENT SCHEMA', + 'SET SCHEMA', + 'SET', + 'UPDATE', + 'VALUES', + 'WHERE', +] + +const reservedTopLevelWordsNoIndent = [ + 'INTERSECT', + 'INTERSECT ALL', + 'MINUS', + 'UNION', + 'UNION ALL', +] + +const reservedNewlineWords = [ + 'AND', + 'CROSS APPLY', + 'CROSS JOIN', + 'ELSE', + 'INNER JOIN', + 'JOIN', + 'LEFT JOIN', + 'LEFT OUTER JOIN', + 'OR', + 'OUTER APPLY', + 'OUTER JOIN', + 'RIGHT JOIN', + 'RIGHT OUTER JOIN', + 'WHEN', + 'XOR', +] + +let tokenizer + +export default class TiDBSQLFormatter { + /** + * @param {Object} cfg Different set of configurations + */ + constructor(public cfg) { + this.cfg = cfg + } + + /** + * Format the whitespace in a Standard SQL string to make it easier to read + * + * @param {String} query The Standard SQL string + * @return {String} formatted string + */ + format(query) { + if (!tokenizer) { + tokenizer = new Tokenizer({ + reservedWords, + reservedTopLevelWords, + reservedNewlineWords, + reservedTopLevelWordsNoIndent, + stringTypes: [`""`, "N''", "''", '``', '[]'], + openParens: ['(', 'CASE'], + closeParens: [')', 'END'], + indexedPlaceholderTypes: ['?'], + namedPlaceholderTypes: [':'], + lineCommentTypes: ['#', '--'], + specialWordChars: ['@'], + }) + } + return new Formatter(this.cfg, tokenizer).format(query) + } +} diff --git a/ui/lib/utils/sqlFormatter/index.ts b/ui/lib/utils/sqlFormatter/index.ts new file mode 100644 index 0000000000..9405055f5f --- /dev/null +++ b/ui/lib/utils/sqlFormatter/index.ts @@ -0,0 +1,7 @@ +import TiDBSQLFormatter from './TiDBSQLFormatter' + +const mySqlFormatter = new TiDBSQLFormatter({ uppercase: true }) + +export default function formatSql(sql?: string): string { + return mySqlFormatter.format(sql || '') +}