happy-sql 2.4.0 → 2.5.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 (38) hide show
  1. package/ChangeLog +34 -0
  2. package/lib/parser/parser.js +11 -11
  3. package/lib/parser/visitors/clause/alter-table/alter-table.js +9 -6
  4. package/lib/parser/visitors/clause/binary.js +17 -8
  5. package/lib/parser/visitors/clause/column-definition/column-definition.js +22 -9
  6. package/lib/parser/visitors/clause/create-index/create-index.js +1 -1
  7. package/lib/parser/visitors/clause/create-table/create-table.js +9 -4
  8. package/lib/parser/visitors/clause/create-view/create-view.js +2 -1
  9. package/lib/parser/visitors/clause/from/from.js +21 -60
  10. package/lib/parser/visitors/clause/from/group-by.js +8 -1
  11. package/lib/parser/visitors/clause/from/limit.js +1 -1
  12. package/lib/parser/visitors/clause/from/order-by.js +2 -1
  13. package/lib/parser/visitors/clause/from/table-func.js +10 -1
  14. package/lib/parser/visitors/clause/index.js +18 -2
  15. package/lib/parser/visitors/clause/insert/insert.js +44 -6
  16. package/lib/parser/visitors/clause/operand.js +11 -1
  17. package/lib/parser/visitors/clause/select/select.js +20 -9
  18. package/lib/parser/visitors/clause/where/where.js +30 -3
  19. package/lib/parser/visitors/clause/with-named/with-named.js +15 -1
  20. package/lib/printer/visitors/binary-expression.js +28 -5
  21. package/lib/printer/visitors/call-expression/clauses/add-constraint/add-constraint.js +4 -0
  22. package/lib/printer/visitors/call-expression/clauses/clauses.js +10 -1
  23. package/lib/printer/visitors/call-expression/clauses/create-index/create-index.js +10 -1
  24. package/lib/printer/visitors/call-expression/clauses/from/from.js +64 -47
  25. package/lib/printer/visitors/call-expression/clauses/into/into.js +5 -2
  26. package/lib/printer/visitors/call-expression/clauses/is-null/is-null.js +14 -0
  27. package/lib/printer/visitors/call-expression/clauses/json-op.js +8 -1
  28. package/lib/printer/visitors/call-expression/clauses/lateral-join/lateral-join.js +7 -11
  29. package/lib/printer/visitors/call-expression/clauses/not/not.js +13 -1
  30. package/lib/printer/visitors/call-expression/clauses/on-conflict/on-conflict.js +9 -2
  31. package/lib/printer/visitors/call-expression/clauses/references/references.js +26 -14
  32. package/lib/printer/visitors/call-expression/clauses/section/section.js +5 -1
  33. package/lib/printer/visitors/call-expression/clauses/select/select.js +7 -6
  34. package/lib/printer/visitors/call-expression/clauses/update/update.js +7 -11
  35. package/lib/printer/visitors/call-expression/clauses/window-frame/window-frame.js +2 -2
  36. package/lib/printer/visitors/logical-expression.js +10 -2
  37. package/lib/printer/visitors/string-literal.js +15 -1
  38. package/package.json +3 -3
