happy-sql 2.0.8 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/ChangeLog +5 -0
  2. package/lib/parser/visitors/clause/from/fixture/group-by-fix.js +14 -0
  3. package/lib/parser/visitors/clause/from/fixture/group-by-having-fix.js +22 -0
  4. package/lib/parser/visitors/clause/from/fixture/group-by-having.sql +1 -0
  5. package/lib/parser/visitors/clause/from/fixture/group-by-multi-fix.js +13 -0
  6. package/lib/parser/visitors/clause/from/fixture/group-by-multi.sql +1 -0
  7. package/lib/parser/visitors/clause/from/fixture/group-by.sql +1 -0
  8. package/lib/parser/visitors/clause/from/from.js +9 -1
  9. package/lib/parser/visitors/clause/from/group-by.js +6 -0
  10. package/lib/parser/visitors/clause/from/having.js +8 -0
  11. package/lib/parser/visitors/clause/index.js +12 -1
  12. package/lib/parser/visitors/clause/operand.js +4 -0
  13. package/lib/parser/visitors/clause/select/select.js +16 -0
  14. package/lib/parser/visitors/clause/union/fixture/union-all-fix.js +10 -0
  15. package/lib/parser/visitors/clause/union/fixture/union-all.sql +1 -0
  16. package/lib/parser/visitors/clause/union/union.js +11 -0
  17. package/lib/parser/visitors/clause/where/where.js +1 -1
  18. package/lib/parser/visitors/clause/with-named/fixture/with-named-select-fix.js +19 -0
  19. package/lib/parser/visitors/clause/with-named/fixture/with-named-select.sql +1 -0
  20. package/lib/parser/visitors/clause/with-named/fixture/with-recursive-fix.js +27 -0
  21. package/lib/parser/visitors/clause/with-named/fixture/with-recursive-multi-fix.js +38 -0
  22. package/lib/parser/visitors/clause/with-named/fixture/with-recursive-multi.sql +1 -0
  23. package/lib/parser/visitors/clause/with-named/fixture/with-recursive-no-columns-fix.js +20 -0
  24. package/lib/parser/visitors/clause/with-named/fixture/with-recursive-no-columns.sql +1 -0
  25. package/lib/parser/visitors/clause/with-named/fixture/with-recursive.sql +1 -0
  26. package/lib/parser/visitors/clause/with-named/with-named.js +15 -2
  27. package/lib/printer/visitors/call-expression/clauses/clauses.js +8 -0
  28. package/lib/printer/visitors/call-expression/clauses/from/fixture/group-by-having-fix.sql +5 -0
  29. package/lib/printer/visitors/call-expression/clauses/from/fixture/group-by-having.js +22 -0
  30. package/lib/printer/visitors/call-expression/clauses/from/from.js +13 -6
  31. package/lib/printer/visitors/call-expression/clauses/group-by/fixture/group-by-fix.sql +3 -0
  32. package/lib/printer/visitors/call-expression/clauses/group-by/fixture/group-by-multi-fix.sql +3 -0
  33. package/lib/printer/visitors/call-expression/clauses/group-by/fixture/group-by-multi.js +13 -0
  34. package/lib/printer/visitors/call-expression/clauses/group-by/fixture/group-by.js +14 -0
  35. package/lib/printer/visitors/call-expression/clauses/group-by/group-by.js +12 -0
  36. package/lib/printer/visitors/call-expression/clauses/having/fixture/having-fix.sql +3 -0
  37. package/lib/printer/visitors/call-expression/clauses/having/fixture/having.js +16 -0
  38. package/lib/printer/visitors/call-expression/clauses/having/having.js +4 -0
  39. package/lib/printer/visitors/call-expression/clauses/insert/insert.js +1 -1
  40. package/lib/printer/visitors/call-expression/clauses/join/join.js +1 -1
  41. package/lib/printer/visitors/call-expression/clauses/select/select.js +1 -1
  42. package/lib/printer/visitors/call-expression/clauses/union-all/fixture/union-all-fix.sql +3 -0
  43. package/lib/printer/visitors/call-expression/clauses/union-all/fixture/union-all.js +1 -0
  44. package/lib/printer/visitors/call-expression/clauses/union-all/union-all.js +9 -0
  45. package/lib/printer/visitors/call-expression/clauses/with-named/fixture/with-named-insert-fix.sql +3 -3
  46. package/lib/printer/visitors/call-expression/clauses/with-named/fixture/with-named-select-fix.sql +8 -0
  47. package/lib/printer/visitors/call-expression/clauses/with-named/fixture/with-named-select.js +19 -0
  48. package/lib/printer/visitors/call-expression/clauses/with-named/fixture/withNamed-fix.sql +3 -3
  49. package/lib/printer/visitors/call-expression/clauses/with-recursive/fixture/with-recursive-fix.sql +10 -0
  50. package/lib/printer/visitors/call-expression/clauses/with-recursive/fixture/with-recursive-multi-fix.sql +14 -0
  51. package/lib/printer/visitors/call-expression/clauses/with-recursive/fixture/with-recursive-multi.js +38 -0
  52. package/lib/printer/visitors/call-expression/clauses/with-recursive/fixture/with-recursive-no-columns-fix.sql +8 -0
  53. package/lib/printer/visitors/call-expression/clauses/with-recursive/fixture/with-recursive-no-columns.js +20 -0
  54. package/lib/printer/visitors/call-expression/clauses/with-recursive/fixture/with-recursive.js +27 -0
  55. package/lib/printer/visitors/call-expression/clauses/with-recursive/with-recursive.js +57 -0
  56. package/package.json +2 -1
