happy-sql 2.4.0 → 2.4.1
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 +28 -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 +2 -2
- 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 +16 -9
- 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 +1 -1
- package/lib/parser/visitors/clause/select/select.js +16 -9
- package/lib/parser/visitors/clause/with-named/with-named.js +15 -1
- package/lib/printer/visitors/call-expression/clauses/add-constraint/add-constraint.js +4 -0
- package/lib/printer/visitors/call-expression/clauses/clauses.js +2 -0
- package/lib/printer/visitors/call-expression/clauses/from/from.js +16 -3
- package/lib/printer/visitors/call-expression/clauses/into/into.js +5 -2
- package/lib/printer/visitors/call-expression/clauses/json-op.js +8 -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/window-frame/window-frame.js +2 -2
- package/lib/printer/visitors/logical-expression.js +4 -1
- package/package.json +3 -3
package/ChangeLog
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
2026.11.14, v2.4.1
|
|
2
|
+
|
|
3
|
+
fix:
|
|
4
|
+
- 10d5818 happy-sql: join
|
|
5
|
+
- 7510903 alter-table: rename table, add constraint, RLS actions
|
|
6
|
+
- a4e14a1 references: ON DELETE and ON UPDATE actions
|
|
7
|
+
- 60cbd74 from: extract capitalizeWord, remove inlined arrow
|
|
8
|
+
- 3ebbea9 with-named: extract convertColumn, remove inlined arrow
|
|
9
|
+
- 8848396 printer/from: remove inlined arrows and ternary
|
|
10
|
+
- 0cc326c window-frame: remove optional chaining
|
|
11
|
+
- 652387a references: remove optional chaining
|
|
12
|
+
- 344f7c3 clause/index: remove optional chaining, nullish coalescing, inlined arrows
|
|
13
|
+
- 313fa5f binary: remove optional chaining and nullish coalescing
|
|
14
|
+
- 12b6f3c insert: remove optional chaining and inlined arrows/ternaries
|
|
15
|
+
- 652bcad create-index: remove optional chaining
|
|
16
|
+
- b5b40ac limit: remove optional chaining
|
|
17
|
+
- 5eb1732 create-table: remove optional chaining and inlined arrows
|
|
18
|
+
- f078532 create-view: extract isMaterializedKind predicate
|
|
19
|
+
- 472e627 order-by: extract nulls order fn name
|
|
20
|
+
- ddcb389 group-by: remove inlined arrow in map
|
|
21
|
+
- 1be75b4 table-func: extract getRawName, remove inlined ternary
|
|
22
|
+
- 10a0b35 column-definition: remove optional chaining, nullish coalescing, inlined arrows
|
|
23
|
+
- 9108d3e operand: remove optional chaining and nullish coalescing
|
|
24
|
+
|
|
25
|
+
feature:
|
|
26
|
+
- ffaef07 happy-sql: sql-parser-cst v0.44.1
|
|
27
|
+
- 981cf13 happy-sql: @putout/plugin-printer v9.0.0
|
|
28
|
+
|
|
1
29
|
2026.08.27, v2.4.0
|
|
2
30
|
|
|
3
31
|
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 = [
|
|
@@ -172,7 +172,7 @@ export const convertBinary = (node) => {
|
|
|
172
172
|
if (convert)
|
|
173
173
|
return convert(node);
|
|
174
174
|
|
|
175
|
-
const jsOp = SQL_TO_JS_OP[op]
|
|
175
|
+
const jsOp = SQL_TO_JS_OP[op] || '===';
|
|
176
176
|
|
|
177
177
|
return binaryExpression(
|
|
178
178
|
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;
|
|
@@ -145,6 +145,11 @@ const joinTypeParts = (operator) => {
|
|
|
145
145
|
return parts;
|
|
146
146
|
};
|
|
147
147
|
|
|
148
|
+
const capitalizeWord = (s) => s[0].toUpperCase() +
|
|
149
|
+
s
|
|
150
|
+
.slice(1)
|
|
151
|
+
.toLowerCase();
|
|
152
|
+
|
|
148
153
|
const convertJoinType = (operator) => {
|
|
149
154
|
const parts = joinTypeParts(operator);
|
|
150
155
|
|
|
@@ -155,14 +160,12 @@ const convertJoinType = (operator) => {
|
|
|
155
160
|
return 'join';
|
|
156
161
|
|
|
157
162
|
// 'LEFT OUTER' → 'leftOuter' → 'leftOuterJoin'
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.toLowerCase())
|
|
165
|
-
.join('');
|
|
163
|
+
const tail = [];
|
|
164
|
+
|
|
165
|
+
for (const part of parts.slice(1))
|
|
166
|
+
tail.push(capitalizeWord(part));
|
|
167
|
+
|
|
168
|
+
const camel = parts[0].toLowerCase() + tail.join('');
|
|
166
169
|
|
|
167
170
|
return `${camel}Join`;
|
|
168
171
|
};
|
|
@@ -268,7 +271,11 @@ export const convertFrom = ({expr, whereClause, groupByClause, havingClause, ord
|
|
|
268
271
|
|
|
269
272
|
if (expr.type === 'join_expr') {
|
|
270
273
|
const parts = flattenJoins(expr);
|
|
271
|
-
|
|
274
|
+
|
|
275
|
+
return callExpression(identifier('from'), [
|
|
276
|
+
...parts,
|
|
277
|
+
...fromArgs,
|
|
278
|
+
]);
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
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'), [
|
|
@@ -145,10 +145,12 @@ const convertFrameBound = (bound) => {
|
|
|
145
145
|
|
|
146
146
|
const name = bound.type === 'frame_bound_preceding' ? 'preceding' : 'following';
|
|
147
147
|
|
|
148
|
-
const isUnbounded = bound.expr
|
|
149
|
-
const args = !isUnbounded && bound.expr
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
const isUnbounded = bound.expr && bound.expr.type === 'frame_unbounded';
|
|
149
|
+
const args = !isUnbounded && bound.expr
|
|
150
|
+
? [
|
|
151
|
+
numericLiteral(bound.expr.value),
|
|
152
|
+
]
|
|
153
|
+
: [];
|
|
152
154
|
|
|
153
155
|
return callExpression(identifier(name), args);
|
|
154
156
|
};
|
|
@@ -215,8 +217,10 @@ const convertFuncCall = (column) => {
|
|
|
215
217
|
for (const item of items)
|
|
216
218
|
args.push(convertOperand(item));
|
|
217
219
|
|
|
218
|
-
if (column.over)
|
|
219
|
-
|
|
220
|
+
if (column.over) {
|
|
221
|
+
const funcName = LOWERCASE_FUNCS.has(lower) ? lower : name;
|
|
222
|
+
return convertOver(column, callExpression(identifier(funcName), args));
|
|
223
|
+
}
|
|
220
224
|
|
|
221
225
|
if (LOWERCASE_FUNCS.has(lower))
|
|
222
226
|
return callExpression(identifier(lower), args);
|
|
@@ -234,13 +238,16 @@ const convertColumns = (columns) => {
|
|
|
234
238
|
return columns.items.map(convertColumn);
|
|
235
239
|
};
|
|
236
240
|
|
|
237
|
-
const
|
|
241
|
+
const isSelectDistinct = ({type}) => type === 'select_distinct';
|
|
242
|
+
const isSelectDistinctOn = ({type}) => type === 'select_distinct_on';
|
|
243
|
+
|
|
244
|
+
const isDistinct = (selectClause) => selectClause.modifiers.some(isSelectDistinct);
|
|
238
245
|
|
|
239
246
|
const convertDistinctOn = ({modifiers}) => {
|
|
240
|
-
if (!modifiers.some(
|
|
247
|
+
if (!modifiers.some(isSelectDistinctOn))
|
|
241
248
|
return null;
|
|
242
249
|
|
|
243
|
-
const modifier = modifiers.find(
|
|
250
|
+
const modifier = modifiers.find(isSelectDistinctOn);
|
|
244
251
|
const onCols = [];
|
|
245
252
|
|
|
246
253
|
for (const item of modifier.columns.expr.items) {
|
|
@@ -15,9 +15,23 @@ const convertTable = ({table, expr}) => {
|
|
|
15
15
|
return assignmentExpression('=', identifier(table.name), convertClause(expr.expr));
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const convertColumn = (column) => stringLiteral(column.name);
|
|
19
|
+
|
|
20
|
+
const getColArgs = (columns) => {
|
|
21
|
+
if (!columns)
|
|
22
|
+
return [];
|
|
23
|
+
|
|
24
|
+
const args = [];
|
|
25
|
+
|
|
26
|
+
for (const col of columns.expr.items)
|
|
27
|
+
args.push(convertColumn(col));
|
|
28
|
+
|
|
29
|
+
return args;
|
|
30
|
+
};
|
|
31
|
+
|
|
18
32
|
// recursive CTE: numbers('value', unionAll(...))
|
|
19
33
|
const convertRecursiveTable = ({table, columns, expr}) => {
|
|
20
|
-
const colArgs = columns
|
|
34
|
+
const colArgs = getColArgs(columns);
|
|
21
35
|
const body = convertClause(expr.expr);
|
|
22
36
|
|
|
23
37
|
return callExpression(identifier(table.name), [...colArgs, body]);
|
|
@@ -108,6 +108,7 @@ import {withoutRowid} from './without-rowid/without-rowid.js';
|
|
|
108
108
|
import {createTableAs} from './create-table-as/create-table-as.js';
|
|
109
109
|
import {alterTable} from './alter-table/alter-table.js';
|
|
110
110
|
import {addColumn} from './add-column/add-column.js';
|
|
111
|
+
import {addConstraint} from './add-constraint/add-constraint.js';
|
|
111
112
|
import {dropColumn} from './drop-column/drop-column.js';
|
|
112
113
|
import {renameColumn} from './rename-column/rename-column.js';
|
|
113
114
|
import {alterColumnType} from './alter-column-type/alter-column-type.js';
|
|
@@ -278,6 +279,7 @@ export const clauses = {
|
|
|
278
279
|
createTableAs,
|
|
279
280
|
alterTable,
|
|
280
281
|
addColumn,
|
|
282
|
+
addConstraint,
|
|
281
283
|
dropColumn,
|
|
282
284
|
renameColumn,
|
|
283
285
|
alterColumnType,
|
|
@@ -68,7 +68,13 @@ const toSnake = (a) => a.replace(CAMEL_RE, (_, c) => `_${c.toLowerCase()}`);
|
|
|
68
68
|
|
|
69
69
|
// single-word ALL-CAPS functions (UNNEST) keep their casing
|
|
70
70
|
const ALL_CAPS_RE = /^[A-Z]+$/;
|
|
71
|
-
|
|
71
|
+
|
|
72
|
+
const toFuncName = (name) => {
|
|
73
|
+
if (ALL_CAPS_RE.test(name))
|
|
74
|
+
return name;
|
|
75
|
+
|
|
76
|
+
return toSnake(name);
|
|
77
|
+
};
|
|
72
78
|
|
|
73
79
|
const isTableFunc = (node) => isCallExpression(node) && !TABLE_ARG_NAMES.includes(node.callee.name);
|
|
74
80
|
|
|
@@ -104,8 +110,15 @@ const printArg = (path, printer) => {
|
|
|
104
110
|
|
|
105
111
|
export const from = (path, {write, traverse}) => {
|
|
106
112
|
const args = path.get('arguments');
|
|
107
|
-
const tableArgs =
|
|
108
|
-
const clauseArgs =
|
|
113
|
+
const tableArgs = [];
|
|
114
|
+
const clauseArgs = [];
|
|
115
|
+
|
|
116
|
+
for (const arg of args) {
|
|
117
|
+
if (isClause(arg))
|
|
118
|
+
clauseArgs.push(arg);
|
|
119
|
+
else
|
|
120
|
+
tableArgs.push(arg);
|
|
121
|
+
}
|
|
109
122
|
|
|
110
123
|
write('FROM ');
|
|
111
124
|
printArg(tableArgs[0], {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {types} from '@putout/babel';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
isTSAsExpression,
|
|
5
|
+
isArrayExpression,
|
|
6
|
+
} = types;
|
|
4
7
|
|
|
5
8
|
const printTable = (table, {write, traverse}) => {
|
|
6
9
|
if (isTSAsExpression(table.node)) {
|
|
@@ -36,7 +39,7 @@ export const into = (path, {write, traverse}) => {
|
|
|
36
39
|
|
|
37
40
|
write(' (');
|
|
38
41
|
|
|
39
|
-
if (columns.node
|
|
42
|
+
if (isArrayExpression(columns.node)) {
|
|
40
43
|
const colArgs = columns.get('elements');
|
|
41
44
|
|
|
42
45
|
for (let i = 0; i < colArgs.length; i++) {
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
isStringLiteral,
|
|
5
|
+
isArrayExpression,
|
|
6
|
+
} = types;
|
|
7
|
+
|
|
1
8
|
const JSON_OPS = new Set([
|
|
2
9
|
'->',
|
|
3
10
|
'->>',
|
|
@@ -11,7 +18,7 @@ const JSON_OPS = new Set([
|
|
|
11
18
|
'<@',
|
|
12
19
|
]);
|
|
13
20
|
|
|
14
|
-
export const isJsonTuple = (node) => node
|
|
21
|
+
export const isJsonTuple = (node) => isArrayExpression(node) && node.elements.length === 3 && isStringLiteral(node.elements[1]) && JSON_OPS.has(node.elements[1].value);
|
|
15
22
|
|
|
16
23
|
export const printJsonTuple = (path, {write, traverse}) => {
|
|
17
24
|
const elems = path.get('elements');
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
isIdentifier,
|
|
5
|
+
isCallExpression,
|
|
6
|
+
} = types;
|
|
7
|
+
|
|
8
|
+
const isWhereArg = ({node}) => isCallExpression(node) && node.callee.name === 'where';
|
|
2
9
|
|
|
3
10
|
export const onConflict = (path, {write, traverse}) => {
|
|
4
11
|
const args = path.get('arguments');
|
|
@@ -34,7 +41,7 @@ export const onConflict = (path, {write, traverse}) => {
|
|
|
34
41
|
|
|
35
42
|
write(' DO ');
|
|
36
43
|
|
|
37
|
-
if (action.node
|
|
44
|
+
if (isIdentifier(action.node) && action.node.name === 'nothing') {
|
|
38
45
|
write('NOTHING');
|
|
39
46
|
return;
|
|
40
47
|
}
|
|
@@ -1,27 +1,39 @@
|
|
|
1
|
+
const getCalleeName = (node) => {
|
|
2
|
+
if (!node || !node.callee)
|
|
3
|
+
return null;
|
|
4
|
+
|
|
5
|
+
return node.callee.name;
|
|
6
|
+
};
|
|
7
|
+
|
|
1
8
|
export const references = (path, {write, traverse}) => {
|
|
2
9
|
const [first, ...other] = path.get('arguments');
|
|
10
|
+
const cols = [];
|
|
11
|
+
const actions = [];
|
|
12
|
+
|
|
13
|
+
for (const arg of other) {
|
|
14
|
+
const name = getCalleeName(arg.node);
|
|
15
|
+
|
|
16
|
+
if (name === 'onDelete' || name === 'onUpdate')
|
|
17
|
+
actions.push(arg);
|
|
18
|
+
else
|
|
19
|
+
cols.push(arg);
|
|
20
|
+
}
|
|
3
21
|
|
|
4
22
|
write('REFERENCES ');
|
|
5
23
|
traverse(first);
|
|
6
24
|
write('(');
|
|
7
25
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
for (const arg of other) {
|
|
11
|
-
const name = arg.node?.callee?.name;
|
|
12
|
-
|
|
13
|
-
if (name === 'onDelete' || name === 'onUpdate') {
|
|
14
|
-
write(' ');
|
|
15
|
-
traverse(arg);
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (wroteCol)
|
|
26
|
+
for (const [i, col] of cols.entries()) {
|
|
27
|
+
if (i > 0)
|
|
20
28
|
write(', ');
|
|
21
29
|
|
|
22
|
-
traverse(
|
|
23
|
-
wroteCol = true;
|
|
30
|
+
traverse(col);
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
write(')');
|
|
34
|
+
|
|
35
|
+
for (const action of actions) {
|
|
36
|
+
write(' ');
|
|
37
|
+
traverse(action);
|
|
38
|
+
}
|
|
27
39
|
};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
|
|
3
|
+
const {isArrayExpression} = types;
|
|
4
|
+
|
|
1
5
|
export const section = (path, {write, traverse}) => {
|
|
2
6
|
const [name, stmt] = path.get('arguments');
|
|
3
7
|
write(`-- ${name.node.value}\n`);
|
|
4
8
|
|
|
5
|
-
if (stmt.node
|
|
9
|
+
if (isArrayExpression(stmt.node)) {
|
|
6
10
|
const elements = stmt.get('elements');
|
|
7
11
|
const lastIndex = elements.length - 1;
|
|
8
12
|
|
|
@@ -10,7 +10,7 @@ export const rowsBetween = (path, {write, traverse}) => {
|
|
|
10
10
|
export const preceding = (path, {write, traverse}) => {
|
|
11
11
|
const [expr] = path.get('arguments');
|
|
12
12
|
|
|
13
|
-
if (expr
|
|
13
|
+
if (expr && expr.node) {
|
|
14
14
|
traverse(expr);
|
|
15
15
|
write(' PRECEDING');
|
|
16
16
|
|
|
@@ -23,7 +23,7 @@ export const preceding = (path, {write, traverse}) => {
|
|
|
23
23
|
export const following = (path, {write, traverse}) => {
|
|
24
24
|
const [expr] = path.get('arguments');
|
|
25
25
|
|
|
26
|
-
if (expr
|
|
26
|
+
if (expr && expr.node) {
|
|
27
27
|
traverse(expr);
|
|
28
28
|
write(' FOLLOWING');
|
|
29
29
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
|
|
3
|
+
const {isLogicalExpression} = types;
|
|
4
|
+
const needsParensIn = (node, op) => op === '&&' && isLogicalExpression(node) && node.operator === '||';
|
|
2
5
|
|
|
3
6
|
const printNestedOr = (node, printer) => {
|
|
4
7
|
const {write, traverse} = printer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "happy-sql",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "sql to js ast parser and printer",
|
|
6
6
|
"exports": {
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"homepage": "https://github.com/coderaiser/happy-sql",
|
|
85
85
|
"devDependencies": {
|
|
86
|
-
"@putout/plugin-printer": "^
|
|
86
|
+
"@putout/plugin-printer": "^9.0.0",
|
|
87
87
|
"@putout/test": "^15.8.2",
|
|
88
88
|
"eslint": "^10.8.1",
|
|
89
89
|
"eslint-plugin-node": "^11.1.0",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@putout/printer": "^18.38.1",
|
|
104
104
|
"just-camel-case": "^6.2.0",
|
|
105
105
|
"redstd": "^1.0.2",
|
|
106
|
-
"sql-parser-cst": "^0.
|
|
106
|
+
"sql-parser-cst": "^0.44.1",
|
|
107
107
|
"try-catch": "^4.0.9"
|
|
108
108
|
}
|
|
109
109
|
}
|