diff --git a/README.md b/README.md index 0114ba0..6fd1486 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ This way you have sure is a valid query before trying to identify the types. ## Current Available Types +For the show statements, please refer to the [MySQL Docs about SHOW Statements](https://dev.mysql.com/doc/refman/8.0/en/show.html). + * INSERT * UPDATE * DELETE @@ -59,6 +61,38 @@ This way you have sure is a valid query before trying to identify the types. * ALTER_INDEX * ALTER_PROCEDURE * ANON_BLOCK (BigQuery and Oracle dialects only) +* SHOW_BINARY (MySQL and generic dialects only) +* SHOW_BINLOG (MySQL and generic dialects only) +* SHOW_CHARACTER (MySQL and generic dialects only) +* SHOW_COLLATION (MySQL and generic dialects only) +* SHOW_COLUMNS (MySQL and generic dialects only) +* SHOW_CREATE (MySQL and generic dialects only) +* SHOW_DATABASES (MySQL and generic dialects only) +* SHOW_ENGINE (MySQL and generic dialects only) +* SHOW_ENGINES (MySQL and generic dialects only) +* SHOW_ERRORS (MySQL and generic dialects only) +* SHOW_EVENTS (MySQL and generic dialects only) +* SHOW_FUNCTION (MySQL and generic dialects only) +* SHOW_GRANTS (MySQL and generic dialects only) +* SHOW_INDEX (MySQL and generic dialects only) +* SHOW_MASTER (MySQL and generic dialects only) +* SHOW_OPEN (MySQL and generic dialects only) +* SHOW_PLUGINS (MySQL and generic dialects only) +* SHOW_PRIVILEGES (MySQL and generic dialects only) +* SHOW_PROCEDURE (MySQL and generic dialects only) +* SHOW_PROCESSLIST (MySQL and generic dialects only) +* SHOW_PROFILE (MySQL and generic dialects only) +* SHOW_PROFILES (MySQL and generic dialects only) +* SHOW_RELAYLOG (MySQL and generic dialects only) +* SHOW_REPLICAS (MySQL and generic dialects only) +* SHOW_SLAVE (MySQL and generic dialects only) +* SHOW_REPLICA (MySQL and generic dialects only) +* SHOW_STATUS (MySQL and generic dialects only) +* SHOW_TABLE (MySQL and generic dialects only) +* SHOW_TABLES (MySQL and generic dialects only) +* SHOW_TRIGGERS (MySQL and generic dialects only) +* SHOW_VARIABLES (MySQL and generic dialects only) +* SHOW_WARNINGS (MySQL and generic dialects only) * UNKNOWN (only available if strict mode is disabled) ## Execution types diff --git a/src/defines.ts b/src/defines.ts index 2b45648..66e4dd3 100644 --- a/src/defines.ts +++ b/src/defines.ts @@ -7,7 +7,7 @@ export const DIALECTS = [ 'bigquery', 'generic', ] as const; -export type Dialect = typeof DIALECTS[number]; +export type Dialect = (typeof DIALECTS)[number]; export type StatementType = | 'INSERT' | 'UPDATE' @@ -22,6 +22,39 @@ export type StatementType = | 'CREATE_FUNCTION' | 'CREATE_INDEX' | 'CREATE_PROCEDURE' + | 'SHOW_BINARY' + | 'SHOW_BINLOG' + | 'SHOW_CHARACTER' + | 'SHOW_COLLATION' + | 'SHOW_CREATE' + | 'SHOW_ENGINE' + | 'SHOW_ENGINES' + | 'SHOW_ERRORS' + | 'SHOW_EVENTS' + | 'SHOW_FUNCTION' + | 'SHOW_GRANTS' + | 'SHOW_MASTER' + | 'SHOW_OPEN' + | 'SHOW_PLUGINS' + | 'SHOW_PRIVILEGES' + | 'SHOW_PROCEDURE' + | 'SHOW_PROCESSLIST' + | 'SHOW_PROFILE' + | 'SHOW_PROFILES' + | 'SHOW_RELAYLOG' + | 'SHOW_REPLICAS' + | 'SHOW_SLAVE' + | 'SHOW_REPLICA' + | 'SHOW_STATUS' + | 'SHOW_TRIGGERS' + | 'SHOW_VARIABLES' + | 'SHOW_WARNINGS' + | 'SHOW_DATABASES' + | 'SHOW_KEYS' + | 'SHOW_INDEX' + | 'SHOW_TABLE' + | 'SHOW_TABLES' + | 'SHOW_COLUMNS' | 'DROP_DATABASE' | 'DROP_SCHEMA' | 'DROP_TABLE' @@ -41,7 +74,7 @@ export type StatementType = | 'ANON_BLOCK' | 'UNKNOWN'; -export type ExecutionType = 'LISTING' | 'MODIFICATION' | 'ANON_BLOCK' | 'UNKNOWN'; +export type ExecutionType = 'LISTING' | 'MODIFICATION' | 'INFORMATION' | 'ANON_BLOCK' | 'UNKNOWN'; export interface IdentifyOptions { strict?: boolean; diff --git a/src/parser.ts b/src/parser.ts index 14dffa5..48ea882 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -37,6 +37,39 @@ export const EXECUTION_TYPES: Record = { CREATE_FUNCTION: 'MODIFICATION', CREATE_INDEX: 'MODIFICATION', CREATE_PROCEDURE: 'MODIFICATION', + SHOW_BINARY: 'LISTING', + SHOW_BINLOG: 'LISTING', + SHOW_CHARACTER: 'LISTING', + SHOW_COLLATION: 'LISTING', + SHOW_CREATE: 'LISTING', + SHOW_ENGINE: 'LISTING', + SHOW_ENGINES: 'LISTING', + SHOW_ERRORS: 'LISTING', + SHOW_EVENTS: 'LISTING', + SHOW_FUNCTION: 'LISTING', + SHOW_GRANTS: 'LISTING', + SHOW_MASTER: 'LISTING', + SHOW_OPEN: 'LISTING', + SHOW_PLUGINS: 'LISTING', + SHOW_PRIVILEGES: 'LISTING', + SHOW_PROCEDURE: 'LISTING', + SHOW_PROCESSLIST: 'LISTING', + SHOW_PROFILE: 'LISTING', + SHOW_PROFILES: 'LISTING', + SHOW_RELAYLOG: 'LISTING', + SHOW_REPLICAS: 'LISTING', + SHOW_SLAVE: 'LISTING', + SHOW_REPLICA: 'LISTING', + SHOW_STATUS: 'LISTING', + SHOW_TRIGGERS: 'LISTING', + SHOW_VARIABLES: 'LISTING', + SHOW_WARNINGS: 'LISTING', + SHOW_DATABASES: 'LISTING', + SHOW_KEYS: 'LISTING', + SHOW_INDEX: 'LISTING', + SHOW_TABLE: 'LISTING', // for SHOW TABLE STATUS + SHOW_TABLES: 'LISTING', + SHOW_COLUMNS: 'LISTING', DROP_DATABASE: 'MODIFICATION', DROP_SCHEMA: 'MODIFICATION', DROP_TABLE: 'MODIFICATION', @@ -265,6 +298,11 @@ function createStatementParserByToken( return createSelectStatementParser(options); case 'CREATE': return createCreateStatementParser(options); + case 'SHOW': + if (['mysql', 'generic'].includes(options.dialect)) { + return createShowStatementParser(options); + } + break; case 'DROP': return createDropStatementParser(options); case 'ALTER': @@ -580,6 +618,74 @@ function createTruncateStatementParser(options: ParseOptions) { return stateMachineStatementParser(statement, steps, options); } +function createShowStatementParser(options: ParseOptions) { + const statement = createInitialStatement(); + + const steps: Step[] = [ + { + preCanGoToNext: () => false, + validation: { + acceptTokens: [{ type: 'keyword', value: 'SHOW' }], + }, + add: (token) => { + if (statement.start < 0) { + statement.start = token.start; + } + }, + postCanGoToNext: () => true, + }, + // Database/Table/Columns/... + { + preCanGoToNext: () => false, + validation: { + requireBefore: ['whitespace'], + acceptTokens: [ + { type: 'keyword', value: 'DATABASES' }, + { type: 'keyword', value: 'DATABASE' }, + { type: 'keyword', value: 'KEYS' }, + { type: 'keyword', value: 'INDEX' }, + { type: 'keyword', value: 'COLUMNS' }, + { type: 'keyword', value: 'TABLES' }, + { type: 'keyword', value: 'TABLE' }, + { type: 'keyword', value: 'BINARY' }, + { type: 'keyword', value: 'BINLOG' }, + { type: 'keyword', value: 'CHARACTER' }, + { type: 'keyword', value: 'COLLATION' }, + { type: 'keyword', value: 'CREATE' }, + { type: 'keyword', value: 'ENGINE' }, + { type: 'keyword', value: 'ENGINES' }, + { type: 'keyword', value: 'ERRORS' }, + { type: 'keyword', value: 'EVENTS' }, + { type: 'keyword', value: 'FUNCTION' }, + { type: 'keyword', value: 'GRANTS' }, + { type: 'keyword', value: 'MASTER' }, + { type: 'keyword', value: 'OPEN' }, + { type: 'keyword', value: 'PLUGINS' }, + { type: 'keyword', value: 'PRIVILEGES' }, + { type: 'keyword', value: 'PROCEDURE' }, + { type: 'keyword', value: 'PROCESSLIST' }, + { type: 'keyword', value: 'PROFILE' }, + { type: 'keyword', value: 'PROFILES' }, + { type: 'keyword', value: 'RELAYLOG' }, + { type: 'keyword', value: 'REPLICAS' }, + { type: 'keyword', value: 'REPLICA' }, + { type: 'keyword', value: 'SLAVE' }, + { type: 'keyword', value: 'STATUS' }, + { type: 'keyword', value: 'TRIGGERS' }, + { type: 'keyword', value: 'VARIABLES' }, + { type: 'keyword', value: 'WARNINGS' }, + ], + }, + add: (token) => { + statement.type = `SHOW_${token.value.toUpperCase().replace(' ', '_')}` as StatementType; + }, + postCanGoToNext: () => true, + }, + ]; + + return stateMachineStatementParser(statement, steps, options); +} + function createUnknownStatementParser(options: ParseOptions) { const statement = createInitialStatement(); diff --git a/src/tokenizer.ts b/src/tokenizer.ts index cf5fbff..bc3b10d 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -34,6 +34,35 @@ const KEYWORDS = [ 'WHILE', 'FOR', 'PROCEDURE', + 'SHOW', + 'DATABASES', + 'KEYS', + 'TABLES', + 'COLUMNS', + 'STATUS', + 'BINARY', + 'BINLOG', + 'CHARACTER', + 'COLLATION', + 'ENGINE', + 'ENGINES', + 'ERRORS', + 'EVENTS', + 'GRANTS', + 'MASTER', + 'OPEN', + 'PLUGINS', + 'PRIVILEGES', + 'PROCESSLIST', + 'PROFILE', + 'PROFILES', + 'RELAYLOG', + 'REPLICAS', + 'SLAVE', + 'REPLICA', + 'TRIGGERS', + 'VARIABLES', + 'WARNINGS', ]; const INDIVIDUALS: Record = { diff --git a/test/identifier/single-statement.spec.ts b/test/identifier/single-statement.spec.ts index b652937..71f7fe9 100644 --- a/test/identifier/single-statement.spec.ts +++ b/test/identifier/single-statement.spec.ts @@ -522,6 +522,68 @@ describe('identifier', () => { }); }); + describe('identify SHOW statements', () => { + (['mysql', 'generic', 'mssql'] as Dialect[]).forEach((dialect) => { + [ + ['BINARY', "SHOW BINARY LOGS 'blerns';"], + ['BINLOG', "SHOW BINLOG EVENTS 'blerns';"], + ['CHARACTER', "SHOW CHARACTER SET 'blerns';"], + ['COLLATION', "SHOW COLLATION 'blerns';"], + ['COLUMNS', "SHOW COLUMNS 'blerns';"], + ['CREATE', "SHOW CREATE DATABASE 'blerns';"], + ['DATABASES', "SHOW DATABASES 'blerns';"], + ['ENGINE', "SHOW ENGINE 'blerns';"], + ['ENGINES', "SHOW ENGINES 'blerns';"], + ['ERRORS', "SHOW ERRORS 'blerns';"], + ['EVENTS', "SHOW EVENTS 'blerns';"], + ['FUNCTION', "SHOW FUNCTION CODE 'blerns';"], + ['GRANTS', "SHOW GRANTS 'blerns';"], + ['INDEX', "SHOW INDEX 'blerns';"], + ['MASTER', "SHOW MASTER STATUS 'blerns';"], + ['OPEN', "SHOW OPEN TABLES 'blerns';"], + ['PLUGINS', "SHOW PLUGINS 'blerns';"], + ['PRIVILEGES', "SHOW PRIVILEGES 'blerns';"], + ['PROCEDURE', "SHOW PROCEDURE CODE 'blerns';"], + ['PROCESSLIST', "SHOW PROCESSLIST 'blerns';"], + ['PROFILE', "SHOW PROFILE 'blerns';"], + ['PROFILES', "SHOW PROFILES 'blerns';"], + ['RELAYLOG', "SHOW RELAYLOG EVENTS 'blerns';"], + ['REPLICAS', "SHOW REPLICAS 'blerns';"], + ['SLAVE', 'SHOW SLAVE HOSTS;'], + ['REPLICA', "SHOW REPLICA STATUS 'blerns';"], + ['STATUS', "SHOW STATUS 'blerns';"], + ['TABLE', "SHOW TABLE STATUS 'blerns';"], + ['TABLES', "SHOW TABLES 'blerns';"], + ['TRIGGERS', "SHOW TRIGGERS 'blerns';"], + ['VARIABLES', "SHOW VARIABLES 'blerns';"], + ['WARNINGS', "SHOW WARNINGS 'blerns';"], + ].forEach(([type, sql]) => { + it(`identify "SHOW ${type}" statements for ${dialect}`, () => { + const sqlStatement = sql; + const testFunction = () => identify(sqlStatement, { dialect }); + if (dialect === 'mssql') { + expect(testFunction).to.throw('Invalid statement parser "SHOW"'); + return; + } + + const actual = identify(sqlStatement, { dialect }); + const expected = [ + { + start: 0, + end: sqlStatement.length - 1, + text: sqlStatement, + type: `SHOW_${type}`, + executionType: 'LISTING', + parameters: [], + }, + ]; + + expect(actual).to.eql(expected); + }); + }); + }); + }); + describe('identify "CREATE FUNCTION" statements', () => { it('should identify postgres "CREATE FUNCTION" statement with LANGUAGE at end', () => { const sql = `CREATE FUNCTION quarterly_summary_func(start_date date DEFAULT CURRENT_TIMESTAMP) diff --git a/test/parser/single-statements.spec.ts b/test/parser/single-statements.spec.ts index df23ac6..9e3f9e4 100644 --- a/test/parser/single-statements.spec.ts +++ b/test/parser/single-statements.spec.ts @@ -226,9 +226,15 @@ describe('parser', () => { end: 14, }, { - type: 'unknown', - value: ' Profile', + type: 'whitespace', + value: ' ', start: 15, + end: 15, + }, + { + type: 'keyword', + value: 'Profile', + start: 16, end: 22, }, { @@ -338,9 +344,15 @@ describe('parser', () => { end: 12, }, { - type: 'unknown', - value: ' Profile', + type: 'whitespace', + value: ' ', start: 13, + end: 13, + }, + { + type: 'keyword', + value: 'Profile', + start: 14, end: 20, }, {