package/ChangeLog CHANGED
@@ -1,3 +1,8 @@
1
+ 2026.08.22, v2.1.0
2
+
3
+ feature:
4
+ - 533076c parser: WITH RECURSIVE: add
5
+
1
6
  2026.08.21, v2.0.8
2
7
 
3
8
  fix:
@@ -0,0 +1,14 @@
1
+ [
2
+ select(
3
+ kind,
4
+ count(
5
+ '*',
6
+ ),
7
+ from(
8
+ VariableDeclaration,
9
+ groupBy(
10
+ kind,
11
+ ),
12
+ ),
13
+ ),
14
+ ];
@@ -0,0 +1,22 @@
1
+ [
2
+ select(
3
+ kind,
4
+ count(
5
+ '*',
6
+ ),
7
+ from(
8
+ VariableDeclaration,
9
+ where(
10
+ kind === 'const',
11
+ ),
12
+ groupBy(
13
+ kind,
14
+ ),
15
+ having(
16
+ count(
17
+ '*',
18
+ ) > 1,
19
+ ),
20
+ ),
21
+ ),
22
+ ];
@@ -0,0 +1 @@
1
+ SELECT kind, COUNT(*) FROM VariableDeclaration WHERE kind = 'const' GROUP BY kind HAVING COUNT(*) > 1
@@ -0,0 +1,13 @@
1
+ [
2
+ select(
3
+ kind,
4
+ name,
5
+ from(
6
+ VariableDeclaration,
7
+ groupBy(
8
+ kind,
9
+ name,
10
+ ),
11
+ ),
12
+ ),
13
+ ];
@@ -0,0 +1 @@
1
+ SELECT kind, name FROM VariableDeclaration GROUP BY kind, name
@@ -0,0 +1 @@
1
+ SELECT kind, COUNT(*) FROM VariableDeclaration GROUP BY kind
@@ -1,5 +1,7 @@
1
1
  import {types} from '@putout/babel';
2
2
  import {convertOperand} from '#parser/clause/operand';
3
+ import {convertGroupBy} from './group-by.js';
4
+ import {convertHaving} from './having.js';
3
5
 
4
6
  const isString = (a) => typeof a === 'string';
5
7
 
@@ -108,7 +110,7 @@ const flattenJoins = (node, joins = []) => {
108
110
  return joins;
109
111
  };
110
112
 
