diff --git a/pegjs/sqlite.pegjs b/pegjs/sqlite.pegjs index 399b1258d..7f61ee162 100644 --- a/pegjs/sqlite.pegjs +++ b/pegjs/sqlite.pegjs @@ -1316,7 +1316,7 @@ with_clause } cte_definition - = name:(literal_string / ident_name / table_name) __ columns:cte_column_definition? __ KW_AS __ LPAREN __ stmt:union_stmt __ RPAREN { + = name:(literal_string / ident_name / table_name) __ columns:cte_column_definition? __ KW_AS __ LPAREN __ stmt:(value_clause / union_stmt) __ RPAREN { if (typeof name === 'string') name = { type: 'default', value: name } if (name.table) name = { type: 'default', value: name.table } return { name, stmt, columns }; diff --git a/test/sqlite.spec.js b/test/sqlite.spec.js index aaf48514c..20d3ffbf1 100644 --- a/test/sqlite.spec.js +++ b/test/sqlite.spec.js @@ -342,4 +342,78 @@ describe('sqlite', () => { 'SELECT * FROM "t1" CROSS JOIN "t2" INNER JOIN "t3" ON "t2"."id" = "t3"."id"' ); }); + + it('should support VALUES in a CTE', () => { + const sql = `WITH cte1 (col1, col2) AS ( + VALUES ('Name1', 'type1'), ('Name2', 'type1') + ) + SELECT a.name + FROM table1 a + JOIN cte1 c ON lower(a.name) = lower(c.col1)`; + expect(getParsedSql(sql)).to.be.equal( + `WITH "cte1"("col1", "col2") AS (VALUES ('Name1','type1'), ('Name2','type1')) SELECT "a"."name" FROM "table1" AS "a" INNER JOIN "cte1" AS "c" ON lower("a"."name") = lower("c"."col1")` + ); + }); + + it('should support single-column VALUES in a CTE', () => { + const sql = `WITH cte1 (col1) AS ( + VALUES ('Value1'), ('Value2') + ) + SELECT a.name FROM table1 a JOIN cte1 c ON lower(a.name) = lower(c.col1)`; + expect(getParsedSql(sql)).to.be.equal( + `WITH "cte1"("col1") AS (VALUES ('Value1'), ('Value2')) SELECT "a"."name" FROM "table1" AS "a" INNER JOIN "cte1" AS "c" ON lower("a"."name") = lower("c"."col1")` + ); + }); + + it('should support VALUES in a CTE without an explicit column list', () => { + const sql = `WITH cte1 AS ( + VALUES ('Value1'), ('Value2') + ) + SELECT * FROM cte1`; + expect(getParsedSql(sql)).to.be.equal( + `WITH "cte1" AS (VALUES ('Value1'), ('Value2')) SELECT * FROM "cte1"` + ); + }); + + it('should support VALUES in a CTE with a trailing semicolon', () => { + const sql = `WITH cte1 (col1) AS ( + VALUES ('Value1'), ('Value2') + ) + SELECT a.name FROM table1 a JOIN cte1 c ON lower(a.name) = lower(c.col1);`; + expect(getParsedSql(sql)).to.be.equal( + `WITH "cte1"("col1") AS (VALUES ('Value1'), ('Value2')) SELECT "a"."name" FROM "table1" AS "a" INNER JOIN "cte1" AS "c" ON lower("a"."name") = lower("c"."col1")` + ); + }); + + it('should support multiple CTEs with VALUES and SELECT', () => { + const sql = `WITH cte1 (col1) AS ( + VALUES ('Value1'), ('Value2') + ), + cte2 AS ( + SELECT name FROM table1 WHERE name IS NOT NULL + ) + SELECT f.name, c.col1 + FROM cte2 f + JOIN cte1 c ON lower(f.name) = lower(c.col1)`; + expect(getParsedSql(sql)).to.be.equal( + `WITH "cte1"("col1") AS (VALUES ('Value1'), ('Value2')), "cte2" AS (SELECT "name" FROM "table1" WHERE "name" IS NOT NULL) SELECT "f"."name", "c"."col1" FROM "cte2" AS "f" INNER JOIN "cte1" AS "c" ON lower("f"."name") = lower("c"."col1")` + ); + }); + + it('should support VALUES and UNION ALL CTEs in the same query', () => { + const sql = `WITH cte1 (col1, col2) AS ( + VALUES ('Name1', 'type1') + ), + cte2 AS ( + SELECT 'Name2' AS col1, 'type1' AS col2 + UNION ALL + SELECT 'Name3', 'type1' + ) + SELECT v.col1 FROM cte1 v + UNION ALL + SELECT u.col1 FROM cte2 u`; + expect(getParsedSql(sql)).to.be.equal( + `WITH "cte1"("col1", "col2") AS (VALUES ('Name1','type1')), "cte2" AS (SELECT 'Name2' AS "col1", 'type1' AS "col2" UNION ALL SELECT 'Name3', 'type1') SELECT "v"."col1" FROM "cte1" AS "v" UNION ALL SELECT "u"."col1" FROM "cte2" AS "u"` + ); + }); })