happy-sql 1.3.0 → 1.4.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 (52) hide show
  1. package/ChangeLog +9 -0
  2. package/lib/parser/fixture/comment-fix.js +3 -0
  3. package/lib/parser/fixture/comment.sql +2 -0
  4. package/lib/parser/fixture/section-multi-fix.js +6 -0
  5. package/lib/parser/fixture/section-multi.sql +3 -0
  6. package/lib/parser/fixture/with-named-fix.js +11 -0
  7. package/lib/parser/fixture/with-named.sql +8 -0
  8. package/lib/parser/parser.js +57 -25
  9. package/lib/parser/visitors/clause/column-definition/column-definition.js +41 -0
  10. package/lib/parser/visitors/clause/create-table/create-table.js +17 -0
  11. package/lib/parser/visitors/clause/create-table/fixture/create-table-autoincrement-fix.js +5 -0
  12. package/lib/parser/visitors/clause/create-table/fixture/create-table-autoincrement.sql +1 -0
  13. package/lib/parser/visitors/clause/create-table/fixture/create-table-serial-fix.js +5 -0
  14. package/lib/parser/visitors/clause/create-table/fixture/create-table-serial.sql +1 -0
  15. package/lib/parser/visitors/clause/index.js +14 -1
  16. package/lib/parser/visitors/clause/insert/fixture/insert-returning-conflict-fix.js +3 -0
  17. package/lib/parser/visitors/clause/insert/fixture/insert-returning-conflict.sql +1 -0
  18. package/lib/parser/visitors/clause/insert/fixture/insert-returning-fix.js +7 -0
  19. package/lib/parser/visitors/clause/insert/fixture/insert-returning-id-fix.js +7 -0
  20. package/lib/parser/visitors/clause/insert/fixture/insert-returning-id.sql +1 -0
  21. package/lib/parser/visitors/clause/insert/fixture/insert-returning.sql +1 -0
  22. package/lib/parser/visitors/clause/insert/insert.js +38 -7
  23. package/lib/parser/visitors/clause/select/fixture/select-last-insert-rowid-fix.js +3 -0
  24. package/lib/parser/visitors/clause/select/fixture/select-last-insert-rowid.sql +1 -0
  25. package/lib/parser/visitors/clause/select/fixture/select-lastval-fix.js +3 -0
  26. package/lib/parser/visitors/clause/select/fixture/select-lastval.sql +1 -0
  27. package/lib/parser/visitors/clause/select/select.js +12 -0
  28. package/lib/parser/visitors/clause/with-named/with-named.js +31 -0
  29. package/lib/printer/visitors/call-expression/clauses/as/as.js +6 -0
  30. package/lib/printer/visitors/call-expression/clauses/auto-increment/auto-increment.js +1 -0
  31. package/lib/printer/visitors/call-expression/clauses/clauses.js +20 -0
  32. package/lib/printer/visitors/call-expression/clauses/column/column.js +10 -0
  33. package/lib/printer/visitors/call-expression/clauses/create-table/create-table.js +18 -0
  34. package/lib/printer/visitors/call-expression/clauses/create-table/fixture/createTable.sql +1 -0
  35. package/lib/printer/visitors/call-expression/clauses/create-table/fixture/createTableSerial.sql +1 -0
  36. package/lib/printer/visitors/call-expression/clauses/insert/insert.js +6 -3
  37. package/lib/printer/visitors/call-expression/clauses/last-insert-rowid/fixture/lastInsertRowid.sql +2 -0
  38. package/lib/printer/visitors/call-expression/clauses/last-insert-rowid/last-insert-rowid.js +1 -0
  39. package/lib/printer/visitors/call-expression/clauses/lastval/fixture/lastval.sql +2 -0
  40. package/lib/printer/visitors/call-expression/clauses/lastval/lastval.js +1 -0
  41. package/lib/printer/visitors/call-expression/clauses/primary-key/primary-key.js +1 -0
  42. package/lib/printer/visitors/call-expression/clauses/returning/fixture/returning-conflict-fix.sql +1 -0
  43. package/lib/printer/visitors/call-expression/clauses/returning/fixture/returning-fix.sql +1 -0
  44. package/lib/printer/visitors/call-expression/clauses/returning/fixture/returning-id-fix.sql +1 -0
  45. package/lib/printer/visitors/call-expression/clauses/returning/fixture/returning-multi-fix.sql +1 -0
  46. package/lib/printer/visitors/call-expression/clauses/returning/returning.js +11 -0
  47. package/lib/printer/visitors/call-expression/clauses/section/fixture/section-multi-fix.sql +7 -0
  48. package/lib/printer/visitors/call-expression/clauses/section/section.js +13 -0
  49. package/lib/printer/visitors/call-expression/clauses/serial/serial.js +1 -0
  50. package/lib/printer/visitors/call-expression/clauses/with-named/fixture/withNamed-fix.sql +10 -0
  51. package/lib/printer/visitors/call-expression/clauses/with-named/with-named.js +21 -0
  52. package/package.json +7 -4