111
- export const convertFrom = ({expr, whereClause}) => {
113
+ export const convertFrom = ({expr, whereClause, groupByClause, havingClause}) => {
112
114
  const fromArgs = [];
113
115
 
114
116
  if (whereClause)
@@ -116,6 +118,12 @@ export const convertFrom = ({expr, whereClause}) => {
116
118
  convertCondition(whereClause.expr),
117
119
  ]));
118
120
 
121
+ if (groupByClause)
122
+ fromArgs.push(convertGroupBy(groupByClause));
123
+
124
+ if (havingClause)
125
+ fromArgs.push(convertHaving(havingClause));
126
+
119
127
  if (expr.type === 'join_expr') {
120
128
  const parts = flattenJoins(expr);
121
129
  return callExpression(identifier('from'), parts);
@@ -0,0 +1,6 @@
1
+ import {types} from '@putout/babel';
2
+ import {convertOperand} from '#parser/clause/operand';
3
+
4
+ const {callExpression, identifier} = types;
5
+
6
+ export const convertGroupBy = (clause) => callExpression(identifier('groupBy'), clause.columns.items.map(convertOperand));
@@ -0,0 +1,8 @@
1
+ import {types} from '@putout/babel';
2
+ import {convertCondition} from '#parser/clause/where';
3
+
4
+ const {callExpression, identifier} = types;
5
+
6
+ export const convertHaving = (clause) => callExpression(identifier('having'), [
7
+ convertCondition(clause.expr),
8
+ ]);
@@ -5,6 +5,7 @@ import {convertDelete} from '#parser/clause/delete';
5
5
  import {convertWithNamed} from '#parser/clause/with-named';
6
6
  import {convertCreateTable} from '#parser/clause/create-table';
7
7
  import {convertCreateSequence} from '#parser/clause/create-sequence';
8
+ import {convertUnionAll} from '#parser/clause/union';
8
9
  import {findClause} from '#parser/clause/find';
9
10
 
10
11
  export const convertClause = (stmt) => {
@@ -26,11 +27,21 @@ const convertInsertOrNamed = (stmt) => {
26
27
  return convertInsert(stmt);
27
28
  };
28
29
 
30
+ const convertSelectOrNamed = (stmt) => {
31
+ const {clauses} = stmt;
32
+
33
+ if (findClause(clauses, 'with_clause'))
34
+ return convertWithNamed(stmt);
35
+
36
+ return convertSelect(stmt);
37
+ };
38
+
29
39
  const clauseVisitors = {
30
- select_stmt: convertSelect,
40
+ select_stmt: convertSelectOrNamed,
31
41
  insert_stmt: convertInsertOrNamed,
32
42
  update_stmt: convertUpdate,
33
43
  delete_stmt: convertDelete,
34
44
  create_table_stmt: convertCreateTable,
35
45
  create_sequence_stmt: convertCreateSequence,
46
+ compound_select_stmt: convertUnionAll,
36
47
  };
@@ -1,4 +1,5 @@
1
1
  import {types} from '@putout/babel';
2
+ import {convertCount, isCount} from '#parser/clause/count';
2
3
 
3
4
  const {
4
5
  identifier,
@@ -10,6 +11,9 @@ const {
10
11
  } = types;
11
12
 
12
13
  export const convertOperand = (node) => {
14
+ if (isCount(node))
15
+ return convertCount(node);
16
+
13
17
  if (node.type === 'member_expr')
14
18
  return memberExpression(identifier(node.object.name), identifier(node.property.name));
15
19
 
@@ -11,6 +11,8 @@ const {
11
11
  tsLiteralType,
12
12
  callExpression,
13
13
  memberExpression,
14
+ numericLiteral,
15
+ binaryExpression,
14
16
  } = types;
15
17
 
16
18
  const convertColumn = (column) => {
@@ -32,6 +34,16 @@ const convertColumn = (column) => {
32
34
  if (column.type === 'parameter')
33
35
  return stringLiteral(column.text);
34
36
 
37
+ if (column.type === 'number_literal')
38
+ return numericLiteral(column.value);
39
+
40
+ if (column.type === 'binary_expr')
41
+ return binaryExpression(
42
+ column.operator,
43
+ convertOperand(column.left),
44
+ convertOperand(column.right),
45
+ );
46
+
35
47
  return identifier(column.name);
36
48
  };
37
49
 
@@ -57,12 +69,16 @@ export const convertSelect = ({clauses}) => {
57
69
  const selectClause = findClause(clauses, 'select_clause');
58
70
  const fromClause = findClause(clauses, 'from_clause');
59
71
  const whereClause = findClause(clauses, 'where_clause');
72
+ const groupByClause = findClause(clauses, 'group_by_clause');
73
+ const havingClause = findClause(clauses, 'having_clause');
60
74
 
61
75
  const args = [
62
76
  ...convertColumns(selectClause.columns),
63
77
  fromClause && convertFrom({
64
78
  expr: fromClause.expr,
65
79
  whereClause,
80
+ groupByClause,
81
+ havingClause,
66
82
  }),
67
83
  ].filter(Boolean);
68
84
 
@@ -0,0 +1,10 @@
1
+ [
2
+ unionAll(
3
+ select(
4
+ 1,
5
+ ),
6
+ select(
7
+ 2,
8
+ ),
9
+ ),
10
+ ];
@@ -0,0 +1 @@
1
+ SELECT 1 UNION ALL SELECT 2
@@ -0,0 +1,11 @@
1
+ import {types} from '@putout/babel';
2
+ import {convertClause} from '#parser/clause';
3
+
4
+ const {callExpression, identifier} = types;
5
+
6
+ export const convertUnionAll = (stmt) => {
7
+ return callExpression(identifier('unionAll'), [
8
+ convertClause(stmt.left),
9
+ convertClause(stmt.right),
10
+ ]);
11
+ };
@@ -71,7 +71,7 @@ const convertBinary = (node) => {
71
71
  );
72
72
  };
73
73
 
74
- const convertCondition = (node) => {
74
+ export const convertCondition = (node) => {
75
75
  if (node.operator?.name === 'AND')
76
76
  return convertAnd(node);
77
77
 
@@ -0,0 +1,19 @@
1
+ [
2
+ withNamed(
3
+ recent = select(
4
+ id,
5
+ from(
6
+ users,
7
+ where(
8
+ kind === 'const',
9
+ ),
10
+ ),
11
+ ),
12
+ select(
13
+ id,
14
+ from(
15
+ recent,
16
+ ),
17
+ ),
18
+ ),
19
+ ];
@@ -0,0 +1 @@
1
+ WITH recent AS (SELECT id FROM users WHERE kind = 'const') SELECT id FROM recent
@@ -0,0 +1,27 @@
1
+ [
2
+ withRecursive(
3
+ numbers(
4
+ 'value',
5
+ unionAll(
6
+ select(
7
+ 1,
8
+ ),
9
+ select(
10
+ value + 1,
11
+ from(
12
+ numbers,
13
+ where(
14
+ value < 10,
15
+ ),
16
+ ),
17
+ ),
18
+ ),
19
+ ),
20
+ select(
21
+ value,
22
+ from(
23
+ numbers,
24
+ ),
25
+ ),
26
+ ),
27
+ ];
@@ -0,0 +1,38 @@
1
+ [
2
+ withRecursive(
3
+ t(
4
+ unionAll(
5
+ select(
6
+ 1,
7
+ ),
8
+ select(
9
+ 2,
10
+ ),
11
+ ),
12
+ ),
13
+ u(
14
+ 'a',
15
+ 'b',
16
+ unionAll(
17
+ select(
18
+ 1,
19
+ 2,
20
+ ),
21
+ select(
22
+ a,
23
+ b,
24
+ from(
25
+ t,
26
+ ),
27
+ ),
28
+ ),
29
+ ),
30
+ select(
31
+ a,
32
+ b,
33
+ from(
34
+ u,
35
+ ),
36
+ ),
37
+ ),
38
+ ];
@@ -0,0 +1 @@
1
+ WITH RECURSIVE t AS (SELECT 1 UNION ALL SELECT 2), u(a, b) AS (SELECT 1, 2 UNION ALL SELECT a, b FROM t) SELECT a, b FROM u
@@ -0,0 +1,20 @@
1
+ [
2
+ withRecursive(
3
+ t(
4
+ unionAll(
5
+ select(
6
+ 1,
7
+ ),
8
+ select(
9
+ 2,
10
+ ),
11
+ ),
12
+ ),
13
+ select(
14
+ '*',
15
+ from(
16
+ t,
17
+ ),
18
+ ),
19
+ ),
20
+ ];
@@ -0,0 +1 @@
1
+ WITH RECURSIVE t AS (SELECT 1 UNION ALL SELECT 2) SELECT * FROM t
@@ -0,0 +1 @@
1
+ WITH RECURSIVE numbers(value) AS (SELECT 1 UNION ALL SELECT value + 1 FROM numbers WHERE value < 10) SELECT value FROM numbers
@@ -6,6 +6,7 @@ const {
6
6
  identifier,
7
7
  assignmentExpression,
8
8
  callExpression,
9
+ stringLiteral,
9
10
  } = types;
10
11
 
11
12
  const isWithClause = (clause) => clause.type !== 'with_clause';
@@ -14,18 +15,30 @@ const convertTable = ({table, expr}) => {
14
15
  return assignmentExpression('=', identifier(table.name), convertClause(expr.expr));
15
16
  };
16
17
 
18
+ // recursive CTE: numbers('value', unionAll(...))
19
+ const convertRecursiveTable = ({table, columns, expr}) => {
20
+ const colArgs = columns ? columns.expr.items.map((column) => stringLiteral(column.name)) : [];
21
+ const body = convertClause(expr.expr);
22
+
23
+ return callExpression(identifier(table.name), [...colArgs, body]);
24
+ };
25
+
17
26
  export const convertWithNamed = (stmt) => {
18
27
  const withClause = findClause(stmt.clauses, 'with_clause');
28
+ const isRecursive = Boolean(withClause.recursiveKw);
29
+ const fnName = isRecursive ? 'withRecursive' : 'withNamed';
19
30
 
20
31
  const finalStatement = convertClause({
21
32
  ...stmt,
22
33
  clauses: stmt.clauses.filter(isWithClause),
23
34
  });
24
35
 
36
+ const convertCte = isRecursive ? convertRecursiveTable : convertTable;
37
+
25
38
  const args = [
26
- ...withClause.tables.items.map(convertTable),
39
+ ...withClause.tables.items.map(convertCte),
27
40
  finalStatement,
28
41
  ];
29
42
 
30
- return callExpression(identifier('withNamed'), args);
43
+ return callExpression(identifier(fnName), args);
31
44
  };
@@ -14,6 +14,10 @@ import {section} from './section/section.js';
14
14
  import {count} from './count/count.js';
15
15
  import {returning} from './returning/returning.js';
16
16
  import {withNamed} from './with-named/with-named.js';
17
+ import {withRecursive} from './with-recursive/with-recursive.js';
18
+ import {unionAll} from './union-all/union-all.js';
19
+ import {groupBy} from './group-by/group-by.js';
20
+ import {having} from './having/having.js';
17
21
  import {createTable} from './create-table/create-table.js';
18
22
  import {createSequence} from './create-sequence/create-sequence.js';
19
23
  import {column} from './column/column.js';
@@ -48,6 +52,10 @@ export const clauses = {
48
52
  section,
49
53
  returning,
50
54
  withNamed,
55
+ withRecursive,
56
+ unionAll,
57
+ groupBy,
58
+ having,
51
59
  createTable,
52
60
  createSequence,
53
61
  column,
@@ -0,0 +1,5 @@
1
+ SELECT kind, COUNT(*)
2
+ FROM VariableDeclaration
3
+ WHERE kind = 'const'
4
+ GROUP BY kind
5
+ HAVING COUNT(*) > 1
@@ -0,0 +1,22 @@
1
+ [
2
+ select(
3
+ kind,
4
+ count(
5
+ '*',
6
+ ),
7
+ from(
8
+ VariableDeclaration,
9
+ where(
10
+ kind === 'const',
11
+ ),
12
+ groupBy(
13
+ kind,
14
+ ),
15
+ having(
16
+ count(
17
+ '*',
18
+ ) > 1,
19
+ ),
20
+ ),
21
+ ),
22
+ ];
@@ -7,6 +7,14 @@ const printAsExpression = (path, {write, traverse}) => {
7
7
  write(` AS ${path.node.typeAnnotation.literal.value}`);
8
8
  };
9
9
 
10
+ const CLAUSE_NAMES = [
11
+ 'where',
12
+ 'groupBy',
13
+ 'having',
14
+ ];
15
+
16
+ const isClause = ({node}) => node.type === 'CallExpression' && CLAUSE_NAMES.includes(node.callee?.name);
17
+
10
18
  const printArg = (path, ctx) => {
11
19
  if (isTSAsExpression(path.node))
12
20
  return printAsExpression(path, ctx);
@@ -16,9 +24,8 @@ const printArg = (path, ctx) => {
16
24
 
17
25
  export const from = (path, {write, traverse}) => {
18
26
  const args = path.get('arguments');
19
- const lastArg = args.at(-1);
20
- const hasWhere = lastArg?.node?.callee?.name === 'where';
21
- const tableArgs = hasWhere ? args.slice(0, -1) : args;
27
+ const tableArgs = args.filter((arg) => !isClause(arg));
28
+ const clauseArgs = args.filter(isClause);
22
29
 
23
30
  write('FROM ');
24
31
  printArg(tableArgs[0], {
@@ -32,8 +39,8 @@ export const from = (path, {write, traverse}) => {
32
39
  traverse,
33
40
  });
34
41
 
35
- if (hasWhere) {
36
- write.newline();
37
- traverse(lastArg);
42
+ for (const clause of clauseArgs) {
43
+ write.breakline();
44
+ traverse(clause);
38
45
  }
39
46
  };
@@ -0,0 +1,3 @@
1
+ SELECT kind, COUNT(*)
2
+ FROM VariableDeclaration
3
+ GROUP BY kind
@@ -0,0 +1,3 @@
1
+ SELECT kind, name
2
+ FROM VariableDeclaration
3
+ GROUP BY kind, name
@@ -0,0 +1,13 @@
1
+ [
2
+ select(
3
+ kind,
4
+ name,
5
+ from(
6
+ VariableDeclaration,
7
+ groupBy(
8
+ kind,
9
+ name,
10
+ ),
11
+ ),
12
+ ),
13
+ ];
@@ -0,0 +1,14 @@
1
+ [
2
+ select(
3
+ kind,
4
+ count(
5
+ '*',
6
+ ),
7
+ from(
8
+ VariableDeclaration,
9
+ groupBy(
10
+ kind,
11
+ ),
12
+ ),
13
+ ),
14
+ ];
@@ -0,0 +1,12 @@
1
+ export const groupBy = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+
4
+ write('GROUP BY ');
5
+
6
+ for (const [i, arg] of args.entries()) {
7
+ if (i > 0)
8
+ write(', ');
9
+
10
+ traverse(arg);
11
+ }
12
+ };
@@ -0,0 +1,3 @@
1
+ SELECT kind, COUNT(*)
2
+ FROM VariableDeclaration
3
+ HAVING COUNT(*) > 1
@@ -0,0 +1,16 @@
1
+ [
2
+ select(
3
+ kind,
4
+ count(
5
+ '*',
6
+ ),
7
+ from(
8
+ VariableDeclaration,
9
+ having(
10
+ count(
11
+ '*',
12
+ ) > 1,
13
+ ),
14
+ ),
15
+ ),
16
+ ];
@@ -0,0 +1,4 @@
1
+ export const having = (path, {write, traverse}) => {
2
+ write('HAVING ');
3
+ traverse(path.get('arguments')[0]);
4
+ };
@@ -6,7 +6,7 @@ export const insert = (path, {write, traverse}) => {
6
6
  traverse(intoArg);
7
7
 
8
8
  for (const arg of rest) {
9
- write.newline();
9
+ write.breakline();
10
10
  traverse(arg);
11
11
  }
12
12
  };
@@ -17,7 +17,7 @@ const printTable = (path, ctx) => {
17
17
  export const join = (path, {write, traverse}) => {
18
18
  const [table, onClause] = path.get('arguments');
19
19
 
20
- write.newline();
20
+ write.breakline();
21
21
  write('JOIN ');
22
22
  printTable(table, {
23
23
  write,
@@ -34,7 +34,7 @@ export const select = (path, {write, traverse}) => {
34
34
  }
35
35
 
36
36
  if (fromArg) {
37
- write.newline();
37
+ write.breakline();
38
38
  traverse(fromArg);
39
39
  }
40
40
  };
@@ -0,0 +1 @@
1
+ [unionAll(select(1), select(2))];
@@ -0,0 +1,9 @@
1
+ export const unionAll = (path, {write, traverse}) => {
2
+ const [left, right] = path.get('arguments');
3
+
4
+ traverse(left);
5
+ write.breakline();
6
+ write('UNION ALL');
7
+ write.breakline();
8
+ traverse(right);
9
+ };
@@ -1,12 +1,12 @@
1
1
  WITH
2
2
  call AS (
3
3
  INSERT INTO CallExpression (file, parent_id) VALUES (:file, :parent_id)
4
- RETURNING id
4
+ RETURNING id
5
5
  ),
6
6
  member AS (
7
7
  INSERT INTO MemberExpression (file, parent_id) SELECT :file, id
8
- FROM call
9
- RETURNING id
8
+ FROM call
9
+ RETURNING id
10
10
  )
11
11
  INSERT INTO Identifier (file, name, parent_id) SELECT :file, 'isNaN', id
12
12
  FROM member
@@ -0,0 +1,8 @@
1
+ WITH
2
+ recent AS (
3
+ SELECT id
4
+ FROM users
5
+ WHERE kind = 'const'
6
+ )
7
+ SELECT id
8
+ FROM recent
@@ -0,0 +1,19 @@
1
+ [
2
+ withNamed(
3
+ recent = select(
4
+ id,
5
+ from(
6
+ users,
7
+ where(
8
+ kind === 'const',
9
+ ),
10
+ ),
11
+ ),
12
+ select(
13
+ id,
14
+ from(
15
+ recent,
16
+ ),
17
+ ),
18
+ ),
19
+ ];
@@ -1,12 +1,12 @@
1
1
  WITH
2
2
  call AS (
3
3
  INSERT INTO CallExpression (parent_id) VALUES (:parent_id)
4
- RETURNING id
4
+ RETURNING id
5
5
  ),
6
6
  member AS (
7
7
  INSERT INTO MemberExpression (parent_id) SELECT id
8
- FROM call
9
- RETURNING id
8
+ FROM call
9
+ RETURNING id
10
10
  )
11
11
  INSERT INTO Identifier (name, parent_id) SELECT 'x', id
12
12
  FROM member
@@ -0,0 +1,10 @@
1
+ WITH RECURSIVE
2
+ numbers(value) AS (
3
+ SELECT 1
4
+ UNION ALL
5
+ SELECT value + 1
6
+ FROM numbers
7
+ WHERE value < 10
8
+ )
9
+ SELECT value
10
+ FROM numbers
@@ -0,0 +1,14 @@
1
+ WITH RECURSIVE
2
+ t AS (
3
+ SELECT 1
4
+ UNION ALL
5
+ SELECT 2
6
+ ),
7
+ u(a, b) AS (
8
+ SELECT 1, 2
9
+ UNION ALL
10
+ SELECT a, b
11
+ FROM t
12
+ )
13
+ SELECT a, b
14
+ FROM u
@@ -0,0 +1,38 @@
1
+ [
2
+ withRecursive(
3
+ t(
4
+ unionAll(
5
+ select(
6
+ 1,
7
+ ),
8
+ select(
9
+ 2,
10
+ ),
11
+ ),
12
+ ),
13
+ u(
14
+ 'a',
15
+ 'b',
16
+ unionAll(
17
+ select(
18
+ 1,
19
+ 2,
20
+ ),
21
+ select(
22
+ a,
23
+ b,
24
+ from(
25
+ t,
26
+ ),
27
+ ),
28
+ ),
29
+ ),
30
+ select(
31
+ a,
32
+ b,
33
+ from(
34
+ u,
35
+ ),
36
+ ),
37
+ ),
38
+ ];
@@ -0,0 +1,8 @@
1
+ WITH RECURSIVE
2
+ t AS (
3
+ SELECT 1
4
+ UNION ALL
5
+ SELECT 2
6
+ )
7
+ SELECT *
8
+ FROM t
@@ -0,0 +1,20 @@
1
+ [
2
+ withRecursive(
3
+ t(
4
+ unionAll(
5
+ select(
6
+ 1,
7
+ ),
8
+ select(
9
+ 2,
10
+ ),
11
+ ),
12
+ ),
13
+ select(
14
+ '*',
15
+ from(
16
+ t,
17
+ ),
18
+ ),
19
+ ),
20
+ ];
@@ -0,0 +1,27 @@
1
+ [
2
+ withRecursive(
3
+ numbers(
4
+ 'value',
5
+ unionAll(
6
+ select(
7
+ 1,
8
+ ),
9
+ select(
10
+ value + 1,
11
+ from(
12
+ numbers,
13
+ where(
14
+ value < 10,
15
+ ),
16
+ ),
17
+ ),
18
+ ),
19
+ ),
20
+ select(
21
+ value,
22
+ from(
23
+ numbers,
24
+ ),
25
+ ),
26
+ ),
27
+ ];
@@ -0,0 +1,57 @@
1
+ const isStringLiteral = ({type}) => type === 'StringLiteral';
2
+
3
+ export const withRecursive = (path, {write, indent, traverse}) => {
4
+ const args = path.get('arguments');
5
+ const ctes = args.slice(0, -1);
6
+ const finalStatement = args.at(-1);
7
+
8
+ write('WITH RECURSIVE');
9
+ write.newline();
10
+
11
+ const last = ctes.length - 1;
12
+
13
+ for (const [i, cte] of ctes.entries()) {
14
+ const {name} = cte.node.callee;
15
+ const cteArgs = cte.get('arguments');
16
+ const colArgs = cteArgs.filter(isStringLiteral);
17
+
18
+ indent.inc();
19
+ write.indent();
20
+ indent.dec();
21
+ write(name);
22
+
23
+ if (colArgs.length) {
24
+ write('(');
25
+
26
+ for (const [j, col] of colArgs.entries()) {
27
+ if (j > 0)
28
+ write(', ');
29
+
30
+ write(col.node.value);
31
+ }
32
+
33
+ write(')');
34
+ }
35
+
36
+ write(' AS (');
37
+ write.newline();
38
+ indent.inc();
39
+ indent.inc();
40
+ write.indent();
41
+ traverse(cteArgs.at(-1));
42
+ indent.dec();
43
+ indent.dec();
44
+ write.newline();
45
+ indent.inc();
46
+ write.indent();
47
+ indent.dec();
48
+ write(')');
49
+
50
+ if (i < last)
51
+ write(',');
52
+
53
+ write.newline();
54
+ }
55
+
56
+ traverse(finalStatement);
57
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-sql",
3
- "version": "2.0.8",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "sql to js ast parser and printer",
6
6
  "exports": {
@@ -25,6 +25,7 @@
25
25
  "#parser/clause/update": "./lib/parser/visitors/clause/update/update.js",
26
26
  "#parser/clause/delete": "./lib/parser/visitors/clause/delete/delete.js",
27
27
  "#parser/clause/with-named": "./lib/parser/visitors/clause/with-named/with-named.js",
28
+ "#parser/clause/union": "./lib/parser/visitors/clause/union/union.js",
28
29
  "#parser/clause/create-table": "./lib/parser/visitors/clause/create-table/create-table.js",
29
30
  "#parser/clause/create-sequence": "./lib/parser/visitors/clause/create-sequence/create-sequence.js",
30
31
  "#parser/clause/column-definition": "./lib/parser/visitors/clause/column-definition/column-definition.js",