import { describe, expect, it } from 'vitest' import { endsWithUnterminatedStatement, joinSqlStatements, splitSqlRuns, splitSqlStatements, stripSqlComments } from './sqlDdl' describe('endsWithUnterminatedStatement', () => { it('reports a missing terminator on the final statement', () => { expect(endsWithUnterminatedStatement('SELECT 1;')).toBe(false) expect(endsWithUnterminatedStatement('SELECT 1')).toBe(true) expect(endsWithUnterminatedStatement('')).toBe(false) }) it('is not fooled by a trailing comment after the terminator', () => { expect(endsWithUnterminatedStatement('SELECT 1;\n-- note')).toBe(false) expect(endsWithUnterminatedStatement('SELECT 1\n-- note')).toBe(true) }) it('treats terminators and comment markers inside quoted runs as data', () => { expect(endsWithUnterminatedStatement("SELECT ';--literal'")).toBe(true) expect(endsWithUnterminatedStatement("SELECT ';--literal';")).toBe(false) // Dollar-quoted bodies protect their contents just as strings do. expect(endsWithUnterminatedStatement('SELECT $$;--literal$$')).toBe(true) expect(endsWithUnterminatedStatement('SELECT $$;--literal$$;')).toBe(false) expect(endsWithUnterminatedStatement('SELECT $tag$;--x$tag$')).toBe(true) }) }) describe('stripSqlComments', () => { it('removes comments without touching quoted runs', () => { expect(stripSqlComments('SELECT 1 -- note').trim()).toBe('SELECT 1') expect(stripSqlComments("SELECT '--not a comment'").trim()).toBe("SELECT '--not a comment'") expect(stripSqlComments('SELECT $$--not a comment$$').trim()).toBe('SELECT $$--not a comment$$') }) it('only treats // as a comment when asked, and only at a line start', () => { // `//` is division mid-line: never stripped, and not even when asked. expect(stripSqlComments('SELECT 1 // note').trim()).toBe('SELECT 1 // note') expect(stripSqlComments('SELECT 5 // 2', true).trim()).toBe('SELECT 5 // 2') // A `//` that opens a line is a Windmill annotation header. expect(stripSqlComments('// materialize x\nSELECT 1', true).trim()).toBe('SELECT 1') }) }) describe('splitSqlStatements', () => { it('keeps splitting behaviour intact through the shared scan', () => { expect(splitSqlStatements('SELECT 1; SELECT 2;')).toEqual(['SELECT 1', 'SELECT 2']) expect(splitSqlStatements("SELECT ';'; SELECT 2")).toEqual(["SELECT ';'", 'SELECT 2']) expect(splitSqlStatements('SELECT $$;$$;')).toEqual(['SELECT $$;$$']) }) it('escapes quotes per dialect (backslashEscapes)', () => { // Standard SQL (false): `\` is data, quotes escape by doubling. A `\`-ending // literal is complete and a doubled quote embeds one, so neither splits. expect(splitSqlStatements("SELECT 'C:\\'; SELECT 2", false)).toEqual([ "SELECT 'C:\\'", 'SELECT 2' ]) expect(splitSqlStatements("SELECT 'a''; b'", false)).toEqual(["SELECT 'a''; b'"]) // Even in standard mode a Postgres E-string backslash-escapes, so its `;` is data. expect(splitSqlStatements("SELECT E'x: \\'; still data' AS v; SELECT 2", false)).toEqual([ "SELECT E'x: \\'; still data' AS v", 'SELECT 2' ]) // Backslash dialects (default, e.g. MySQL): an ordinary `\'` is an escaped // quote, so the embedded `;` is data and the literal does not split. expect(splitSqlStatements("SELECT 'it\\'s; still data' AS v; SELECT 2")).toEqual([ "SELECT 'it\\'s; still data' AS v", 'SELECT 2' ]) }) }) describe('splitSqlRuns', () => { it('groups adjacent DDL statements and breaks the run on a non-DDL one', () => { expect( splitSqlRuns( 'CREATE TABLE a (id int); ALTER TABLE a ADD b int; INSERT INTO a VALUES (1); DROP TABLE a;' ) ).toEqual([ { isDdl: true, statements: ['CREATE TABLE a (id int)', 'ALTER TABLE a ADD b int'] }, { isDdl: false, statements: ['INSERT INTO a VALUES (1)'] }, { isDdl: true, statements: ['DROP TABLE a'] } ]) }) it('keeps a leading comment with the statement it introduces', () => { expect(splitSqlRuns('-- add it\nCREATE TABLE a (id int);')).toEqual([ { isDdl: true, statements: ['-- add it\nCREATE TABLE a (id int)'] } ]) }) }) describe('joinSqlStatements', () => { it('keeps a terminator out of a statement-trailing line comment', () => { expect(joinSqlStatements(['CREATE TABLE a (id int)', 'ALTER TABLE a ADD b int'])).toBe( 'CREATE TABLE a (id int);\nALTER TABLE a ADD b int;' ) // A `;` appended after the comment would be commented out, gluing this // statement onto the next one. expect(joinSqlStatements(['CREATE TABLE a (id int) -- new', 'ALTER TABLE a ADD b int'])).toBe( 'CREATE TABLE a (id int) -- new\n;\nALTER TABLE a ADD b int;' ) }) })