package/ChangeLog CHANGED
@@ -1,3 +1,37 @@
1
+ 2026.11.15, v2.5.0
2
+
3
+ feature:
4
+ - 1c961c1 happy-sql: apply type checker
5
+ - a4f7d90 happy-sql: where: keep parentheses in nested expressions
6
+
7
+ 2026.11.14, v2.4.1
8
+
9
+ fix:
10
+ - 10d5818 happy-sql: join
11
+ - 7510903 alter-table: rename table, add constraint, RLS actions
12
+ - a4e14a1 references: ON DELETE and ON UPDATE actions
13
+ - 60cbd74 from: extract capitalizeWord, remove inlined arrow
14
+ - 3ebbea9 with-named: extract convertColumn, remove inlined arrow
15
+ - 8848396 printer/from: remove inlined arrows and ternary
16
+ - 0cc326c window-frame: remove optional chaining
17
+ - 652387a references: remove optional chaining
18
+ - 344f7c3 clause/index: remove optional chaining, nullish coalescing, inlined arrows
19
+ - 313fa5f binary: remove optional chaining and nullish coalescing
20
+ - 12b6f3c insert: remove optional chaining and inlined arrows/ternaries
21
+ - 652bcad create-index: remove optional chaining
22
+ - b5b40ac limit: remove optional chaining
23
+ - 5eb1732 create-table: remove optional chaining and inlined arrows
24
+ - f078532 create-view: extract isMaterializedKind predicate
25
+ - 472e627 order-by: extract nulls order fn name
26
+ - ddcb389 group-by: remove inlined arrow in map
27
+ - 1be75b4 table-func: extract getRawName, remove inlined ternary
28
+ - 10a0b35 column-definition: remove optional chaining, nullish coalescing, inlined arrows
29
+ - 9108d3e operand: remove optional chaining and nullish coalescing
30
+
31
+ feature:
32
+ - ffaef07 happy-sql: sql-parser-cst v0.44.1
33
+ - 981cf13 happy-sql: @putout/plugin-printer v9.0.0
34
+
1
35
  2026.08.27, v2.4.0
2
36
 
3
37
  fix:
@@ -18,19 +18,17 @@ const DIALECTS = [
18
18
  'sqlite',
19
19
  ];
20
20
 