package/ChangeLog CHANGED
@@ -1,3 +1,12 @@
1
+ 2026.08.14, v1.4.0
2
+
3
+ feature:
4
+ - 1f764d9 extend support of sqlite and postgres
5
+ - 3a39fbf with: improve
6
+ - c7d966c with: add
7
+ - bf17b06 parser/printer: RETURNING clause
8
+ - d779866 parser/printer: multi-statement sections
9
+
1
10
  2026.07.29, v1.3.0
2
11
 
3
12
  feature:
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(users)),
3
+ ];
@@ -0,0 +1,2 @@
1
+ -- hello world
2
+ SELECT * FROM users;
@@ -0,0 +1,6 @@
1
+ [
2
+ section('@fix', [
3
+ update(Identifier, set(name === 'isNaN'), where(id === ':nan_id')),
4
+ update(BinaryExpression, set(operator === '==='), where(id === ':id')),
5
+ ]),
6
+ ];
@@ -0,0 +1,3 @@
1
+ -- @fix
2
+ UPDATE Identifier SET name = 'isNaN' WHERE id = :nan_id;
3
+ UPDATE BinaryExpression SET operator = '===' WHERE id = :id;
@@ -0,0 +1,11 @@
1
+ [
2
+ withNamed(call = insert(into(
3
+ CallExpression,
4
+ parent_id,
5
+ values(':parent_id'),
6
+ ), returning(id)), member = insert(into(
7
+ MemberExpression,
8
+ parent_id,
9
+ select(id, from(call)),
10
+ ), returning(id)), insert(into(Identifier, [name, parent_id], select('x', id, from(member))))),
11
+ ];
@@ -0,0 +1,8 @@
1
+ WITH
2
+ call AS (
3
+ INSERT INTO CallExpression (parent_id) VALUES (:parent_id) RETURNING id
4
+ ),
5
+ member AS (
6
+ INSERT INTO MemberExpression (parent_id) SELECT id FROM call RETURNING id
7
+ )
8
+ INSERT INTO Identifier (name, parent_id) SELECT 'x', id FROM member;
@@ -1,4 +1,5 @@
1
- import {parse} from 'sql-parser-cst';
1
+ import {tryCatch} from 'try-catch';
2
+ import {parse as parseCst} from 'sql-parser-cst';
2
3
  import {types} from '@putout/babel';
3
4
  import {convertClause} from '#parser/clause';
4
5
 
@@ -12,42 +13,73 @@ const {
12
13
  program,
13
14
  } = types;
14
15
 
16
+ const DIALECTS = [
17
+ 'postgresql',
18
+ 'sqlite',
19
+ ];
20
+
21
+ const parseCstDialect = (source, dialect) => parseCst(source, {
22
+ dialect,
23
+ paramTypes: [':name'],
24
+ includeRange: true,
25
+ includeComments: true,
26
+ });
27
+
28
+ export const parseSqlNode = (source) => {
29
+ for (const dialect of DIALECTS) {
30
+ const [, cst] = tryCatch(parseCstDialect, source, dialect);
31
+
32
+ if (cst)
33
+ return convertSource(cst);
34
+ }
35
+
36
+ throw Error('Cannot parse SQL');
37
+ };
38
+
15
39
  const getSectionName = (leading) => {
16
- const comment = leading?.find((c) => c.text.startsWith('-- @'));
40
+ if (!leading)
41
+ return null;
42
+
43
+ const comment = leading.find((c) => c.text.startsWith('-- @'));
17
44
 
18
45
  return comment ? comment.text
19
46
  .slice(2)
20
47
  .trim() : null;
21
48
  };
