From 3aeec3d3f01e60e517ee28ba9b2f5d56f9c26f4a Mon Sep 17 00:00:00 2001 From: Matthew Rathbone Date: Fri, 22 May 2026 08:57:05 -0500 Subject: [PATCH] fix: treat `SELECT ... INTO` as a modification `SELECT ... INTO target` creates and populates a new table, so it modifies the database. The parser reported executionType `LISTING` for it because the statement type is `SELECT`. Flag any `SELECT` statement that contains an `INTO` clause as `MODIFICATION` for the dialects where `SELECT INTO` builds a table (generic, mssql, psql). Oracle and MySQL use `SELECT INTO` to assign variables, so they keep the `LISTING` classification. Closes #81 --- src/parser.ts | 12 ++++++ test/identifier/single-statement.spec.ts | 53 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/parser.ts b/src/parser.ts index 71efcef..818ea7a 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -983,6 +983,18 @@ function stateMachineStatementParser( statement.parameters.push(token.value); } + // `SELECT ... INTO target` creates and populates a new table, so the + // statement modifies the database even though its type is SELECT. Only + // dialects where `SELECT INTO` builds a table are flagged here; Oracle + // and MySQL use `SELECT INTO` to assign variables, so they are excluded. + if ( + statement.type === 'SELECT' && + token.value.toUpperCase() === 'INTO' && + ['generic', 'mssql', 'psql'].includes(dialect) + ) { + statement.executionType = 'MODIFICATION'; + } + if (statement.type && statement.start >= 0) { // statement has already been identified // just wait until end of the statement diff --git a/test/identifier/single-statement.spec.ts b/test/identifier/single-statement.spec.ts index 7952083..78fdac7 100644 --- a/test/identifier/single-statement.spec.ts +++ b/test/identifier/single-statement.spec.ts @@ -76,6 +76,59 @@ describe('identifier', () => { expect(actual).to.eql(expected); }); + describe('identify "SELECT ... INTO" statements', () => { + // `SELECT ... INTO` creates and populates a new table, so it modifies the + // database. See https://github.com/coresql/sql-query-identifier/issues/81 + it('should identify "SELECT ... INTO" as a modification', () => { + const sql = 'SELECT * INTO public."MyTable1" FROM public."MyTable2"'; + const actual = identify(sql); + const expected = [ + { + start: 0, + end: sql.length - 1, + text: sql, + type: 'SELECT', + executionType: 'MODIFICATION', + parameters: [], + tables: [], + columns: [], + }, + ]; + + expect(actual).to.eql(expected); + }); + + it('should identify "SELECT ... INTO" as a modification in mssql', () => { + const sql = 'SELECT id, name INTO [dbo].[copy] FROM [dbo].[users]'; + const actual = identify(sql, { dialect: 'mssql' }); + + expect(actual[0].type).to.eql('SELECT'); + expect(actual[0].executionType).to.eql('MODIFICATION'); + }); + + it('should identify "SELECT ... INTO" as a modification in psql', () => { + const sql = 'SELECT * INTO new_table FROM old_table'; + const actual = identify(sql, { dialect: 'psql' }); + + expect(actual[0].type).to.eql('SELECT'); + expect(actual[0].executionType).to.eql('MODIFICATION'); + }); + + it('should still identify a plain "SELECT" as a listing', () => { + const actual = identify('SELECT * FROM Persons'); + + expect(actual[0].executionType).to.eql('LISTING'); + }); + + it('should not flag oracle "SELECT INTO", which assigns variables', () => { + const sql = 'SELECT name INTO v_name FROM employees WHERE id = 1'; + const actual = identify(sql, { dialect: 'oracle' }); + + expect(actual[0].type).to.eql('SELECT'); + expect(actual[0].executionType).to.eql('LISTING'); + }); + }); + ['DATABASE', 'SCHEMA'].forEach((type) => { describe(`identify "CREATE ${type}" statements`, () => { const sql = `CREATE ${type} Profile;`;