21
- const parseCstDialect = (source, dialect) => parseCst(source, {
22
- dialect,
23
- paramTypes: [':name'],
24
- includeRange: true,
25
- includeComments: true,
26
- });
27
-
28
21
  export const parseSql = (source) => {
29
22
  let cst;
30
23
  let error;
31
24
 
32
25
  for (const dialect of DIALECTS) {
33
- [error, cst] = tryCatch(parseCstDialect, source, dialect);
26
+ [error, cst] = tryCatch(parseCst, source, {
27
+ dialect,
28
+ paramTypes: [':name'],
29
+ includeRange: true,
30
+ includeComments: true,
31
+ });
34
32
 
35
33
  if (cst)
36
34
  return convertSource(cst);
@@ -47,9 +45,11 @@ const getSectionName = (leading) => {
47
45
 
48
46
  const comment = leading.find(isComment);
49
47
 
50
- return comment ? comment.text
51
- .slice(2)
52
- .trim() : null;
48
+ return comment
49
+ ? comment.text
50
+ .slice(2)
51
+ .trim()
52
+ : null;
53
53
  };
54
54
 
55
55
  const convertSource = (cst) => {
@@ -14,14 +14,17 @@ const convertRenameTable = (action) => callExpression(identifier('renameTable'),
14
14
 
15
15
  const convertAddConstraint = ({name, constraint}) => {
16
16
  const node = convertTableConstraint(constraint);
17
+ const args = [];
17
18
 
18
- if (!name)
19
- return node;
19
+ if (name)
20
+ args.push(callExpression(identifier('constraint'), [
21
+ identifier(name.name.name),
22
+ node,
23
+ ]));
24
+ else
25
+ args.push(node);
20
26
 
21
- return callExpression(identifier('constraint'), [
22
- identifier(name.name.name),
23
- node,
24
- ]);
27
+ return callExpression(identifier('addConstraint'), args);
25
28
  };
26
29
 
27
30
  const convertDropColumn = (action) => callExpression(identifier('dropColumn'), [
@@ -31,7 +31,7 @@ export const opName = (op) => {
31
31
  return op.name;
32
32
  };
33
33
 
34
- const isEscape = (node) => node?.type === 'binary_expr' && opName(node.operator) === 'ESCAPE';
34
+ const isEscape = (node) => node && node.type === 'binary_expr' && opName(node.operator) === 'ESCAPE';
35
35
 
36
36
  const convertLike = (node) => {
37
37
  const args = [
@@ -89,13 +89,22 @@ const convertCollate = (node) => callExpression(identifier('collate'), [
89
89
  convertOperand(node.right),
90
90
  ]);
91
91
 
92
- const convertIsNull = (node) => callExpression(identifier('isNull'), [
93
- convertOperand(node.left),
94
- ]);
92
+ const isNullLiteral = ({type}) => type === 'null_literal';
95
93
 
96
- const convertIsNotNull = (node) => callExpression(identifier('isNotNull'), [
97
- convertOperand(node.left),
98
- ]);
94
+ const createIs = (nullName, name) => (node) => {
95
+ if (isNullLiteral(node.right))
96
+ return callExpression(identifier(nullName), [
97
+ convertOperand(node.left),
98
+ ]);
99
+
100
+ return callExpression(identifier(name), [
101
+ convertOperand(node.left),
102
+ convertOperand(node.right),
103
+ ]);
104
+ };
105
+
106
+ const convertIsNull = createIs('isNull', 'is');
107
+ const convertIsNotNull = createIs('isNotNull', 'isNot');
99
108
 
100
109
  const convertIn = (node) => {
101
110
  const inner = node.right.expr;
@@ -172,7 +181,7 @@ export const convertBinary = (node) => {
172
181
  if (convert)
173
182
  return convert(node);
174
183
 
175
- const jsOp = SQL_TO_JS_OP[op] ?? '===';
184
+ const jsOp = SQL_TO_JS_OP[op] || '===';
176
185
 
177
186
  return binaryExpression(
178
187
  jsOp,
@@ -31,16 +31,18 @@ const convertGenerated = (constraint) => {
31
31
  return callExpression(identifier('identity'), []);
32
32
  };
33
33
 
34
- const isGeneratedExpr = ({expr}) => expr?.type === 'paren_expr';
34
+ const isGeneratedExpr = ({expr}) => expr && expr.type === 'paren_expr';
35
+
36
+ const isStored = (constraint) => Boolean(constraint.storageKw) && constraint.storageKw.name === 'STORED';
35
37
 
36
38
  const convertGeneratedExpr = (constraint) => callExpression(identifier('generatedAlwaysAs'), [
37
39
  convertBinary(constraint.expr.expr),
38
- booleanLiteral(constraint.storageKw?.name === 'STORED'),
40
+ booleanLiteral(isStored(constraint)),
39
41
  ]);
40
42
 
41
43
  const convertDefaultNextval = (expr) => {
42
44
  const [seq] = expr.args.expr.args.items;
43
- const seqName = seq.value ?? seq.name;
45
+ const seqName = seq.value || seq.name;
44
46
 
45
47
  return callExpression(identifier('defaultNextval'), [
46
48
  identifier(seqName),
@@ -87,15 +89,26 @@ const ACTION_NAMES = {
87
89
  SET_DEFAULT: true,
88
90
  };
89
91
 
90
- const convertReferentialAction = ({eventKw, actionKw}) => {
91
- const event = eventKw.name === 'DELETE' ? 'onDelete' : 'onUpdate';
92
- const action = Array.isArray(actionKw) ? actionKw
93
- .map(({name}) => name)
94
- .join(' ') : actionKw.name;
92
+ const getActionName = (kw) => kw.name;
93
+
94
+ const normalizeAction = (a) => {
95
+ if (ACTION_NAMES[a])
96
+ return a;
95
97
 
96
- const name = ACTION_NAMES[action] ? action : action
98
+ return a
97
99
  .replace(/\s+/g, '_')
98
100
  .toUpperCase();
101
+ };
102
+
103
+ const convertReferentialAction = ({eventKw, actionKw}) => {
104
+ const event = eventKw.name === 'DELETE' ? 'onDelete' : 'onUpdate';
105
+ const action = Array.isArray(actionKw)
106
+ ? actionKw
107
+ .map(getActionName)
108
+ .join(' ')
109
+ : actionKw.name;
110
+
111
+ const name = normalizeAction(action);
99
112
 
100
113
  return callExpression(identifier(event), [
101
114
  identifier(name),
@@ -4,7 +4,7 @@ import {findClause} from '#parser/clause/find';
4
4
 
5
5
  const {identifier, callExpression} = types;
6
6
 
7
- const isDesc = (item) => item.direction?.type === 'sort_direction_desc';
7
+ const isDesc = (item) => item.direction && item.direction.type === 'sort_direction_desc';
8
8
 
9
9
  const convertColumns = (columns) => {
10
10
  const {items} = columns.expr;
@@ -12,7 +12,7 @@ const {
12
12
  const isAsClause = ({type}) => type === 'as_clause';
13
13
  const isVirtual = (stmt) => stmt.kind && stmt.kind.kindKw.name === 'VIRTUAL';
14
14
  const isColumnDefinition = ({type}) => type === 'column_definition';
15
- const isTemp = (stmt) => ['TEMP', 'TEMPORARY'].includes(stmt.kind?.kindKw.name);
15
+ const isTemp = (stmt) => stmt.kind && ['TEMP', 'TEMPORARY'].includes(stmt.kind.kindKw.name);
16
16
 
17
17
  const convertColumnListItem = (item) => {
18
18
  if (isColumnDefinition(item))
@@ -21,10 +21,15 @@ const convertColumnListItem = (item) => {
21
21
  return convertTableConstraint(item);
22
22
  };
23
23
 
24
- const isWithoutRowid = (options) => options?.items.some(({name}) => name?.some?.(({name}) => name === 'WITHOUT'));
24
+ const isWithoutName = ({name}) => name === 'WITHOUT';
25
+ const isWithoutKeyword = ({name}) => Array.isArray(name) && name.some(isWithoutName);
26
+ const isWithoutRowid = (options) => options && options.items.some(isWithoutKeyword);
25
27
 
26
28
  const convertTableArgs = (stmt) => {
27
- const colNodes = stmt.columns.expr.items.map(convertColumnListItem);
29
+ const colNodes = [];
30
+
31
+ for (const item of stmt.columns.expr.items)
32
+ colNodes.push(convertColumnListItem(item));
28
33
 
29
34
  if (isWithoutRowid(stmt.options))
30
35
  colNodes.push(callExpression(identifier('withoutRowid'), []));
@@ -54,7 +59,7 @@ export const convertCreateTable = (stmt) => {
54
59
  ]);
55
60
  }
56
61
 
57
- const asClause = stmt.clauses?.find(isAsClause);
62
+ const asClause = stmt.clauses && stmt.clauses.find(isAsClause);
58
63
 
59
64
  if (asClause)
60
65
  return callExpression(identifier('createTableAs'), [
@@ -3,7 +3,8 @@ import {convertClause} from '#parser/clause';
3
3
 
4
4
  const {identifier, callExpression} = types;
5
5
 
6
- const isMaterialized = (stmt) => stmt.kinds.some(({kindKw}) => kindKw.name === 'MATERIALIZED');
6
+ const isMaterializedKind = ({kindKw}) => kindKw.name === 'MATERIALIZED';
7
+ const isMaterialized = (stmt) => stmt.kinds.some(isMaterializedKind);
7
8
 
8
9
  export const convertCreateView = (stmt) => {
9
10
  const [asClause] = stmt.clauses;
@@ -1,7 +1,9 @@
1
1
  import {types} from '@putout/babel';
2
- import {convertBetween} from '#parser/clause/where';
2
+ import {
3
+ convertCondition,
4
+ convertWhere,
5
+ } from '#parser/clause/where';
3
6
  import {convertClause} from '#parser/clause';
4
- import {convertBinary} from '#parser/clause/binary';
5
7
  import {
6
8
  convertOperand,
7
9
  convertMemberExpr,
@@ -20,59 +22,13 @@ import {
20
22
  const {
21
23
  identifier,
22
24
  callExpression,
23
- logicalExpression,
24
25
  stringLiteral,
25
26
  tsAsExpression,
26
27
  tsLiteralType,
27
28
  } = types;
28
29
 
29
- const isExists = (node) => node.type === 'prefix_op_expr' && node.operator.name === 'EXISTS';
30
- const isNotExists = (node) => node.type === 'prefix_op_expr' && node.operator.name === 'NOT' && isExists(node.expr);
31
30
  const isParenExpr = (node) => node.type === 'paren_expr';
32
31
 
33
- const POSTFIX_OP = {
34
- ISNULL: 'isNull',
35
- NOTNULL: 'isNotNull',
36
- };
37
-
38
- const convertPostfix = (node) => {
39
- const {name} = node.operator;
40
- const fn = POSTFIX_OP[name];
41
-
42
- return callExpression(identifier(fn), [
43
- convertOperand(node.expr),
44
- ]);
45
- };
46
-
47
- const convertCondition = (node) => {
48
- if (node.operator && node.operator.name === 'AND')
49
- return logicalExpression('&&', convertCondition(node.left), convertCondition(node.right));
50
-
51
- if (node.operator && node.operator.name === 'OR')
52
- return logicalExpression('||', convertCondition(node.left), convertCondition(node.right));
53
-
54
- if (node.type === 'between_expr')
55
- return convertBetween(node);
56
-
57
- if (node.type === 'postfix_op_expr')
58
- return convertPostfix(node);
59
-
60
- if (isNotExists(node))
61
- return callExpression(identifier('notExists'), [
62
- convertClause(node.expr.expr.expr),
63
- ]);
64
-
65
- if (isExists(node))
66
- return callExpression(identifier('exists'), [
67
- convertClause(node.expr.expr),
68
- ]);
69
-
70
- if (isParenExpr(node))
71
- return convertCondition(node.expr);
72
-
73
- return convertBinary(node);
74
- };
75
-
76
32
  const convertOn = (spec) => callExpression(identifier('on'), [
77
33
  convertCondition(spec.expr),
78
34
  ]);
@@ -145,6 +101,11 @@ const joinTypeParts = (operator) => {
145
101
  return parts;
146
102
  };
147
103
 
104
+ const capitalizeWord = (s) => s[0].toUpperCase() +
105
+ s
106
+ .slice(1)
107
+ .toLowerCase();
108
+
148
109
  const convertJoinType = (operator) => {
149
110
  const parts = joinTypeParts(operator);
150
111
 
@@ -155,14 +116,12 @@ const convertJoinType = (operator) => {
155
116
  return 'join';
156
117
 
157
118
  // 'LEFT OUTER' → 'leftOuter' → 'leftOuterJoin'
158
- const camel = parts[0].toLowerCase() +
159
- parts
160
- .slice(1)
161
- .map((s) => s[0].toUpperCase() +
162
- s
163
- .slice(1)
164
- .toLowerCase())
165
- .join('');
119
+ const tail = [];
120
+
121
+ for (const part of parts.slice(1))
122
+ tail.push(capitalizeWord(part));
123
+
124
+ const camel = parts[0].toLowerCase() + tail.join('');
166
125
 
167
126
  return `${camel}Join`;
168
127
  };
@@ -238,9 +197,7 @@ export const convertFrom = ({expr, whereClause, groupByClause, havingClause, ord
238
197
  const fromArgs = [];
239
198
 
240
199
  if (whereClause)
241
- fromArgs.push(callExpression(identifier('where'), [
242
- convertCondition(whereClause.expr),
243
- ]));
200
+ fromArgs.push(convertWhere(whereClause));
244
201
 
245
202
  if (groupByClause)
246
203
  fromArgs.push(convertGroupBy(groupByClause));
@@ -268,7 +225,11 @@ export const convertFrom = ({expr, whereClause, groupByClause, havingClause, ord
268
225
 
269
226
  if (expr.type === 'join_expr') {
270
227
  const parts = flattenJoins(expr);
271
- return callExpression(identifier('from'), parts);
228
+
229
+ return callExpression(identifier('from'), [
230
+ ...parts,
231
+ ...fromArgs,
232
+ ]);
272
233
  }
273
234
 
274
235
  if (expr.type === 'alias')
@@ -3,4 +3,11 @@ import {convertOperand} from '#parser/clause/operand';
3
3
 
4
4
  const {callExpression, identifier} = types;
5
5
 
6
- export const convertGroupBy = (clause) => callExpression(identifier('groupBy'), clause.columns.items.map(convertOperand));
6
+ export const convertGroupBy = (clause) => {
7
+ const args = [];
8
+
9
+ for (const item of clause.columns.items)
10
+ args.push(convertOperand(item));
11
+
12
+ return callExpression(identifier('groupBy'), args);
13
+ };
@@ -7,7 +7,7 @@ const {
7
7
  } = types;
8
8
 
9
9
  export const convertLimit = (limitClause) => {
10
- if (limitClause.count?.type === 'limit_all')
10
+ if (limitClause.count && limitClause.count.type === 'limit_all')
11
11
  return callExpression(identifier('limitAll'), []);
12
12
 
13
13
  const args = [
@@ -7,8 +7,9 @@ const isDesc = ({type}) => type === 'sort_direction_desc';
7
7
 
8
8
  const convertNullsOrder = (col, nullHandlingKw) => {
9
9
  const isLast = nullHandlingKw.at(-1).name === 'LAST';
10
+ const fnName = isLast ? 'nullsLast' : 'nullsFirst';
10
11
 
11
- return callExpression(identifier(isLast ? 'nullsLast' : 'nullsFirst'), [col]);
12
+ return callExpression(identifier(fnName), [col]);
12
13
  };
13
14
 
14
15
  const convertOrderItem = (item) => {
@@ -6,9 +6,18 @@ const {callExpression, identifier} = types;
6
6
  const SNAKE_RE = /_([a-z])/g;
7
7
  const toCamel = (s) => s.replace(SNAKE_RE, (_, c) => c.toUpperCase());
8
8
 
9
+ const getRawName = (expr) => {
10
+ const raw = expr.name.name;
11
+
12
+ if (raw.includes('_'))
13
+ return raw.toLowerCase();
14
+
15
+ return raw;
16
+ };
17
+
9
18
  export const convertTableFunc = (expr) => {
10
19
  // multi-word snake_case names are lowercased first (GENERATE_SERIES → generateSeries)
11
- const name = toCamel(expr.name.name.includes('_') ? expr.name.name.toLowerCase() : expr.name.name);
20
+ const name = toCamel(getRawName(expr));
12
21
  const args = expr.args.expr.args.items.map(convertOperand);
13
22
 
14
23
  return callExpression(identifier(name), args);
@@ -75,6 +75,8 @@ const convertSelectOrNamed = (stmt) => {
75
75
  return convertSelect(stmt);
76
76
  };
77
77
 
78
+ const getAnalyzeTables = ({tables}) => tables.items || tables;
79
+
78
80
  const clauseVisitors = {
79
81
  select_stmt: convertSelectOrNamed,
80
82
  insert_stmt: convertInsertOrNamed,
@@ -96,7 +98,14 @@ const clauseVisitors = {
96
98
  vacuum_stmt: ({file}) => callExpression(identifier('vacuum'), file ? [
97
99
  stringLiteral(file.value),
98
100
  ] : []),
99
- truncate_stmt: ({tables}) => callExpression(identifier('truncate'), tables.items.map(({name}) => identifier(name))),
101
+ truncate_stmt: ({tables}) => {
102
+ const args = [];
103
+
104
+ for (const table of tables.items)
105
+ args.push(convertTableName(table));
106
+
107
+ return callExpression(identifier('truncate'), args);
108
+ },
100
109
  detach_database_stmt: ({schema}) => callExpression(identifier('detach'), [
101
110
  identifier(schema.name),
102
111
  ]),
@@ -111,7 +120,14 @@ const clauseVisitors = {
111
120
  reindex_stmt: (stmt) => callExpression(identifier('reindex'), stmt.name ? [
112
121
  identifier(stmt.name.name),
113
122
  ] : []),
114
- analyze_stmt: (stmt) => callExpression(identifier('analyze'), (stmt.tables?.items ?? []).map(convertTableName)),
123
+ analyze_stmt: (stmt) => {
124
+ const args = [];
125
+
126
+ for (const table of getAnalyzeTables(stmt))
127
+ args.push(convertTableName(table));
128
+
129
+ return callExpression(identifier('analyze'), args);
130
+ },
115
131
  release_savepoint_stmt: ({savepoint}) => callExpression(identifier('releaseSavepoint'), [
116
132
  identifier(savepoint.name),
117
133
  ]),
@@ -53,12 +53,31 @@ const convertSource = (vc) => {
53
53
  return callExpression(identifier('values'), rowArgs);
54
54
  };
55
55
 
56
+ const getTargetArgs = (target) => {
57
+ if (!target)
58
+ return [];
59
+
60
+ const args = [];
61
+
62
+ for (const item of target.expr.items)
63
+ args.push(convertExprToIdentifier(item));
64
+
65
+ return args;
66
+ };
67
+
68
+ const convertUpsertAction = (action) => {
69
+ if (action.type === 'upsert_action_nothing')
70
+ return identifier('nothing');
71
+
72
+ return convertSet(action.set);
73
+ };
74
+
56
75
  const convertUpsert = (upsertClause) => {
57
76
  const target = upsertClause.conflictTarget;
58
77
  const {action} = upsertClause;
59
78
 
60
- const targetArgs = target ? target.expr.items.map(convertExprToIdentifier) : [];
61
- const actionNode = action.type === 'upsert_action_nothing' ? identifier('nothing') : convertSet(action.set);
79
+ const targetArgs = getTargetArgs(target);
80
+ const actionNode = convertUpsertAction(action);
62
81
 
63
82
  if (upsertClause.where)
64
83
  targetArgs.push(callExpression(identifier('where'), [
@@ -68,14 +87,26 @@ const convertUpsert = (upsertClause) => {
68
87
  return callExpression(identifier('onConflict'), [...targetArgs, actionNode]);
69
88
  };
70
89
 
71
- const isReplaceInto = ({insertKw, orAction}) => !orAction && insertKw?.name === 'REPLACE';
90
+ const isReplaceInto = ({insertKw, orAction}) => !orAction && insertKw && insertKw.name === 'REPLACE';
91
+
92
+ const colsToArg = (cols) => {
93
+ if (cols.length === 1)
94
+ return identifier(cols[0].name);
95
+
96
+ const args = [];
97
+
98
+ for (const col of cols)
99
+ args.push(convertColToIdentifier(col));
100
+
101
+ return arrayExpression(args);
102
+ };
72
103
 
73
104
  const convertReplaceInto = ({clauses}) => {
74
105
  const ic = findClause(clauses, 'insert_clause');
75
106
  const rc = findClause(clauses, 'returning_clause');
76
107
 
77
108
  const cols = ic.columns.expr.items;
78
- const colArg = cols.length === 1 ? identifier(cols[0].name) : arrayExpression(cols.map(convertColToIdentifier));
109
+ const colArg = colsToArg(cols);
79
110
  const source = convertSource(findClause(clauses, 'values_clause'));
80
111
 
81
112
  return callExpression(identifier('replaceInto'), [
@@ -88,6 +119,13 @@ const convertReplaceInto = ({clauses}) => {
88
119
  ].filter(Boolean));
89
120
  };
90
121
 
122
+ const getSource = (dc, vc) => {
123
+ if (dc)
124
+ return callExpression(identifier('defaultValues'), []);
125
+
126
+ return convertSource(vc);
127
+ };
128
+
91
129
  export const convertInsert = (stmt) => {
92
130
  if (isReplaceInto(stmt.clauses[0]))
93
131
  return convertReplaceInto(stmt);
@@ -100,7 +138,7 @@ export const convertInsert = (stmt) => {
100
138
  const dc = findClause(clauses, 'default_values');
101
139
 
102
140
  const colArgs = convertColumns(ic);
103
- const source = dc ? callExpression(identifier('defaultValues'), []) : convertSource(vc);
141
+ const source = getSource(dc, vc);
104
142
 
105
143
  const args = [
106
144
  ic.orAction && convertOrAction(ic.orAction),
@@ -134,7 +172,7 @@ const convertColumns = (ic) => {
134
172
  return [];
135
173
 
136
174
  const cols = columns.expr.items;
137
- const colArg = cols.length === 1 ? identifier(cols[0].name) : arrayExpression(cols.map(convertColToIdentifier));
175
+ const colArg = colsToArg(cols);
138
176
 
139
177
  return [
140
178
  colArg,
@@ -68,7 +68,7 @@ const convertQuantifier = (node) => {
68
68
 
69
69
  const convertPrefix = (node) => {
70
70
  const {operator} = node;
71
- const name = operator?.name ?? operator;
71
+ const name = operator.name || operator;
72
72
 
73
73
  if (name === 'EXISTS')
74
74
  return callExpression(identifier('exists'), [
@@ -131,6 +131,16 @@ export const convertOperand = (node) => {
131
131
  convertClause(node.expr),
132
132
  ]);
133
133
 
134
+ if (node.type === 'paren_expr') {
135
+ const inner = convertOperand(node.expr);
136
+
137
+ inner.extra = {
138
+ parenthesized: true,
139
+ };
140
+
141
+ return inner;
142
+ }
143
+
134
144
  if (node.type === 'parameter')
135
145
  return stringLiteral(node.text);
136
146
 
@@ -10,6 +10,7 @@ import {convertBinary} from '#parser/clause/binary';
10
10
  import {
11
11
  convertCondition,
12
12
  convertBetween,
13
+ convertWhere,
13
14
  } from '#parser/clause/where';
14
15
  import {findClause} from '#parser/clause/find';
15
16
  import {convertOrderBy} from '../from/order-by.js';
@@ -145,10 +146,12 @@ const convertFrameBound = (bound) => {
145
146
 
146
147
  const name = bound.type === 'frame_bound_preceding' ? 'preceding' : 'following';
147
148
 
148
- const isUnbounded = bound.expr?.type === 'frame_unbounded';
149
- const args = !isUnbounded && bound.expr ? [
150
- numericLiteral(bound.expr.value),
151
- ] : [];
149
+ const isUnbounded = bound.expr && bound.expr.type === 'frame_unbounded';
150
+ const args = !isUnbounded && bound.expr
151
+ ? [
152
+ numericLiteral(bound.expr.value),
153
+ ]
154
+ : [];
152
155
 
153
156
  return callExpression(identifier(name), args);
154
157
  };
@@ -215,8 +218,10 @@ const convertFuncCall = (column) => {
215
218
  for (const item of items)
216
219
  args.push(convertOperand(item));
217
220
 
218
- if (column.over)
219
- return convertOver(column, callExpression(identifier(LOWERCASE_FUNCS.has(lower) ? lower : name), args));
221
+ if (column.over) {
222
+ const funcName = LOWERCASE_FUNCS.has(lower) ? lower : name;
223
+ return convertOver(column, callExpression(identifier(funcName), args));
224
+ }
220
225
 
221
226
  if (LOWERCASE_FUNCS.has(lower))
222
227
  return callExpression(identifier(lower), args);
@@ -234,13 +239,16 @@ const convertColumns = (columns) => {
234
239
  return columns.items.map(convertColumn);
235
240
  };
236
241
 
237
- const isDistinct = (selectClause) => selectClause.modifiers.some(({type}) => type === 'select_distinct');
242
+ const isSelectDistinct = ({type}) => type === 'select_distinct';
243
+ const isSelectDistinctOn = ({type}) => type === 'select_distinct_on';
244
+
245
+ const isDistinct = (selectClause) => selectClause.modifiers.some(isSelectDistinct);
238
246
 
239
247
  const convertDistinctOn = ({modifiers}) => {
240
- if (!modifiers.some(({type}) => type === 'select_distinct_on'))
248
+ if (!modifiers.some(isSelectDistinctOn))
241
249
  return null;
242
250
 
243
- const modifier = modifiers.find(({type}) => type === 'select_distinct_on');
251
+ const modifier = modifiers.find(isSelectDistinctOn);
244
252
  const onCols = [];
245
253
 
246
254
  for (const item of modifier.columns.expr.items) {
@@ -305,6 +313,9 @@ export const convertSelect = ({clauses}) => {
305
313
  ].filter(Boolean);
306
314
 
307
315
  if (!fromClause) {
316
+ if (whereClause)
317
+ args.push(convertWhere(whereClause));
318
+
308
319
  if (limitClause)
309
320
  args.push(convertLimit(limitClause));
310
321