22
49
 
23
- const wrapSection = (stmtNode, sectionName) => {
24
- if (!sectionName)
25
- return stmtNode;
26
-
27
- return callExpression(identifier('section'), [
28
- stringLiteral(sectionName),
29
- stmtNode,
30
- ]);
31
- };
32
-
33
- export const parseSqlNode = (source) => {
34
- const cst = parse(source, {
35
- dialect: 'sqlite',
36
- paramTypes: [':name'],
37
- includeRange: true,
38
- includeComments: true,
39
- });
40
-
50
+ const convertSource = (cst) => {
41
51
  const elements = [];
52
+ let currentSection = null;
53
+ const sectionGroups = new Map();
42
54
 
43
- for (const [i, statement] of cst.statements.entries()) {
44
- if (statement.type === 'empty')
55
+ for (const [i, stmt] of cst.statements.entries()) {
56
+ if (stmt.type === 'empty')
45
57
  continue;
46
58
 
47
- const stmtNode = convertClause(statement);
48
- const leading = i ? statement.leading : cst.leading;
59
+ const stmtNode = convertClause(stmt);
60
+ const leading = i ? stmt.leading : cst.leading;
61
+ const name = getSectionName(leading);
62
+
63
+ if (name)
64
+ currentSection = name;
65
+
66
+ if (currentSection) {
67
+ if (!sectionGroups.has(currentSection))
68
+ sectionGroups.set(currentSection, []);
69
+
70
+ sectionGroups.get(currentSection).push(stmtNode);
71
+ } else {
72
+ elements.push(stmtNode);
73
+ }
74
+ }
75
+
76
+ for (const [name, nodes] of sectionGroups) {
77
+ const inner = nodes.length === 1 ? nodes[0] : arrayExpression(nodes);
49
78
 
50
- elements.push(wrapSection(stmtNode, getSectionName(leading)));
79
+ elements.push(callExpression(identifier('section'), [
80
+ stringLiteral(name),
81
+ inner,
82
+ ]));
51
83
  }
52
84
 
53
85
  return file(program([
@@ -0,0 +1,41 @@
1
+ import {types} from '@putout/babel';
2
+
3
+ const {identifier, callExpression} = types;
4
+
5
+ const getDataType = (dataType) => {
6
+ if (dataType.type === 'identifier')
7
+ return dataType;
8
+
9
+ return dataType.name;
10
+ };
11
+
12
+ const isSerial = (dataType) => getDataType(dataType).name === 'SERIAL';
13
+
14
+ const convertConstraint = (constraint) => {
15
+ if (constraint.type === 'constraint_primary_key')
16
+ return callExpression(identifier('primaryKey'), []);
17
+
18
+ return callExpression(identifier('autoIncrement'), []);
19
+ };
20
+
21
+ export const convertColumnDefinition = (column) => {
22
+ const {
23
+ name,
24
+ dataType,
25
+ constraints,
26
+ } = column;
27
+
28
+ const args = [
29
+ identifier(name.name),
30
+ ];
31
+
32
+ if (isSerial(dataType))
33
+ args.push(callExpression(identifier('serial'), []));
34
+ else
35
+ args.push(identifier(getDataType(dataType).text));
36
+
37
+ for (const constraint of constraints)
38
+ args.push(convertConstraint(constraint));
39
+
40
+ return callExpression(identifier('column'), args);
41
+ };
@@ -0,0 +1,17 @@
1
+ import {types} from '@putout/babel';
2
+ import {convertColumnDefinition} from '#parser/clause/column-definition';
3
+
4
+ const {
5
+ identifier,
6
+ callExpression,
7
+ arrayExpression,
8
+ } = types;
9
+
10
+ export const convertCreateTable = ({name, columns}) => {
11
+ const colNodes = columns.expr.items.map(convertColumnDefinition);
12
+
13
+ return callExpression(identifier('createTable'), [
14
+ identifier(name.name),
15
+ arrayExpression(colNodes),
16
+ ]);
17
+ };
@@ -0,0 +1,5 @@
1
+ [
2
+ createTable(t, [
3
+ column(id, INTEGER, primaryKey(), autoIncrement()),
4
+ ]),
5
+ ];
@@ -0,0 +1 @@
1
+ CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)
@@ -0,0 +1,5 @@
1
+ [
2
+ createTable(t, [
3
+ column(id, serial(), primaryKey()),
4
+ ]),
5
+ ];
@@ -0,0 +1 @@
1
+ CREATE TABLE t (id SERIAL PRIMARY KEY)
@@ -2,6 +2,9 @@ import {convertSelect} from '#parser/clause/select';
2
2
  import {convertInsert} from '#parser/clause/insert';
3
3
  import {convertUpdate} from '#parser/clause/update';
4
4
  import {convertDelete} from '#parser/clause/delete';
5
+ import {convertWithNamed} from '#parser/clause/with-named';
6
+ import {convertCreateTable} from '#parser/clause/create-table';
7
+ import {findClause} from '#parser/clause/find';
5
8
 
6
9
  export const convertClause = (stmt) => {
7
10
  const {type} = stmt;
@@ -13,9 +16,19 @@ export const convertClause = (stmt) => {
13
16
  throw Error(`${type} not supported yet`);
14
17
  };
15
18
 
19
+ const convertInsertOrNamed = (stmt) => {
20
+ const {clauses} = stmt;
21
+
22
+ if (findClause(clauses, 'with_clause'))
23
+ return convertWithNamed(stmt);
24
+
25
+ return convertInsert(stmt);
26
+ };
27
+
16
28
  const clauseVisitors = {
17
29
  select_stmt: convertSelect,
18
- insert_stmt: convertInsert,
30
+ insert_stmt: convertInsertOrNamed,
19
31
  update_stmt: convertUpdate,
20
32
  delete_stmt: convertDelete,
33
+ create_table_stmt: convertCreateTable,
21
34
  };
@@ -0,0 +1,3 @@
1
+ [
2
+ insert(into(t, x, values(':x')), onConflict(x, set(y === ':y')), returning(id)),
3
+ ];
@@ -0,0 +1 @@
1
+ INSERT INTO t (x) VALUES (:x) ON CONFLICT (x) DO UPDATE SET y = :y RETURNING id;
@@ -0,0 +1,7 @@
1
+ [
2
+ insert(into(
3
+ CallExpression,
4
+ parent_id,
5
+ values(':parent_id'),
6
+ ), returning(as(id, 'call_id'))),
7
+ ];
@@ -0,0 +1,7 @@
1
+ [
2
+ insert(into(
3
+ CallExpression,
4
+ parent_id,
5
+ values(':parent_id'),
6
+ ), returning(id)),
7
+ ];
@@ -0,0 +1 @@
1
+ INSERT INTO CallExpression (parent_id) VALUES (:parent_id) RETURNING id;
@@ -0,0 +1 @@
1
+ INSERT INTO CallExpression (parent_id) VALUES (:parent_id) RETURNING id AS call_id;
@@ -1,5 +1,6 @@
1
1
  import {types} from '@putout/babel';
2
2
  import {convertSet} from '#parser/clause/set';
3
+ import {convertSelect} from '#parser/clause/select';
3
4
  import {findClause} from '#parser/clause/find';
4
5
 
5
6
  const {
@@ -12,6 +13,39 @@ const {
12
13
  const convertExprToIdentifier = ({expr}) => identifier(expr.name);
13
14
  const convertColToIdentifier = ({name}) => identifier(name);
14
15
 
16
+ const convertReturningItem = (item) => {
17
+ if (item.type === 'alias') {
18
+ const {name} = item.expr;
19
+ const aliasName = item.alias.name;
20
+
21
+ return callExpression(identifier('as'), [
22
+ identifier(name),
23
+ stringLiteral(aliasName),
24
+ ]);
25
+ }
26
+
27
+ return identifier(item.name);
28
+ };
29
+
30
+ const convertReturning = (returningClause) => {
31
+ const args = returningClause.columns.items.map(convertReturningItem);
32
+
33
+ return callExpression(identifier('returning'), args);
34
+ };
35
+
36
+ const convertSource = (vc) => {
37
+ if (vc.type === 'select_stmt')
38
+ return convertSelect(vc);
39
+
40
+ const vals = vc.values.items[0].expr.items;
41
+ const valArgs = [];
42
+
43
+ for (const p of vals)
44
+ valArgs.push(stringLiteral(p.text));
45
+
46
+ return callExpression(identifier('values'), valArgs);
47
+ };
48
+
15
49
  const convertUpsert = (upsertClause) => {
16
50
  const target = upsertClause.conflictTarget;
17
51
  const {action} = upsertClause;
@@ -26,23 +60,20 @@ export const convertInsert = ({clauses}) => {
26
60
  const [ic] = clauses;
27
61
  const [, vc] = clauses;
28
62
  const uc = findClause(clauses, 'upsert_clause');
63
+ const rc = findClause(clauses, 'returning_clause');
29
64
 
30
65
  const cols = ic.columns.expr.items;
31
- const vals = vc.values.items[0].expr.items;
32
66
  const colArg = cols.length === 1 ? identifier(cols[0].name) : arrayExpression(cols.map(convertColToIdentifier));
33
- const valArgs = [];
34
-
35
- for (const p of vals) {
36
- valArgs.push(stringLiteral(p.text));
37
- }
67
+ const source = convertSource(vc);
38
68
 
39
69
  const args = [
40
70
  callExpression(identifier('into'), [
41
71
  identifier(ic.table.name),
42
72
  colArg,
43
- callExpression(identifier('values'), valArgs),
73
+ source,
44
74
  ]),
45
75
  uc && convertUpsert(uc),
76
+ rc && convertReturning(rc),
46
77
  ].filter(Boolean);
47
78
 
48
79
  return callExpression(identifier('insert'), args);
@@ -0,0 +1,3 @@
1
+ [
2
+ select(lastInsertRowid(), from(users)),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT last_insert_rowid() FROM users
@@ -0,0 +1,3 @@
1
+ [
2
+ select(lastval(), from(users)),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT LASTVAL() FROM users
@@ -16,6 +16,9 @@ const convertColumn = (column) => {
16
16
  if (isCount(column))
17
17
  return convertCount(column);
18
18
 
19
+ if (column.type === 'func_call')
20
+ return convertFuncCall(column);
21
+
19
22
  if (column.type === 'alias')
20
23
  return assignmentExpression('=', identifier(column.alias.name), convertOperand(column.expr));
21
24
 
@@ -25,6 +28,15 @@ const convertColumn = (column) => {
25
28
  return identifier(column.name);
26
29
  };
27
30
 
31
+ const convertFuncCall = (column) => {
32
+ const {name} = column.name;
33
+
34
+ if (name === 'last_insert_rowid')
35
+ return callExpression(identifier('lastInsertRowid'), []);
36
+
37
+ return callExpression(identifier('lastval'), []);
38
+ };
39
+
28
40
  const convertColumns = (columns) => {
29
41
  if (columns.items.length === 1 && columns.items[0].type === 'all_columns')
30
42
  return [
@@ -0,0 +1,31 @@
1
+ import {types} from '@putout/babel';
2
+ import {convertClause} from '#parser/clause';
3
+ import {findClause} from '#parser/clause/find';
4
+
5
+ const {
6
+ identifier,
7
+ assignmentExpression,
8
+ callExpression,
9
+ } = types;
10
+
11
+ const isWithClause = (clause) => clause.type !== 'with_clause';
12
+
13
+ const convertTable = ({table, expr}) => {
14
+ return assignmentExpression('=', identifier(table.name), convertClause(expr.expr));
15
+ };
16
+
17
+ export const convertWithNamed = (stmt) => {
18
+ const withClause = findClause(stmt.clauses, 'with_clause');
19
+
20
+ const finalStatement = convertClause({
21
+ ...stmt,
22
+ clauses: stmt.clauses.filter(isWithClause),
23
+ });
24
+
25
+ const args = [
26
+ ...withClause.tables.items.map(convertTable),
27
+ finalStatement,
28
+ ];
29
+
30
+ return callExpression(identifier('withNamed'), args);
31
+ };
@@ -0,0 +1,6 @@
1
+ export const as = (path, {write, traverse}) => {
2
+ const [column, alias] = path.get('arguments');
3
+ traverse(column);
4
+ write(' AS ');
5
+ write(alias.node.value);
6
+ };
@@ -0,0 +1 @@
1
+ export const autoIncrement = (path, {write}) => write('AUTOINCREMENT');
@@ -10,6 +10,16 @@ import {set} from './set/set.js';
10
10
  import {del} from './delete/delete.js';
11
11
  import {section} from './section/section.js';
12
12
  import {count} from './count/count.js';
13
+ import {returning} from './returning/returning.js';
14
+ import {as} from './as/as.js';
15
+ import {withNamed} from './with-named/with-named.js';
16
+ import {createTable} from './create-table/create-table.js';
17
+ import {column} from './column/column.js';
18
+ import {autoIncrement} from './auto-increment/auto-increment.js';
19
+ import {serial} from './serial/serial.js';
20
+ import {primaryKey} from './primary-key/primary-key.js';
21
+ import {lastInsertRowid} from './last-insert-rowid/last-insert-rowid.js';
22
+ import {lastval} from './lastval/lastval.js';
13
23
 
14
24
  export const clauses = {
15
25
  count,
@@ -24,4 +34,14 @@ export const clauses = {
24
34
  set,
25
35
  delete: del,
26
36
  section,
37
+ returning,
38
+ as,
39
+ withNamed,
40
+ createTable,
41
+ column,
42
+ autoIncrement,
43
+ serial,
44
+ primaryKey,
45
+ lastInsertRowid,
46
+ lastval,
27
47
  };
@@ -0,0 +1,10 @@
1
+ export const column = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+
4
+ for (const [i, arg] of args.entries()) {
5
+ if (i > 0)
6
+ write(' ');
7
+
8
+ traverse(arg);
9
+ }
10
+ };
@@ -0,0 +1,18 @@
1
+ export const createTable = (path, {write, traverse}) => {
2
+ const [name, columns] = path.get('arguments');
3
+
4
+ write('CREATE TABLE ');
5
+ traverse(name);
6
+ write(' (');
7
+
8
+ const elements = columns.get('elements');
9
+
10
+ for (const [i, element] of elements.entries()) {
11
+ if (i > 0)
12
+ write(', ');
13
+
14
+ traverse(element);
15
+ }
16
+
17
+ write(')');
18
+ };
@@ -0,0 +1 @@
1
+ CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)
@@ -0,0 +1 @@
1
+ CREATE TABLE t (id SERIAL PRIMARY KEY)
@@ -1,9 +1,12 @@
1
1
  export const insert = (path, {write, traverse}) => {
2
2
  const args = path.get('arguments');
3
- traverse(args[0]);
3
+ const [intoArg] = args;
4
+ const rest = args.slice(1);
4
5
 
5
- if (args[1]) {
6
+ traverse(intoArg);
7
+
8
+ for (const arg of rest) {
6
9
  write(' ');
7
- traverse(args[1]);
10
+ traverse(arg);
8
11
  }
9
12
  };
@@ -0,0 +1,2 @@
1
+ SELECT last_insert_rowid()
2
+ FROM users
@@ -0,0 +1 @@
1
+ export const lastInsertRowid = (path, {write}) => write('last_insert_rowid()');
@@ -0,0 +1,2 @@
1
+ SELECT LASTVAL()
2
+ FROM users
@@ -0,0 +1 @@
1
+ export const lastval = (path, {write}) => write('LASTVAL()');
@@ -0,0 +1 @@
1
+ export const primaryKey = (path, {write}) => write('PRIMARY KEY');
@@ -0,0 +1 @@
1
+ INSERT INTO t (x) VALUES (:x) ON CONFLICT (x) DO UPDATE SET y = :y RETURNING id
@@ -0,0 +1 @@
1
+ INSERT INTO CallExpression (parent_id) VALUES (:parent_id) RETURNING id AS call_id
@@ -0,0 +1 @@
1
+ INSERT INTO CallExpression (parent_id) VALUES (:parent_id) RETURNING id
@@ -0,0 +1 @@
1
+ INSERT INTO t (x) VALUES (:x) RETURNING id, created_at
@@ -0,0 +1,11 @@
1
+ export const returning = (path, {write, traverse}) => {
2
+ write('RETURNING ');
3
+ const args = path.get('arguments');
4
+
5
+ for (const [i, arg] of args.entries()) {
6
+ if (i > 0)
7
+ write(', ');
8
+
9
+ traverse(arg);
10
+ }
11
+ };
@@ -0,0 +1,7 @@
1
+ -- @fix
2
+ UPDATE Identifier
3
+ SET name = 'isNaN'
4
+ WHERE id = :nan_id;
5
+ UPDATE BinaryExpression
6
+ SET operator = '==='
7
+ WHERE id = :id;
@@ -1,6 +1,19 @@
1
1
  export const section = (path, {write, traverse}) => {
2
2
  const [name, stmt] = path.get('arguments');
3
3
  write(`-- ${name.node.value}\n`);
4
+
5
+ if (stmt.node.type === 'ArrayExpression') {
6
+ const elements = stmt.get('elements');
7
+ const lastIndex = elements.length - 1;
8
+
9
+ for (const [i, element] of elements.entries()) {
10
+ traverse(element);
11
+ write(i < lastIndex ? ';\n' : ';');
12
+ }
13
+
14
+ return;
15
+ }
16
+
4
17
  traverse(stmt);
5
18
  write(';');
6
19
  };
@@ -0,0 +1 @@
1
+ export const serial = (path, {write}) => write('SERIAL');
@@ -0,0 +1,10 @@
1
+ WITH
2
+ call AS (
3
+ INSERT INTO CallExpression (parent_id) VALUES (:parent_id) RETURNING id
4
+ ),
5
+ member AS (
6
+ INSERT INTO MemberExpression (parent_id) SELECT id
7
+ FROM call RETURNING id
8
+ )
9
+ INSERT INTO Identifier (name, parent_id) SELECT 'x', id
10
+ FROM member
@@ -0,0 +1,21 @@
1
+ const isAssignment = (arg) => arg.node.type === 'AssignmentExpression';
2
+
3
+ export const withNamed = (path, {write, traverse}) => {
4
+ const args = path.get('arguments');
5
+ const namedQueries = args.filter(isAssignment);
6
+ const finalStatement = args.at(-1);
7
+
8
+ write('WITH\n');
9
+ const last = namedQueries.length - 1;
10
+
11
+ for (const [i, query] of namedQueries.entries()) {
12
+ write(' ');
13
+ write(query.get('left').node.name);
14
+ write(' AS (\n ');
15
+ traverse(query.get('right'));
16
+ write('\n )');
17
+ write(i < last ? ',\n' : '\n');
18
+ }
19
+
20
+ traverse(finalStatement);
21
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-sql",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "sql to js ast parser and printer",
6
6
  "exports": {
@@ -24,6 +24,9 @@
24
24
  "#parser/clause/insert": "./lib/parser/visitors/clause/insert/insert.js",
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
+ "#parser/clause/with-named": "./lib/parser/visitors/clause/with-named/with-named.js",
28
+ "#parser/clause/create-table": "./lib/parser/visitors/clause/create-table/create-table.js",
29
+ "#parser/clause/column-definition": "./lib/parser/visitors/clause/column-definition/column-definition.js",
27
30
  "#printer": "./lib/printer/printer.js",
28
31
  "#printer/test": "./test/create-printer-test.js"
29
32
  },
@@ -73,8 +76,7 @@
73
76
  "putout": "^42.2.3",
74
77
  "redlint": "^6.9.2",
75
78
  "superc8": "^12.7.0",
76
- "supertape": "^13.2.0",
77
- "try-catch": "^4.0.9"
79
+ "supertape": "^13.2.0"
78
80
  },
79
81
  "dependencies": {
80
82
  "@putout/babel": "^5.7.3",
@@ -82,6 +84,7 @@
82
84
  "@putout/printer": "^18.17.0",
83
85
  "just-camel-case": "^6.2.0",
84
86
  "redstd": "^1.0.2",
85
- "sql-parser-cst": "^0.42.1"
87
+ "sql-parser-cst": "^0.42.1",
88
+ "try-catch": "^4.0.9"
86
89
  }
87
90
  }