From 158135db21ccf6300f424d13784b1325fd92c2bb Mon Sep 17 00:00:00 2001 From: Mohammad Azmi Date: Tue, 20 Feb 2024 22:50:02 +0700 Subject: [PATCH 1/2] Add initial support for table identification (#76) Co-authored-by: Matthew Rathbone --- package.json | 3 +- src/defines.ts | 5 ++ src/index.ts | 3 +- src/parser.ts | 81 ++++++++++++++-------- test/identifier/inner-statements.spec.ts | 4 ++ test/identifier/multiple-statement.spec.ts | 27 ++++++++ test/identifier/single-statement.spec.ts | 64 +++++++++++++++++ test/index.spec.ts | 17 +++++ test/parser/multiple-statements.spec.ts | 4 ++ test/parser/single-statements.spec.ts | 16 ++++- 10 files changed, 192 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 913acf7..7742d87 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,11 @@ "prettier": "^2.3.2", "terser-webpack-plugin": "^5.1.1", "ts-loader": "^8.0.17", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^4.1.5", "webpack": "^5.11.1", "webpack-cli": "^4.3.1" }, - "dependencies": {}, "engines": { "node": ">= 10.13" } diff --git a/src/defines.ts b/src/defines.ts index 66e4dd3..955d04f 100644 --- a/src/defines.ts +++ b/src/defines.ts @@ -79,6 +79,7 @@ export type ExecutionType = 'LISTING' | 'MODIFICATION' | 'INFORMATION' | 'ANON_B export interface IdentifyOptions { strict?: boolean; dialect?: Dialect; + identifyTables?: boolean; } export interface IdentifyResult { @@ -88,6 +89,7 @@ export interface IdentifyResult { type: StatementType; executionType: ExecutionType; parameters: string[]; + tables: string[]; } export interface Statement { @@ -101,6 +103,8 @@ export interface Statement { algorithm?: number; sqlSecurity?: number; parameters: string[]; + tables: string[]; + isCte?: boolean; } export interface ConcreteStatement extends Statement { @@ -124,6 +128,7 @@ export interface Token { | 'semicolon' | 'keyword' | 'parameter' + | 'table' | 'unknown'; value: string; start: number; diff --git a/src/index.ts b/src/index.ts index 9a6426e..c57dafd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,7 @@ export function identify(query: string, options: IdentifyOptions = {}): Identify throw new Error(`Unknown dialect. Allowed values: ${DIALECTS.join(', ')}`); } - const result = parse(query, isStrict, dialect); + const result = parse(query, isStrict, dialect, options.identifyTables); return result.body.map((statement) => { const result: IdentifyResult = { @@ -32,6 +32,7 @@ export function identify(query: string, options: IdentifyOptions = {}): Identify executionType: statement.executionType, // we want to sort the postgres params: $1 $2 $3, regardless of the order they appear parameters: dialect === 'psql' ? statement.parameters.sort() : statement.parameters, + tables: statement.tables || [], }; return result; }); diff --git a/src/parser.ts b/src/parser.ts index 657a740..3aec638 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -98,6 +98,10 @@ const statementsWithEnds = [ 'UNKNOWN', ]; +// keywords that come directly before a table name. +// v1 - keeping it very simple. +const PRE_TABLE_KEYWORDS = /^from$|^join$|^into$/i; + const blockOpeners: Record = { generic: ['BEGIN', 'CASE'], psql: ['BEGIN', 'CASE', 'LOOP', 'IF'], @@ -111,6 +115,7 @@ const blockOpeners: Record = { interface ParseOptions { isStrict: boolean; dialect: Dialect; + identifyTables: boolean; } function createInitialStatement(): Statement { @@ -118,14 +123,15 @@ function createInitialStatement(): Statement { start: -1, end: 0, parameters: [], + tables: [], }; } -function nextNonWhitespaceToken(state: State): Token { +function nextNonWhitespaceToken(state: State, dialect: Dialect): Token { let token: Token; do { state = initState({ prevState: state }); - token = scanToken(state); + token = scanToken(state, dialect); } while (token.type === 'whitespace'); return token; } @@ -133,7 +139,12 @@ function nextNonWhitespaceToken(state: State): Token { /** * Parser */ -export function parse(input: string, isStrict = true, dialect: Dialect = 'generic'): ParseResult { +export function parse( + input: string, + isStrict = true, + dialect: Dialect = 'generic', + identifyTables = false, +): ParseResult { const topLevelState = initState({ input }); const topLevelStatement: ParseResult = { type: 'QUERY', @@ -164,14 +175,13 @@ export function parse(input: string, isStrict = true, dialect: Dialect = 'generi while (prevState.position < topLevelState.end) { const tokenState = initState({ prevState }); const token = scanToken(tokenState, dialect); - const nextToken = nextNonWhitespaceToken(tokenState); + const nextToken = nextNonWhitespaceToken(tokenState, dialect); if (!statementParser) { // ignore blank tokens before the start of a CTE / not part of a statement if (!cteState.isCte && ignoreOutsideBlankTokens.includes(token.type)) { topLevelStatement.tokens.push(token); prevState = tokenState; - continue; } else if ( !cteState.isCte && token.type === 'keyword' && @@ -181,7 +191,7 @@ export function parse(input: string, isStrict = true, dialect: Dialect = 'generi topLevelStatement.tokens.push(token); cteState.state = tokenState; prevState = tokenState; - continue; + // If we're scanning in a CTE, handle someone putting a semicolon anywhere (after 'with', // after semicolon, etc.) along it to "early terminate". } else if (cteState.isCte && token.type === 'semicolon') { @@ -193,12 +203,12 @@ export function parse(input: string, isStrict = true, dialect: Dialect = 'generi type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }); cteState.isCte = false; cteState.asSeen = false; cteState.statementEnd = false; cteState.parens = 0; - continue; } else if (cteState.isCte && !cteState.statementEnd) { if (cteState.asSeen) { if (token.value === '(') { @@ -215,14 +225,13 @@ export function parse(input: string, isStrict = true, dialect: Dialect = 'generi topLevelStatement.tokens.push(token); prevState = tokenState; - continue; } else if (cteState.isCte && cteState.statementEnd && token.value === ',') { cteState.asSeen = false; cteState.statementEnd = false; topLevelStatement.tokens.push(token); prevState = tokenState; - continue; + // Ignore blank tokens after the end of the CTE till start of statement } else if ( cteState.isCte && @@ -231,28 +240,32 @@ export function parse(input: string, isStrict = true, dialect: Dialect = 'generi ) { topLevelStatement.tokens.push(token); prevState = tokenState; - continue; + } else { + statementParser = createStatementParserByToken(token, nextToken, { + isStrict, + dialect, + identifyTables, + }); + if (cteState.isCte) { + statementParser.getStatement().start = cteState.state.start; + statementParser.getStatement().isCte = true; + cteState.isCte = false; + cteState.asSeen = false; + cteState.statementEnd = false; + } } + } else { + statementParser.addToken(token, nextToken); + topLevelStatement.tokens.push(token); + prevState = tokenState; - statementParser = createStatementParserByToken(token, nextToken, { isStrict, dialect }); - if (cteState.isCte) { - statementParser.getStatement().start = cteState.state.start; - cteState.isCte = false; - cteState.asSeen = false; - cteState.statementEnd = false; + const statement = statementParser.getStatement(); + if (statement.endStatement) { + statement.end = token.end; + topLevelStatement.body.push(statement as ConcreteStatement); + statementParser = null; } } - - statementParser.addToken(token, nextToken); - topLevelStatement.tokens.push(token); - prevState = tokenState; - - const statement = statementParser.getStatement(); - if (statement.endStatement) { - statement.end = token.end; - topLevelStatement.body.push(statement as ConcreteStatement); - statementParser = null; - } } // last statement without ending key @@ -708,7 +721,7 @@ function createUnknownStatementParser(options: ParseOptions) { function stateMachineStatementParser( statement: Statement, steps: Step[], - { isStrict, dialect }: ParseOptions, + { isStrict, dialect, identifyTables }: ParseOptions, ): StatementParser { let currentStepIndex = 0; let prevToken: Token | undefined; @@ -808,6 +821,18 @@ function stateMachineStatementParser( } } + if ( + identifyTables && + PRE_TABLE_KEYWORDS.exec(token.value) && + !statement.isCte && + statement.type?.match(/SELECT|INSERT/) + ) { + const tableValue = nextToken.value; + if (!statement.tables.includes(tableValue)) { + statement.tables.push(tableValue); + } + } + if ( token.type === 'parameter' && (token.value === '?' || !statement.parameters.includes(token.value)) diff --git a/test/identifier/inner-statements.spec.ts b/test/identifier/inner-statements.spec.ts index e7ca454..cbbc98b 100644 --- a/test/identifier/inner-statements.spec.ts +++ b/test/identifier/inner-statements.spec.ts @@ -16,6 +16,7 @@ describe('identifier', () => { type: 'INSERT', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -34,6 +35,7 @@ describe('identifier', () => { type: 'INSERT', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -54,6 +56,7 @@ describe('identifier', () => { type: 'INSERT', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -75,6 +78,7 @@ describe('identifier', () => { type: 'INSERT', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; diff --git a/test/identifier/multiple-statement.spec.ts b/test/identifier/multiple-statement.spec.ts index fdb6fb2..4e2c003 100644 --- a/test/identifier/multiple-statement.spec.ts +++ b/test/identifier/multiple-statement.spec.ts @@ -16,6 +16,7 @@ describe('identifier', () => { type: 'INSERT', executionType: 'MODIFICATION', parameters: [], + tables: [], }, { end: 76, @@ -24,6 +25,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -44,6 +46,7 @@ describe('identifier', () => { type: 'INSERT', executionType: 'MODIFICATION', parameters: [], + tables: [], }, { start: 74, @@ -52,6 +55,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -75,6 +79,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, { start: 35, @@ -83,6 +88,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -105,6 +111,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, { start: 20, @@ -113,6 +120,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, { start: 50, @@ -121,6 +129,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -161,6 +170,7 @@ describe('identifier', () => { end: 1043, executionType: 'ANON_BLOCK', parameters: [], + tables: [], start: 11, text: 'DECLARE\n PK_NAME VARCHAR(200);\n\n BEGIN\n EXECUTE IMMEDIATE (\'CREATE SEQUENCE "untitled_table8_seq"\');\n\n SELECT\n cols.column_name INTO PK_NAME\n FROM\n all_constraints cons,\n all_cons_columns cols\n WHERE\n cons.constraint_type = \'P\'\n AND cons.constraint_name = cols.constraint_name\n AND cons.owner = cols.owner\n AND cols.table_name = \'untitled_table8\';\n\n execute immediate (\n \'create or replace trigger "untitled_table8_autoinc_trg" BEFORE INSERT on "untitled_table8" for each row declare checking number := 1; begin if (:new."\' || PK_NAME || \'" is null) then while checking >= 1 loop select "untitled_table8_seq".nextval into :new."\' || PK_NAME || \'" from dual; select count("\' || PK_NAME || \'") into checking from "untitled_table8" where "\' || PK_NAME || \'" = :new."\' || PK_NAME || \'"; end loop; end if; end;\'\n );\n\n END;', type: 'ANON_BLOCK', @@ -208,6 +218,7 @@ describe('identifier', () => { end: 167, executionType: 'MODIFICATION', parameters: [], + tables: [], start: 11, text: 'create table\n "untitled_table8" (\n "id" integer not null primary key,\n "created_at" varchar(255) not null\n );', type: 'CREATE_TABLE', @@ -216,6 +227,7 @@ describe('identifier', () => { end: 1212, executionType: 'ANON_BLOCK', parameters: [], + tables: [], start: 180, text: 'DECLARE\n PK_NAME VARCHAR(200);\n\n BEGIN\n EXECUTE IMMEDIATE (\'CREATE SEQUENCE "untitled_table8_seq"\');\n\n SELECT\n cols.column_name INTO PK_NAME\n FROM\n all_constraints cons,\n all_cons_columns cols\n WHERE\n cons.constraint_type = \'P\'\n AND cons.constraint_name = cols.constraint_name\n AND cons.owner = cols.owner\n AND cols.table_name = \'untitled_table8\';\n\n execute immediate (\n \'create or replace trigger "untitled_table8_autoinc_trg" BEFORE INSERT on "untitled_table8" for each row declare checking number := 1; begin if (:new."\' || PK_NAME || \'" is null) then while checking >= 1 loop select "untitled_table8_seq".nextval into :new."\' || PK_NAME || \'" from dual; select count("\' || PK_NAME || \'") into checking from "untitled_table8" where "\' || PK_NAME || \'" = :new."\' || PK_NAME || \'"; end loop; end if; end;\'\n );\n\n END;', type: 'ANON_BLOCK', @@ -248,6 +260,7 @@ describe('identifier', () => { type: 'INSERT', executionType: 'MODIFICATION', parameters: [], + tables: [], }, { start: 79, @@ -256,6 +269,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, { start: 250, @@ -264,6 +278,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -286,6 +301,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, { start: 54, @@ -294,6 +310,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -317,6 +334,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, { start: 6, @@ -325,6 +343,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -347,6 +366,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, { start: 24, @@ -355,6 +375,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -376,6 +397,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, { start: 19, @@ -384,6 +406,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, { start: 29, @@ -392,6 +415,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -411,6 +435,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, { start: 19 + offset, @@ -419,6 +444,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, { start: 29 + offset, @@ -427,6 +453,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); diff --git a/test/identifier/single-statement.spec.ts b/test/identifier/single-statement.spec.ts index 30ec5e0..f86c1d8 100644 --- a/test/identifier/single-statement.spec.ts +++ b/test/identifier/single-statement.spec.ts @@ -14,6 +14,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -30,6 +31,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -46,6 +48,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -62,6 +65,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -81,6 +85,7 @@ describe('identifier', () => { type: `CREATE_${type}`, executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -105,6 +110,7 @@ describe('identifier', () => { type: 'CREATE_TABLE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -122,6 +128,7 @@ describe('identifier', () => { type: 'CREATE_VIEW', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -141,6 +148,7 @@ describe('identifier', () => { type: 'CREATE_VIEW', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -170,6 +178,7 @@ describe('identifier', () => { type: 'CREATE_VIEW', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -206,6 +215,7 @@ describe('identifier', () => { type: 'CREATE_VIEW', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -237,6 +247,7 @@ describe('identifier', () => { type: 'CREATE_VIEW', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -258,6 +269,7 @@ describe('identifier', () => { type: 'CREATE_VIEW', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -281,6 +293,7 @@ describe('identifier', () => { type: 'CREATE_TRIGGER', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -306,6 +319,7 @@ describe('identifier', () => { type: 'CREATE_TRIGGER', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -340,6 +354,7 @@ describe('identifier', () => { type: 'CREATE_TRIGGER', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -357,6 +372,7 @@ describe('identifier', () => { type: 'CREATE_TRIGGER', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -386,6 +402,7 @@ describe('identifier', () => { type: 'CREATE_PROCEDURE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -413,6 +430,7 @@ describe('identifier', () => { type: 'CREATE_PROCEDURE', executionType: 'MODIFICATION', parameters: [], + tables: [], // FIXME: should return mydataset.customers }, ]; expect(actual).to.eql(expected); @@ -438,6 +456,7 @@ describe('identifier', () => { type: 'CREATE_PROCEDURE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -473,6 +492,7 @@ describe('identifier', () => { type: 'DROP_PROCEDURE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -501,6 +521,7 @@ describe('identifier', () => { type: 'ALTER_PROCEDURE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -575,6 +596,7 @@ describe('identifier', () => { type: `SHOW_${type}`, executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -622,6 +644,7 @@ describe('identifier', () => { type: 'CREATE_FUNCTION', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -672,6 +695,7 @@ describe('identifier', () => { type: 'CREATE_FUNCTION', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -696,6 +720,7 @@ describe('identifier', () => { type: 'CREATE_FUNCTION', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -714,6 +739,7 @@ describe('identifier', () => { type: 'CREATE_FUNCTION', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -732,6 +758,7 @@ describe('identifier', () => { type: 'CREATE_FUNCTION', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -765,6 +792,7 @@ describe('identifier', () => { type: 'CREATE_FUNCTION', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -783,6 +811,7 @@ describe('identifier', () => { type: 'CREATE_INDEX', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -799,6 +828,7 @@ describe('identifier', () => { type: 'CREATE_INDEX', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -817,6 +847,7 @@ describe('identifier', () => { type: 'CREATE_INDEX', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -837,6 +868,7 @@ describe('identifier', () => { type: 'CREATE_INDEX', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -858,6 +890,7 @@ describe('identifier', () => { type: `DROP_${type}`, executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -882,6 +915,7 @@ describe('identifier', () => { type: 'DROP_TABLE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -898,6 +932,7 @@ describe('identifier', () => { type: 'DROP_VIEW', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -914,6 +949,7 @@ describe('identifier', () => { type: 'DROP_DATABASE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -930,6 +966,7 @@ describe('identifier', () => { type: 'DROP_TRIGGER', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -946,6 +983,7 @@ describe('identifier', () => { type: 'DROP_FUNCTION', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -962,6 +1000,7 @@ describe('identifier', () => { type: 'DROP_INDEX', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -977,6 +1016,7 @@ describe('identifier', () => { type: 'TRUNCATE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -993,6 +1033,7 @@ describe('identifier', () => { type: 'INSERT', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -1009,6 +1050,7 @@ describe('identifier', () => { type: 'UPDATE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -1027,6 +1069,7 @@ describe('identifier', () => { type: 'UPDATE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; expect(actual).to.eql(expected); @@ -1042,6 +1085,7 @@ describe('identifier', () => { type: 'DELETE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -1068,6 +1112,7 @@ describe('identifier', () => { type: `ALTER_${type}`, executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -1105,6 +1150,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1126,6 +1172,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1147,6 +1194,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1166,6 +1214,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1186,6 +1235,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1208,6 +1258,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1232,6 +1283,7 @@ describe('identifier', () => { type: 'CREATE_LOGFILE', executionType: 'UNKNOWN', parameters: [], + tables: [], }, ]; @@ -1254,6 +1306,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1275,6 +1328,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], // FIXME: should return 'table'? }, ]; @@ -1313,6 +1367,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1341,6 +1396,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ]; @@ -1361,6 +1417,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: ['$1', '$2'], + tables: [], }, ]; @@ -1380,6 +1437,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: ['$1', '$2', '$3', '$4'], + tables: [], }, ]; @@ -1399,6 +1457,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [':one', ':two'], + tables: [], }, ]; @@ -1418,6 +1477,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: [':one', ':two', ':three'], + tables: [], }, ]; @@ -1437,6 +1497,7 @@ describe('identifier', () => { type: 'SELECT', executionType: 'LISTING', parameters: ['?', '?', '?'], + tables: [], }, ]; @@ -1456,6 +1517,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, ]; @@ -1480,6 +1542,7 @@ describe('identifier', () => { type: 'CREATE_PROCEDURE', executionType: 'MODIFICATION', parameters: [], + tables: [], }, ]; @@ -1497,6 +1560,7 @@ describe('identifier', () => { type: 'UNKNOWN', executionType: 'UNKNOWN', parameters: [], + tables: [], }, ]; diff --git a/test/index.spec.ts b/test/index.spec.ts index 2e4ba1e..d7cd5e9 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -17,6 +17,23 @@ describe('identify', () => { type: 'SELECT', executionType: 'LISTING', parameters: ['$1', '$2'], + tables: [], + }, + ]); + }); + + it('should identify tables in simple for basic cases', () => { + expect( + identify('SELECT * FROM foo JOIN bar ON foo.id = bar.id', { identifyTables: true }), + ).to.eql([ + { + start: 0, + end: 44, + text: 'SELECT * FROM foo JOIN bar ON foo.id = bar.id', + type: 'SELECT', + executionType: 'LISTING', + parameters: [], + tables: ['foo', 'bar'], }, ]); }); diff --git a/test/parser/multiple-statements.spec.ts b/test/parser/multiple-statements.spec.ts index dee8793..af638db 100644 --- a/test/parser/multiple-statements.spec.ts +++ b/test/parser/multiple-statements.spec.ts @@ -24,6 +24,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, { start: 56, @@ -31,6 +32,7 @@ describe('parser', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ], tokens: [ @@ -93,6 +95,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, { start: 74, @@ -100,6 +103,7 @@ describe('parser', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ], tokens: [ diff --git a/test/parser/single-statements.spec.ts b/test/parser/single-statements.spec.ts index 00e1b2c..b0a15cc 100644 --- a/test/parser/single-statements.spec.ts +++ b/test/parser/single-statements.spec.ts @@ -5,7 +5,7 @@ import { parse } from '../../src/parser'; import { Token } from '../../src/defines'; describe('parser', () => { - describe('given is a not reconized statement', () => { + describe('given is a not recognized statement', () => { it('should throw an error including the unknown statement', () => { expect(() => parse('LIST * FROM Persons')).to.throw('Invalid statement parser "LIST"'); }); @@ -23,6 +23,7 @@ describe('parser', () => { start: 0, end: 14, parameters: [], + tables: [], type: 'UNKNOWN', executionType: 'UNKNOWN', }, @@ -43,6 +44,7 @@ describe('parser', () => { start: 0, end: 19, parameters: [], + tables: [], type: 'UNKNOWN', executionType: 'UNKNOWN', }, @@ -73,6 +75,7 @@ describe('parser', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ], tokens: [ @@ -110,6 +113,7 @@ describe('parser', () => { type: 'SELECT', executionType: 'LISTING', parameters: [], + tables: [], }, ], tokens: [ @@ -148,6 +152,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -212,6 +217,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -276,6 +282,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -332,6 +339,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -394,6 +402,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -450,6 +459,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -511,6 +521,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -555,6 +566,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -599,6 +611,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ @@ -643,6 +656,7 @@ describe('parser', () => { executionType: 'MODIFICATION', endStatement: ';', parameters: [], + tables: [], }, ], tokens: [ From c17b97327b5482e5f4be8ca89e18c9fdc6229a28 Mon Sep 17 00:00:00 2001 From: Matthew Peveler Date: Wed, 21 Feb 2024 13:43:08 -0500 Subject: [PATCH 2/2] Bump version to 2.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7742d87..11a3348 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sql-query-identifier", - "version": "2.6.0", + "version": "2.7.0", "description": "A SQL query identifier", "license": "MIT", "main": "lib/index.js",