happy-sql 1.0.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/LICENSE +21 -0
- package/README.md +20 -0
- package/bin/convert.js +18 -0
- package/bin/happy-sql.js +10 -0
- package/lib/happy-sql.js +11 -0
- package/lib/parser/fixture/select-fix.js +3 -0
- package/lib/parser/fixture/select.sql +1 -0
- package/lib/parser/parser.js +24 -0
- package/lib/parser/visitors/clause/delete/delete.js +18 -0
- package/lib/parser/visitors/clause/delete/fixture/delete-fix.js +3 -0
- package/lib/parser/visitors/clause/delete/fixture/delete.sql +1 -0
- package/lib/parser/visitors/clause/from/fixture/from-fix.js +3 -0
- package/lib/parser/visitors/clause/from/fixture/from.sql +1 -0
- package/lib/parser/visitors/clause/from/from.js +7 -0
- package/lib/parser/visitors/clause/index.js +21 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-fix.js +3 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-multi-fix.js +3 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-multi.sql +1 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-number-fix.js +3 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-number.sql +1 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-string-fix.js +3 -0
- package/lib/parser/visitors/clause/insert/fixture/insert-string.sql +1 -0
- package/lib/parser/visitors/clause/insert/fixture/insert.sql +1 -0
- package/lib/parser/visitors/clause/insert/insert.js +70 -0
- package/lib/parser/visitors/clause/on-conflict/fixture/on-conflict-fix.js +3 -0
- package/lib/parser/visitors/clause/on-conflict/fixture/on-conflict-nothing-fix.js +3 -0
- package/lib/parser/visitors/clause/on-conflict/fixture/on-conflict-nothing.sql +1 -0
- package/lib/parser/visitors/clause/on-conflict/fixture/on-conflict.sql +1 -0
- package/lib/parser/visitors/clause/select/fixture/select-alias-fix.js +3 -0
- package/lib/parser/visitors/clause/select/fixture/select-alias.sql +1 -0
- package/lib/parser/visitors/clause/select/fixture/select-fix.js +3 -0
- package/lib/parser/visitors/clause/select/fixture/select-number-fix.js +3 -0
- package/lib/parser/visitors/clause/select/fixture/select-number.sql +1 -0
- package/lib/parser/visitors/clause/select/fixture/select.sql +1 -0
- package/lib/parser/visitors/clause/select/select.js +55 -0
- package/lib/parser/visitors/clause/update/fixture/update-fix.js +3 -0
- package/lib/parser/visitors/clause/update/fixture/update-no-where-fix.js +3 -0
- package/lib/parser/visitors/clause/update/fixture/update-no-where.sql +1 -0
- package/lib/parser/visitors/clause/update/fixture/update-string-fix.js +3 -0
- package/lib/parser/visitors/clause/update/fixture/update-string.sql +1 -0
- package/lib/parser/visitors/clause/update/fixture/update.sql +1 -0
- package/lib/parser/visitors/clause/update/update.js +45 -0
- package/lib/parser/visitors/clause/where/fixture/where-fix.js +3 -0
- package/lib/parser/visitors/clause/where/fixture/where-number-fix.js +3 -0
- package/lib/parser/visitors/clause/where/fixture/where-number.sql +1 -0
- package/lib/parser/visitors/clause/where/fixture/where-or-fix.js +3 -0
- package/lib/parser/visitors/clause/where/fixture/where-or.sql +1 -0
- package/lib/parser/visitors/clause/where/fixture/where.sql +1 -0
- package/lib/parser/visitors/clause/where/where.js +41 -0
- package/lib/printer/fixture/select-fix.sql +2 -0
- package/lib/printer/printer.js +8 -0
- package/lib/printer/visitors/array-expression.js +11 -0
- package/lib/printer/visitors/assignment-expression.js +5 -0
- package/lib/printer/visitors/binary-expression.js +5 -0
- package/lib/printer/visitors/call-expression/call-expression.js +10 -0
- package/lib/printer/visitors/call-expression/clauses/clauses.js +23 -0
- package/lib/printer/visitors/call-expression/clauses/delete/delete.js +13 -0
- package/lib/printer/visitors/call-expression/clauses/delete/fixture/delete-fix.sql +2 -0
- package/lib/printer/visitors/call-expression/clauses/from/fixture/from-fix.sql +2 -0
- package/lib/printer/visitors/call-expression/clauses/from/from.js +5 -0
- package/lib/printer/visitors/call-expression/clauses/insert/fixture/insert-fix.sql +1 -0
- package/lib/printer/visitors/call-expression/clauses/insert/insert.js +9 -0
- package/lib/printer/visitors/call-expression/clauses/into/fixture/into-fix.sql +1 -0
- package/lib/printer/visitors/call-expression/clauses/into/into.js +26 -0
- package/lib/printer/visitors/call-expression/clauses/on-conflict/fixture/on-conflict-fix.sql +1 -0
- package/lib/printer/visitors/call-expression/clauses/on-conflict/on-conflict.js +29 -0
- package/lib/printer/visitors/call-expression/clauses/select/fixture/select-fix.sql +2 -0
- package/lib/printer/visitors/call-expression/clauses/select/select.js +19 -0
- package/lib/printer/visitors/call-expression/clauses/set/fixture/set-fix.sql +1 -0
- package/lib/printer/visitors/call-expression/clauses/set/set.js +10 -0
- package/lib/printer/visitors/call-expression/clauses/update/fixture/update-fix.sql +3 -0
- package/lib/printer/visitors/call-expression/clauses/update/update.js +16 -0
- package/lib/printer/visitors/call-expression/clauses/values/fixture/values-fix.sql +1 -0
- package/lib/printer/visitors/call-expression/clauses/values/values.js +12 -0
- package/lib/printer/visitors/call-expression/clauses/where/fixture/where-fix.sql +3 -0
- package/lib/printer/visitors/call-expression/clauses/where/where.js +5 -0
- package/lib/printer/visitors/expression-statement.js +3 -0
- package/lib/printer/visitors/index.js +17 -0
- package/lib/printer/visitors/logical-expression.js +6 -0
- package/lib/printer/visitors/string-literal.js +8 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) coderaiser
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Happy Sql [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL]
|
|
2
|
+
|
|
3
|
+
[NPMURL]: https://npmjs.org/package/happy-sql "npm"
|
|
4
|
+
[NPMIMGURL]: https://img.shields.io/npm/v/happy-sql.svg?style=flat
|
|
5
|
+
[BuildStatusURL]: https://github.com/coderaiser/happy-sql/actions?query=workflow%3A%22Node+CI%22 "Build Status"
|
|
6
|
+
[BuildStatusIMGURL]: https://github.com/coderaiser/happy-sql/workflows/Node%20CI/badge.svg
|
|
7
|
+
[LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat
|
|
8
|
+
[LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License"
|
|
9
|
+
|
|
10
|
+
SQL to JS AST parser and printer.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
npm i happy-sql --save
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## License
|
|
19
|
+
|
|
20
|
+
MIT
|
package/bin/convert.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__markdown_name,
|
|
3
|
+
fromJS,
|
|
4
|
+
} from '@putout/operator-json';
|
|
5
|
+
import {convertJsToSql, convertSqlToJs} from '#happy-sql';
|
|
6
|
+
|
|
7
|
+
export function convert(source) {
|
|
8
|
+
if (source.startsWith(__markdown_name))
|
|
9
|
+
return convertJsToSql(fromJS(
|
|
10
|
+
source,
|
|
11
|
+
__markdown_name,
|
|
12
|
+
));
|
|
13
|
+
|
|
14
|
+
if (source.startsWith('['))
|
|
15
|
+
return convertJsToSql(source);
|
|
16
|
+
|
|
17
|
+
return convertSqlToJs(source);
|
|
18
|
+
}
|
package/bin/happy-sql.js
ADDED
package/lib/happy-sql.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {parse} from '@putout/babel';
|
|
2
|
+
import {print} from '@putout/printer';
|
|
3
|
+
import {parseSqlNode} from '#parser';
|
|
4
|
+
import {printSql} from '#printer';
|
|
5
|
+
|
|
6
|
+
export {printSql} from '#printer';
|
|
7
|
+
export {parseSqlNode} from '#parser';
|
|
8
|
+
|
|
9
|
+
export const convertSqlToJs = (sql) => print(parseSqlNode(sql));
|
|
10
|
+
|
|
11
|
+
export const convertJsToSql = (js) => printSql(parse(js));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT * FROM users
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {parse} from 'sql-parser-cst';
|
|
2
|
+
import {types} from '@putout/babel';
|
|
3
|
+
import {convertClause} from '#parser/clause';
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
arrayExpression,
|
|
7
|
+
expressionStatement,
|
|
8
|
+
file,
|
|
9
|
+
program,
|
|
10
|
+
} = types;
|
|
11
|
+
|
|
12
|
+
export const parseSqlNode = (source) => {
|
|
13
|
+
const cst = parse(source, {
|
|
14
|
+
dialect: 'sqlite',
|
|
15
|
+
paramTypes: [':name'],
|
|
16
|
+
includeRange: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const elements = cst.statements.map(convertClause);
|
|
20
|
+
|
|
21
|
+
return file(program([
|
|
22
|
+
expressionStatement(arrayExpression(elements)),
|
|
23
|
+
]));
|
|
24
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
import {convertWhere} from '#parser/clause/where';
|
|
3
|
+
|
|
4
|
+
const {identifier, callExpression} = types;
|
|
5
|
+
|
|
6
|
+
export const convertDelete = ({clauses}) => {
|
|
7
|
+
const deleteClause = clauses.find((c) => c.type === 'delete_clause');
|
|
8
|
+
const whereClause = clauses.find((c) => c.type === 'where_clause');
|
|
9
|
+
|
|
10
|
+
const table = identifier(deleteClause.tables.items[0].name);
|
|
11
|
+
|
|
12
|
+
const args = [
|
|
13
|
+
callExpression(identifier('from'), [table]),
|
|
14
|
+
whereClause && convertWhere(whereClause),
|
|
15
|
+
].filter(Boolean);
|
|
16
|
+
|
|
17
|
+
return callExpression(identifier('delete'), args);
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DELETE FROM t WHERE id = :id
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT * FROM users
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {convertSelect} from '#parser/clause/select';
|
|
2
|
+
import {convertInsert} from '#parser/clause/insert';
|
|
3
|
+
import {convertUpdate} from '#parser/clause/update';
|
|
4
|
+
import {convertDelete} from '#parser/clause/delete';
|
|
5
|
+
|
|
6
|
+
export const convertClause = (stmt) => {
|
|
7
|
+
const {type} = stmt;
|
|
8
|
+
const visitor = clauseVisitors[type];
|
|
9
|
+
|
|
10
|
+
if (visitor)
|
|
11
|
+
return visitor(stmt);
|
|
12
|
+
|
|
13
|
+
throw Error(`${type} not supported yet`);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const clauseVisitors = {
|
|
17
|
+
select_stmt: convertSelect,
|
|
18
|
+
insert_stmt: convertInsert,
|
|
19
|
+
update_stmt: convertUpdate,
|
|
20
|
+
delete_stmt: convertDelete,
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x, y) VALUES (:x, :y)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x) ON CONFLICT (x, y) DO UPDATE SET count = 1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x) ON CONFLICT (x) DO UPDATE SET name = 'hello'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
identifier,
|
|
5
|
+
stringLiteral,
|
|
6
|
+
binaryExpression,
|
|
7
|
+
arrayExpression,
|
|
8
|
+
callExpression,
|
|
9
|
+
} = types;
|
|
10
|
+
|
|
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
|
+
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
|
+
};
|
|
33
|
+
|
|
34
|
+
const convertUpsert = (upsertClause) => {
|
|
35
|
+
const target = upsertClause.conflictTarget;
|
|
36
|
+
const {action} = upsertClause;
|
|
37
|
+
|
|
38
|
+
const targetArgs = target ? target.expr.items.map((i) => identifier(i.expr.name)) : [];
|
|
39
|
+
const actionNode = action.type === 'upsert_action_nothing' ? identifier('nothing') : convertSet(action.set);
|
|
40
|
+
|
|
41
|
+
return callExpression(identifier('onConflict'), [...targetArgs, actionNode]);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const convertInsert = ({clauses}) => {
|
|
45
|
+
const [ic] = clauses;
|
|
46
|
+
const [, vc] = clauses;
|
|
47
|
+
const uc = clauses.find((c) => c.type === 'upsert_clause');
|
|
48
|
+
|
|
49
|
+
const cols = ic.columns.expr.items;
|
|
50
|
+
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
|
+
|
|
54
|
+
const valArgs = [];
|
|
55
|
+
|
|
56
|
+
for (const p of vals) {
|
|
57
|
+
valArgs.push(stringLiteral(p.text));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const args = [
|
|
61
|
+
callExpression(identifier('into'), [
|
|
62
|
+
identifier(ic.table.name),
|
|
63
|
+
colArg,
|
|
64
|
+
callExpression(identifier('values'), valArgs),
|
|
65
|
+
]),
|
|
66
|
+
uc && convertUpsert(uc),
|
|
67
|
+
].filter(Boolean);
|
|
68
|
+
|
|
69
|
+
return callExpression(identifier('insert'), args);
|
|
70
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x) ON CONFLICT DO NOTHING
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x) ON CONFLICT (x) DO UPDATE SET x = :x
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT 'msg' AS message, 42 AS num FROM t
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT * FROM t WHERE num = 42
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT * FROM users
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
import {convertFrom} from '#parser/clause/from';
|
|
3
|
+
import {convertWhere} from '#parser/clause/where';
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
identifier,
|
|
7
|
+
stringLiteral,
|
|
8
|
+
assignmentExpression,
|
|
9
|
+
callExpression,
|
|
10
|
+
} = types;
|
|
11
|
+
|
|
12
|
+
const findClause = (clauses, type) => clauses.find((c) => c.type === type);
|
|
13
|
+
|
|
14
|
+
const convertOperand = (node) => {
|
|
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);
|
|
23
|
+
|
|
24
|
+
return identifier(node.name);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const convertColumn = (col) => {
|
|
28
|
+
if (col.type === 'alias')
|
|
29
|
+
return assignmentExpression('=', identifier(col.alias.name), convertOperand(col.expr));
|
|
30
|
+
|
|
31
|
+
return identifier(col.name);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const convertColumns = (columns) => {
|
|
35
|
+
if (columns.items.length === 1 && columns.items[0].type === 'all_columns')
|
|
36
|
+
return [
|
|
37
|
+
stringLiteral('*'),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
return columns.items.map(convertColumn);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const convertSelect = ({clauses}) => {
|
|
44
|
+
const selectClause = findClause(clauses, 'select_clause');
|
|
45
|
+
const fromClause = findClause(clauses, 'from_clause');
|
|
46
|
+
const whereClause = findClause(clauses, 'where_clause');
|
|
47
|
+
|
|
48
|
+
const args = [
|
|
49
|
+
...convertColumns(selectClause.columns),
|
|
50
|
+
convertFrom(fromClause),
|
|
51
|
+
whereClause && convertWhere(whereClause),
|
|
52
|
+
].filter(Boolean);
|
|
53
|
+
|
|
54
|
+
return callExpression(identifier('select'), args);
|
|
55
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UPDATE t SET x = :x
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UPDATE t SET name = 'hello' WHERE id = :id
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UPDATE t SET x = :x WHERE id = :id
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
import {convertWhere} from '#parser/clause/where';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
identifier,
|
|
6
|
+
stringLiteral,
|
|
7
|
+
binaryExpression,
|
|
8
|
+
callExpression,
|
|
9
|
+
} = types;
|
|
10
|
+
|
|
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
|
+
export const convertUpdate = ({clauses}) => {
|
|
25
|
+
const updateClause = clauses.find((c) => c.type === 'update_clause');
|
|
26
|
+
const setClause = clauses.find((c) => c.type === 'set_clause');
|
|
27
|
+
const whereClause = clauses.find((c) => c.type === 'where_clause');
|
|
28
|
+
|
|
29
|
+
const table = identifier(updateClause.tables.items[0].name);
|
|
30
|
+
const setArgs = [];
|
|
31
|
+
|
|
32
|
+
for (const a of setClause.assignments.items) {
|
|
33
|
+
setArgs.push(binaryExpression('===', identifier(a.column.name), convertOperand(a.expr)));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const setCall = callExpression(identifier('set'), setArgs);
|
|
37
|
+
|
|
38
|
+
const args = [
|
|
39
|
+
table,
|
|
40
|
+
setCall,
|
|
41
|
+
whereClause && convertWhere(whereClause),
|
|
42
|
+
].filter(Boolean);
|
|
43
|
+
|
|
44
|
+
return callExpression(identifier('update'), args);
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT * FROM t WHERE id = 42
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT * FROM t WHERE x = :x OR y = :y
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT * FROM users WHERE id = :id
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {types} from '@putout/babel';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
binaryExpression,
|
|
5
|
+
logicalExpression,
|
|
6
|
+
identifier,
|
|
7
|
+
stringLiteral,
|
|
8
|
+
numericLiteral,
|
|
9
|
+
callExpression,
|
|
10
|
+
} = types;
|
|
11
|
+
|
|
12
|
+
const convertOperand = (node) => {
|
|
13
|
+
if (node.type === 'parameter')
|
|
14
|
+
return stringLiteral(node.text);
|
|
15
|
+
|
|
16
|
+
if (node.type === 'number_literal')
|
|
17
|
+
return numericLiteral(node.value);
|
|
18
|
+
|
|
19
|
+
if (node.type === 'string_literal')
|
|
20
|
+
return stringLiteral(node.value);
|
|
21
|
+
|
|
22
|
+
return identifier(node.name);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const convertAnd = (node) => logicalExpression('&&', convertCondition(node.left), convertCondition(node.right));
|
|
26
|
+
const convertOr = (node) => logicalExpression('||', convertCondition(node.left), convertCondition(node.right));
|
|
27
|
+
const convertEq = (node) => binaryExpression('===', convertOperand(node.left), convertOperand(node.right));
|
|
28
|
+
|
|
29
|
+
const convertCondition = (node) => {
|
|
30
|
+
if (node.operator?.name === 'AND')
|
|
31
|
+
return convertAnd(node);
|
|
32
|
+
|
|
33
|
+
if (node.operator?.name === 'OR')
|
|
34
|
+
return convertOr(node);
|
|
35
|
+
|
|
36
|
+
return convertEq(node);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const convertWhere = (node) => callExpression(identifier('where'), [
|
|
40
|
+
convertCondition(node.expr),
|
|
41
|
+
]);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {select} from './select/select.js';
|
|
2
|
+
import {from} from './from/from.js';
|
|
3
|
+
import {where} from './where/where.js';
|
|
4
|
+
import {insert} from './insert/insert.js';
|
|
5
|
+
import {into} from './into/into.js';
|
|
6
|
+
import {values} from './values/values.js';
|
|
7
|
+
import {onConflict} from './on-conflict/on-conflict.js';
|
|
8
|
+
import {update} from './update/update.js';
|
|
9
|
+
import {set} from './set/set.js';
|
|
10
|
+
import {del} from './delete/delete.js';
|
|
11
|
+
|
|
12
|
+
export const clauses = {
|
|
13
|
+
select,
|
|
14
|
+
from,
|
|
15
|
+
where,
|
|
16
|
+
insert,
|
|
17
|
+
into,
|
|
18
|
+
values,
|
|
19
|
+
onConflict,
|
|
20
|
+
update,
|
|
21
|
+
set,
|
|
22
|
+
delete: del,
|
|
23
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const del = (path, {write, traverse}) => {
|
|
2
|
+
const args = path.get('arguments');
|
|
3
|
+
const [fromArg] = args;
|
|
4
|
+
const [, whereArg] = args;
|
|
5
|
+
|
|
6
|
+
write('DELETE ');
|
|
7
|
+
traverse(fromArg);
|
|
8
|
+
|
|
9
|
+
if (whereArg) {
|
|
10
|
+
write('\n');
|
|
11
|
+
traverse(whereArg);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const into = (path, {write, traverse}) => {
|
|
2
|
+
const args = path.get('arguments');
|
|
3
|
+
const [table] = args;
|
|
4
|
+
const [, columns] = args;
|
|
5
|
+
const values = args[2];
|
|
6
|
+
|
|
7
|
+
write('INSERT INTO ');
|
|
8
|
+
traverse(table);
|
|
9
|
+
write(' (');
|
|
10
|
+
|
|
11
|
+
if (columns.node.type === 'ArrayExpression') {
|
|
12
|
+
const colArgs = columns.get('elements');
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < colArgs.length; i++) {
|
|
15
|
+
if (i > 0)
|
|
16
|
+
write(', ');
|
|
17
|
+
|
|
18
|
+
traverse(colArgs[i]);
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
traverse(columns);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
write(') ');
|
|
25
|
+
traverse(values);
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INSERT INTO t (x) VALUES (:x) ON CONFLICT (x) DO UPDATE SET x = :x
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const onConflict = (path, {write, traverse}) => {
|
|
2
|
+
const args = path.get('arguments');
|
|
3
|
+
const action = args.at(-1);
|
|
4
|
+
const targets = args.slice(0, -1);
|
|
5
|
+
|
|
6
|
+
write('ON CONFLICT');
|
|
7
|
+
|
|
8
|
+
if (targets.length) {
|
|
9
|
+
write(' (');
|
|
10
|
+
for (let i = 0; i < targets.length; i++) {
|
|
11
|
+
if (i > 0)
|
|
12
|
+
write(', ');
|
|
13
|
+
|
|
14
|
+
traverse(targets[i]);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
write(')');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
write(' DO ');
|
|
21
|
+
|
|
22
|
+
if (action.node.type === 'Identifier' && action.node.name === 'nothing') {
|
|
23
|
+
write('NOTHING');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
write('UPDATE ');
|
|
28
|
+
traverse(action);
|
|
29
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const select = (path, {write, traverse}) => {
|
|
2
|
+
const args = path.get('arguments');
|
|
3
|
+
const fromIdx = args.findIndex((a) => a.node.callee?.name === 'from');
|
|
4
|
+
const cols = args.slice(0, fromIdx);
|
|
5
|
+
const rest = args.slice(fromIdx);
|
|
6
|
+
|
|
7
|
+
write('SELECT ');
|
|
8
|
+
for (let i = 0; i < cols.length; i++) {
|
|
9
|
+
if (i > 0)
|
|
10
|
+
write(', ');
|
|
11
|
+
|
|
12
|
+
traverse(cols[i]);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const arg of rest) {
|
|
16
|
+
write('\n');
|
|
17
|
+
traverse(arg);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SET x = :x
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const update = (path, {write, traverse}) => {
|
|
2
|
+
const args = path.get('arguments');
|
|
3
|
+
const [table] = args;
|
|
4
|
+
const [, set] = args;
|
|
5
|
+
const where = args[2];
|
|
6
|
+
|
|
7
|
+
write('UPDATE ');
|
|
8
|
+
traverse(table);
|
|
9
|
+
write('\n');
|
|
10
|
+
traverse(set);
|
|
11
|
+
|
|
12
|
+
if (where) {
|
|
13
|
+
write('\n');
|
|
14
|
+
traverse(where);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VALUES (:x)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {StringLiteral} from './string-literal.js';
|
|
2
|
+
import {ExpressionStatement} from './expression-statement.js';
|
|
3
|
+
import {CallExpression} from './call-expression/call-expression.js';
|
|
4
|
+
import {ArrayExpression} from './array-expression.js';
|
|
5
|
+
import {BinaryExpression} from './binary-expression.js';
|
|
6
|
+
import {LogicalExpression} from './logical-expression.js';
|
|
7
|
+
import {AssignmentExpression} from './assignment-expression.js';
|
|
8
|
+
|
|
9
|
+
export const visitors = {
|
|
10
|
+
ArrayExpression,
|
|
11
|
+
CallExpression,
|
|
12
|
+
StringLiteral,
|
|
13
|
+
ExpressionStatement,
|
|
14
|
+
BinaryExpression,
|
|
15
|
+
LogicalExpression,
|
|
16
|
+
AssignmentExpression,
|
|
17
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "happy-sql",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "sql to js ast parser and printer",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./lib/happy-sql.js"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"happy-sql": "bin/happy-sql.js"
|
|
11
|
+
},
|
|
12
|
+
"imports": {
|
|
13
|
+
"#happy-sql": "./lib/happy-sql.js",
|
|
14
|
+
"#parser": "./lib/parser/parser.js",
|
|
15
|
+
"#parser/test": "./test/create-parser-test.js",
|
|
16
|
+
"#parser/clause": "./lib/parser/visitors/clause/index.js",
|
|
17
|
+
"#parser/clause/select": "./lib/parser/visitors/clause/select/select.js",
|
|
18
|
+
"#parser/clause/from": "./lib/parser/visitors/clause/from/from.js",
|
|
19
|
+
"#parser/clause/where": "./lib/parser/visitors/clause/where/where.js",
|
|
20
|
+
"#parser/clause/insert": "./lib/parser/visitors/clause/insert/insert.js",
|
|
21
|
+
"#parser/clause/update": "./lib/parser/visitors/clause/update/update.js",
|
|
22
|
+
"#parser/clause/delete": "./lib/parser/visitors/clause/delete/delete.js",
|
|
23
|
+
"#printer": "./lib/printer/printer.js",
|
|
24
|
+
"#printer/test": "./test/create-printer-test.js"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "madrun test",
|
|
28
|
+
"coverage": "madrun coverage",
|
|
29
|
+
"report": "madrun report",
|
|
30
|
+
"lint": "madrun lint",
|
|
31
|
+
"fix:lint": "madrun fix:lint",
|
|
32
|
+
"watcher": "madrun watcher",
|
|
33
|
+
"watch:test": "madrun watch:test",
|
|
34
|
+
"watch:lint": "madrun watch:lint",
|
|
35
|
+
"watch:tape": "madrun watch:tape",
|
|
36
|
+
"watch:coverage:base": "madrun watch:coverage:base",
|
|
37
|
+
"watch:coverage:tape": "madrun watch:coverage:tape",
|
|
38
|
+
"watch:coverage": "madrun watch:coverage"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/coderaiser/happy-sql.git"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"store",
|
|
46
|
+
"variable",
|
|
47
|
+
"functional"
|
|
48
|
+
],
|
|
49
|
+
"author": "coderaiser <mnemonic.enemy@gmail.com> (http://coderaiser.github.io/)",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/coderaiser/happy-sql/issues"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/coderaiser/happy-sql",
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@putout/plugin-printer": "^8.17.0",
|
|
60
|
+
"@putout/test": "^15.4.0",
|
|
61
|
+
"eslint": "^10.0.3",
|
|
62
|
+
"eslint-plugin-node": "^11.0.0",
|
|
63
|
+
"eslint-plugin-putout": "^31.1.1",
|
|
64
|
+
"just-kebab-case": "^4.2.0",
|
|
65
|
+
"madrun": "^13.0.1",
|
|
66
|
+
"montag": "^2.0.1",
|
|
67
|
+
"nodemon": "^3.1.11",
|
|
68
|
+
"putout": "^42.2.3",
|
|
69
|
+
"redlint": "^6.9.2",
|
|
70
|
+
"superc8": "^12.7.0",
|
|
71
|
+
"supertape": "^13.2.0",
|
|
72
|
+
"try-catch": "^4.0.9"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"@putout/babel": "^5.7.3",
|
|
76
|
+
"@putout/operator-json": "^3.4.0",
|
|
77
|
+
"@putout/printer": "^18.17.0",
|
|
78
|
+
"redstd": "^1.0.2",
|
|
79
|
+
"sql-parser-cst": "^0.42.1"
|
|
80
|
+
}
|
|
81
|
+
}
|