happy-sql 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ChangeLog +9 -0
- package/README.md +13 -0
- package/lib/parser/parser.js +33 -1
- package/lib/parser/visitors/clause/count/count.js +23 -0
- package/lib/parser/visitors/clause/count/fixture/count-fix.js +3 -0
- package/lib/parser/visitors/clause/count/fixture/count.sql +1 -0
- package/lib/parser/visitors/clause/delete/delete.js +3 -2
- package/lib/parser/visitors/clause/find-clause.js +3 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-identifier-fix.js +3 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-identifier.sql +1 -0
- package/lib/parser/visitors/clause/insert/insert.js +9 -30
- package/lib/parser/visitors/clause/operand.js +20 -0
- package/lib/parser/visitors/clause/select/fixture/select-alias-param-fix.js +3 -0
- package/lib/parser/visitors/clause/select/fixture/select-alias-param.sql +1 -0
- package/lib/parser/visitors/clause/select/select.js +9 -18
- package/lib/parser/visitors/clause/set.js +18 -0
- package/lib/parser/visitors/clause/update/fixture/update-identifier-fix.js +3 -0
- package/lib/parser/visitors/clause/update/fixture/update-identifier.sql +1 -0
- package/lib/parser/visitors/clause/update/fixture/update-number-fix.js +3 -0
- package/lib/parser/visitors/clause/update/fixture/update-number.sql +1 -0
- package/lib/parser/visitors/clause/update/update.js +5 -17
- package/lib/printer/visitors/call-expression/call-expression.js +3 -2
- package/lib/printer/visitors/call-expression/clauses/clauses.js +5 -1
- package/lib/printer/visitors/call-expression/clauses/count/count.js +7 -0
- package/lib/printer/visitors/call-expression/clauses/count/fixture/count.sql +2 -0
- package/lib/printer/visitors/call-expression/clauses/on-conflict/fixture/on-conflict-multi.sql +1 -0
- package/lib/printer/visitors/call-expression/clauses/section/fixture/section-fix.sql +3 -0
- package/lib/printer/visitors/call-expression/clauses/section/section.js +5 -0
- package/package.json +10 -4
- /package/lib/printer/visitors/call-expression/clauses/delete/fixture/{delete-fix.sql → delete.sql} +0 -0
- /package/lib/printer/visitors/call-expression/clauses/on-conflict/fixture/{on-conflict-fix.sql → on-conflict.sql} +0 -0
package/ChangeLog
ADDED
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
[LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat
|
|
8
8
|
[LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License"
|
|
9
9
|
|
|
10
|
+
<img width="600" height="428" alt="image" src="https://github.com/user-attachments/assets/aefbd055-957e-4de3-aca5-c30e5e3ee3f9" />
|
|
11
|
+
|
|
10
12
|
SQL to JS AST parser and printer.
|
|
11
13
|
|
|
12
14
|
## Install
|
|
@@ -15,6 +17,17 @@ SQL to JS AST parser and printer.
|
|
|
15
17
|
npm i happy-sql --save
|
|
16
18
|
```
|
|
17
19
|
|
|
20
|
+
## API
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const source = `select(count('*'), from(users))`;
|
|
24
|
+
|
|
25
|
+
convertJsToSql(source);
|
|
26
|
+
// returns
|
|
27
|
+
'SELECT COUNT(*)\nFROM users\n');
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
|
|
18
31
|
## License
|
|
19
32
|
|
|
20
33
|
MIT
|
package/lib/parser/parser.js
CHANGED
|
@@ -3,20 +3,52 @@ import {types} from '@putout/babel';
|
|
|
3
3
|
import {convertClause} from '#parser/clause';
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
|
+
identifier,
|
|
7
|
+
stringLiteral,
|
|
8
|
+
callExpression,
|
|
6
9
|
arrayExpression,
|
|
7
10
|
expressionStatement,
|
|
8
11
|
file,
|
|
9
12
|
program,
|
|
10
13
|
} = types;
|
|
11
14
|
|
|
15
|
+
const getSectionName = (leading) => {
|
|
16
|
+
const comment = leading?.find((c) => c.text.startsWith('-- @'));
|
|
17
|
+
|
|
18
|
+
return comment ? comment.text
|
|
19
|
+
.slice(2)
|
|
20
|
+
.trim() : null;
|
|
21
|
+
};
|
|
22
|
+
|
|
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
|
+
|
|
12
33
|
export const parseSqlNode = (source) => {
|
|
13
34
|
const cst = parse(source, {
|
|
14
35
|
dialect: 'sqlite',
|
|
15
36
|
paramTypes: [':name'],
|
|
16
37
|
includeRange: true,
|
|
38
|
+
includeComments: true,
|
|
17
39
|
});
|
|
18
40
|
|
|
19
|
-
const elements =
|
|
41
|
+
const elements = [];
|
|
42
|
+
|
|
43
|
+
for (const [i, statement] of cst.statements.entries()) {
|
|
44
|
+
if (statement.type === 'empty')
|
|
45
|
+
continue;
|
|
46
|
+
|
|
47
|
+
const stmtNode = convertClause(statement);
|
|
48
|
+
const leading = i ? statement.leading : cst.leading;
|
|
49
|
+
|
|
50
|
+
elements.push(wrapSection(stmtNode, getSectionName(leading)));
|
|
51
|
+
}
|
|
20
52
|
|
|
21
53
|
return file(program([
|
|
22
54
|
expressionStatement(arrayExpression(elements)),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
stringLiteral,
|
|
5
|
+
callExpression,
|
|
6
|
+
identifier,
|
|
7
|
+
} = types;
|
|
8
|
+
|
|
9
|
+
export const isCount = (column) => {
|
|
10
|
+
return column.type === 'func_call' && /count/i.test(column.name.name);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const convertCount = (column) => {
|
|
14
|
+
const {items} = column.args.expr.args;
|
|
15
|
+
const args = [];
|
|
16
|
+
|
|
17
|
+
for (const item of items) {
|
|
18
|
+
if (item.type === 'all_columns')
|
|
19
|
+
args.push(stringLiteral('*'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return callExpression(identifier('count'), args);
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT COUNT(*) FROM users
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {types} from '@putout/babel';
|
|
2
2
|
import {convertWhere} from '#parser/clause/where';
|
|
3
|
+
import {findClause} from '#parser/clause/find';
|
|
3
4
|
|
|
4
5
|
const {identifier, callExpression} = types;
|
|
5
6
|
|
|
6
7
|
export const convertDelete = ({clauses}) => {
|
|
7
|
-
const deleteClause = clauses
|
|
8
|
-
const whereClause = clauses
|
|
8
|
+
const deleteClause = findClause(clauses, 'delete_clause');
|
|
9
|
+
const whereClause = findClause(clauses, 'where_clause');
|
|
9
10
|
|
|
10
11
|
const table = identifier(deleteClause.tables.items[0].name);
|
|
11
12
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x) ON CONFLICT (x) DO UPDATE SET y = x
|
|
@@ -1,41 +1,22 @@
|
|
|
1
1
|
import {types} from '@putout/babel';
|
|
2
|
+
import {convertSet} from '#parser/clause/set';
|
|
3
|
+
import {findClause} from '#parser/clause/find';
|
|
2
4
|
|
|
3
5
|
const {
|
|
4
6
|
identifier,
|
|
5
|
-
stringLiteral,
|
|
6
|
-
binaryExpression,
|
|
7
|
-
arrayExpression,
|
|
8
7
|
callExpression,
|
|
8
|
+
arrayExpression,
|
|
9
|
+
stringLiteral,
|
|
9
10
|
} = types;
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
return stringLiteral(node.text);
|
|
14
|
-
|
|
15
|
-
if (node.type === 'number_literal')
|
|
16
|
-
return types.numericLiteral(node.value);
|
|
17
|
-
|
|
18
|
-
if (node.type === 'string_literal')
|
|
19
|
-
return stringLiteral(node.value);
|
|
20
|
-
|
|
21
|
-
return identifier(node.name);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const convertSet = (setClause) => {
|
|
25
|
-
const args = [];
|
|
26
|
-
|
|
27
|
-
for (const a of setClause.assignments.items) {
|
|
28
|
-
args.push(binaryExpression('===', identifier(a.column.name), convertOperand(a.expr)));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return callExpression(identifier('set'), args);
|
|
32
|
-
};
|
|
12
|
+
const convertExprToIdentifier = ({expr}) => identifier(expr.name);
|
|
13
|
+
const convertColToIdentifier = ({name}) => identifier(name);
|
|
33
14
|
|
|
34
15
|
const convertUpsert = (upsertClause) => {
|
|
35
16
|
const target = upsertClause.conflictTarget;
|
|
36
17
|
const {action} = upsertClause;
|
|
37
18
|
|
|
38
|
-
const targetArgs = target ? target.expr.items.map(
|
|
19
|
+
const targetArgs = target ? target.expr.items.map(convertExprToIdentifier) : [];
|
|
39
20
|
const actionNode = action.type === 'upsert_action_nothing' ? identifier('nothing') : convertSet(action.set);
|
|
40
21
|
|
|
41
22
|
return callExpression(identifier('onConflict'), [...targetArgs, actionNode]);
|
|
@@ -44,13 +25,11 @@ const convertUpsert = (upsertClause) => {
|
|
|
44
25
|
export const convertInsert = ({clauses}) => {
|
|
45
26
|
const [ic] = clauses;
|
|
46
27
|
const [, vc] = clauses;
|
|
47
|
-
const uc = clauses
|
|
28
|
+
const uc = findClause(clauses, 'upsert_clause');
|
|
48
29
|
|
|
49
30
|
const cols = ic.columns.expr.items;
|
|
50
31
|
const vals = vc.values.items[0].expr.items;
|
|
51
|
-
|
|
52
|
-
const colArg = cols.length === 1 ? identifier(cols[0].name) : arrayExpression(cols.map((c) => identifier(c.name)));
|
|
53
|
-
|
|
32
|
+
const colArg = cols.length === 1 ? identifier(cols[0].name) : arrayExpression(cols.map(convertColToIdentifier));
|
|
54
33
|
const valArgs = [];
|
|
55
34
|
|
|
56
35
|
for (const p of vals) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
identifier,
|
|
5
|
+
stringLiteral,
|
|
6
|
+
numericLiteral,
|
|
7
|
+
} = types;
|
|
8
|
+
|
|
9
|
+
export const convertOperand = (node) => {
|
|
10
|
+
if (node.type === 'parameter')
|
|
11
|
+
return stringLiteral(node.text);
|
|
12
|
+
|
|
13
|
+
if (node.type === 'number_literal')
|
|
14
|
+
return numericLiteral(node.value);
|
|
15
|
+
|
|
16
|
+
if (node.type === 'string_literal')
|
|
17
|
+
return stringLiteral(node.value);
|
|
18
|
+
|
|
19
|
+
return identifier(node.name);
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT :file AS file FROM t
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {types} from '@putout/babel';
|
|
2
|
+
import {convertOperand} from '#parser/clause/operand';
|
|
2
3
|
import {convertFrom} from '#parser/clause/from';
|
|
3
4
|
import {convertWhere} from '#parser/clause/where';
|
|
5
|
+
import {findClause} from '#parser/clause/find';
|
|
6
|
+
import {convertCount, isCount} from '#parser/clause/count';
|
|
4
7
|
|
|
5
8
|
const {
|
|
6
9
|
identifier,
|
|
@@ -9,26 +12,14 @@ const {
|
|
|
9
12
|
callExpression,
|
|
10
13
|
} = types;
|
|
11
14
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (node.type === 'parameter')
|
|
16
|
-
return stringLiteral(node.text);
|
|
17
|
-
|
|
18
|
-
if (node.type === 'number_literal')
|
|
19
|
-
return types.numericLiteral(node.value);
|
|
20
|
-
|
|
21
|
-
if (node.type === 'string_literal')
|
|
22
|
-
return stringLiteral(node.value);
|
|
15
|
+
const convertColumn = (column) => {
|
|
16
|
+
if (isCount(column))
|
|
17
|
+
return convertCount(column);
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const convertColumn = (col) => {
|
|
28
|
-
if (col.type === 'alias')
|
|
29
|
-
return assignmentExpression('=', identifier(col.alias.name), convertOperand(col.expr));
|
|
19
|
+
if (column.type === 'alias')
|
|
20
|
+
return assignmentExpression('=', identifier(column.alias.name), convertOperand(column.expr));
|
|
30
21
|
|
|
31
|
-
return identifier(
|
|
22
|
+
return identifier(column.name);
|
|
32
23
|
};
|
|
33
24
|
|
|
34
25
|
const convertColumns = (columns) => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
import {convertOperand} from '#parser/clause/operand';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
callExpression,
|
|
6
|
+
identifier,
|
|
7
|
+
binaryExpression,
|
|
8
|
+
} = types;
|
|
9
|
+
|
|
10
|
+
export const convertSet = (setClause) => {
|
|
11
|
+
const args = [];
|
|
12
|
+
|
|
13
|
+
for (const a of setClause.assignments.items) {
|
|
14
|
+
args.push(binaryExpression('===', identifier(a.column.name), convertOperand(a.expr)));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return callExpression(identifier('set'), args);
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UPDATE t SET x = y WHERE id = :id
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UPDATE t SET x = 1 WHERE id = :id
|
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
import {types} from '@putout/babel';
|
|
2
2
|
import {convertWhere} from '#parser/clause/where';
|
|
3
|
+
import {convertOperand} from '#parser/clause/operand';
|
|
4
|
+
import {findClause} from '#parser/clause/find';
|
|
3
5
|
|
|
4
6
|
const {
|
|
5
7
|
identifier,
|
|
6
|
-
stringLiteral,
|
|
7
8
|
binaryExpression,
|
|
8
9
|
callExpression,
|
|
9
10
|
} = types;
|
|
10
11
|
|
|
11
|
-
const convertOperand = (node) => {
|
|
12
|
-
if (node.type === 'parameter')
|
|
13
|
-
return stringLiteral(node.text);
|
|
14
|
-
|
|
15
|
-
if (node.type === 'number_literal')
|
|
16
|
-
return types.numericLiteral(node.value);
|
|
17
|
-
|
|
18
|
-
if (node.type === 'string_literal')
|
|
19
|
-
return stringLiteral(node.value);
|
|
20
|
-
|
|
21
|
-
return identifier(node.name);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
12
|
export const convertUpdate = ({clauses}) => {
|
|
25
|
-
const updateClause = clauses
|
|
26
|
-
const setClause = clauses
|
|
27
|
-
const whereClause = clauses
|
|
13
|
+
const updateClause = findClause(clauses, 'update_clause');
|
|
14
|
+
const setClause = findClause(clauses, 'set_clause');
|
|
15
|
+
const whereClause = findClause(clauses, 'where_clause');
|
|
28
16
|
|
|
29
17
|
const table = identifier(updateClause.tables.items[0].name);
|
|
30
18
|
const setArgs = [];
|
|
@@ -2,9 +2,10 @@ import {clauses} from './clauses/clauses.js';
|
|
|
2
2
|
|
|
3
3
|
export const CallExpression = (path, printer) => {
|
|
4
4
|
const {name} = path.node.callee;
|
|
5
|
+
const visit = clauses[name];
|
|
5
6
|
|
|
6
|
-
if (!
|
|
7
|
+
if (!visit)
|
|
7
8
|
throw Error(`${name} not supported yet`);
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
visit(path, printer);
|
|
10
11
|
};
|
|
@@ -8,10 +8,13 @@ import {onConflict} from './on-conflict/on-conflict.js';
|
|
|
8
8
|
import {update} from './update/update.js';
|
|
9
9
|
import {set} from './set/set.js';
|
|
10
10
|
import {del} from './delete/delete.js';
|
|
11
|
+
import {section} from './section/section.js';
|
|
12
|
+
import {count} from './count/count.js';
|
|
11
13
|
|
|
12
14
|
export const clauses = {
|
|
13
|
-
|
|
15
|
+
count,
|
|
14
16
|
from,
|
|
17
|
+
select,
|
|
15
18
|
where,
|
|
16
19
|
insert,
|
|
17
20
|
into,
|
|
@@ -20,4 +23,5 @@ export const clauses = {
|
|
|
20
23
|
update,
|
|
21
24
|
set,
|
|
22
25
|
delete: del,
|
|
26
|
+
section,
|
|
23
27
|
};
|
package/lib/printer/visitors/call-expression/clauses/on-conflict/fixture/on-conflict-multi.sql
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x) ON CONFLICT (x, y) DO UPDATE SET x = :x
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "happy-sql",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "sql to js ast parser and printer",
|
|
6
6
|
"exports": {
|
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
"imports": {
|
|
13
13
|
"#happy-sql": "./lib/happy-sql.js",
|
|
14
14
|
"#parser": "./lib/parser/parser.js",
|
|
15
|
+
"#parser/clause/operand": "./lib/parser/visitors/clause/operand.js",
|
|
16
|
+
"#parser/clause/set": "./lib/parser/visitors/clause/set.js",
|
|
17
|
+
"#parser/clause/find": "./lib/parser/visitors/clause/find-clause.js",
|
|
15
18
|
"#parser/test": "./test/create-parser-test.js",
|
|
16
19
|
"#parser/clause": "./lib/parser/visitors/clause/index.js",
|
|
17
20
|
"#parser/clause/select": "./lib/parser/visitors/clause/select/select.js",
|
|
21
|
+
"#parser/clause/count": "./lib/parser/visitors/clause/count/count.js",
|
|
18
22
|
"#parser/clause/from": "./lib/parser/visitors/clause/from/from.js",
|
|
19
23
|
"#parser/clause/where": "./lib/parser/visitors/clause/where/where.js",
|
|
20
24
|
"#parser/clause/insert": "./lib/parser/visitors/clause/insert/insert.js",
|
|
@@ -42,9 +46,10 @@
|
|
|
42
46
|
"url": "git+https://github.com/coderaiser/happy-sql.git"
|
|
43
47
|
},
|
|
44
48
|
"keywords": [
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
49
|
+
"sql",
|
|
50
|
+
"js",
|
|
51
|
+
"print",
|
|
52
|
+
"parse"
|
|
48
53
|
],
|
|
49
54
|
"author": "coderaiser <mnemonic.enemy@gmail.com> (http://coderaiser.github.io/)",
|
|
50
55
|
"license": "MIT",
|
|
@@ -75,6 +80,7 @@
|
|
|
75
80
|
"@putout/babel": "^5.7.3",
|
|
76
81
|
"@putout/operator-json": "^3.4.0",
|
|
77
82
|
"@putout/printer": "^18.17.0",
|
|
83
|
+
"just-camel-case": "^6.2.0",
|
|
78
84
|
"redstd": "^1.0.2",
|
|
79
85
|
"sql-parser-cst": "^0.42.1"
|
|
80
86
|
}
|
/package/lib/printer/visitors/call-expression/clauses/delete/fixture/{delete-fix.sql → delete.sql}
RENAMED
|
File without changes
|
|
File without changes
|