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.
- package/ChangeLog +34 -0
- package/lib/parser/parser.js +11 -11
- package/lib/parser/visitors/clause/alter-table/alter-table.js +9 -6
- package/lib/parser/visitors/clause/binary.js +17 -8
- package/lib/parser/visitors/clause/column-definition/column-definition.js +22 -9
- package/lib/parser/visitors/clause/create-index/create-index.js +1 -1
- package/lib/parser/visitors/clause/create-table/create-table.js +9 -4
- package/lib/parser/visitors/clause/create-view/create-view.js +2 -1
- package/lib/parser/visitors/clause/from/from.js +21 -60
- package/lib/parser/visitors/clause/from/group-by.js +8 -1
- package/lib/parser/visitors/clause/from/limit.js +1 -1
- package/lib/parser/visitors/clause/from/order-by.js +2 -1
- package/lib/parser/visitors/clause/from/table-func.js +10 -1
- package/lib/parser/visitors/clause/index.js +18 -2
- package/lib/parser/visitors/clause/insert/insert.js +44 -6
- package/lib/parser/visitors/clause/operand.js +11 -1
- package/lib/parser/visitors/clause/select/select.js +20 -9
- package/lib/parser/visitors/clause/where/where.js +30 -3
- package/lib/parser/visitors/clause/with-named/with-named.js +15 -1
- package/lib/printer/visitors/binary-expression.js +28 -5
- package/lib/printer/visitors/call-expression/clauses/add-constraint/add-constraint.js +4 -0
- package/lib/printer/visitors/call-expression/clauses/clauses.js +10 -1
- package/lib/printer/visitors/call-expression/clauses/create-index/create-index.js +10 -1
- package/lib/printer/visitors/call-expression/clauses/from/from.js +64 -47
- package/lib/printer/visitors/call-expression/clauses/into/into.js +5 -2
- package/lib/printer/visitors/call-expression/clauses/is-null/is-null.js +14 -0
- package/lib/printer/visitors/call-expression/clauses/json-op.js +8 -1
- package/lib/printer/visitors/call-expression/clauses/lateral-join/lateral-join.js +7 -11
- package/lib/printer/visitors/call-expression/clauses/not/not.js +13 -1
- package/lib/printer/visitors/call-expression/clauses/on-conflict/on-conflict.js +9 -2
- package/lib/printer/visitors/call-expression/clauses/references/references.js +26 -14
- package/lib/printer/visitors/call-expression/clauses/section/section.js +5 -1
- package/lib/printer/visitors/call-expression/clauses/select/select.js +7 -6
- package/lib/printer/visitors/call-expression/clauses/update/update.js +7 -11
- package/lib/printer/visitors/call-expression/clauses/window-frame/window-frame.js +2 -2
- package/lib/printer/visitors/logical-expression.js +10 -2
- package/lib/printer/visitors/string-literal.js +15 -1
- 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:
|
package/lib/parser/parser.js
CHANGED
|
@@ -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(
|
|
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
|
|
51
|
-
.
|
|
52
|
-
|
|
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 (
|
|
19
|
-
|
|
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('
|
|
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
|
|
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
|
|
93
|
-
convertOperand(node.left),
|
|
94
|
-
]);
|
|
92
|
+
const isNullLiteral = ({type}) => type === 'null_literal';
|
|
95
93
|
|
|
96
|
-
const
|
|
97
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const getActionName = (kw) => kw.name;
|
|
93
|
+
|
|
94
|
+
const normalizeAction = (a) => {
|
|
95
|
+
if (ACTION_NAMES[a])
|
|
96
|
+
return a;
|
|
95
97
|
|
|
96
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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(
|
|
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
|
-
|
|
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) =>
|
|
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,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(
|
|
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(
|
|
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}) =>
|
|
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) =>
|
|
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
|
|
61
|
-
const actionNode =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
149
|
-
const args = !isUnbounded && bound.expr
|
|
150
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
248
|
+
if (!modifiers.some(isSelectDistinctOn))
|
|
241
249
|
return null;
|
|
242
250
|
|
|
243
|
-
const modifier = modifiers.find(
|
|
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
|
|