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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/bin/convert.js +18 -0
  4. package/bin/happy-sql.js +10 -0
  5. package/lib/happy-sql.js +11 -0
  6. package/lib/parser/fixture/select-fix.js +3 -0
  7. package/lib/parser/fixture/select.sql +1 -0
  8. package/lib/parser/parser.js +24 -0
  9. package/lib/parser/visitors/clause/delete/delete.js +18 -0
  10. package/lib/parser/visitors/clause/delete/fixture/delete-fix.js +3 -0
  11. package/lib/parser/visitors/clause/delete/fixture/delete.sql +1 -0
  12. package/lib/parser/visitors/clause/from/fixture/from-fix.js +3 -0
  13. package/lib/parser/visitors/clause/from/fixture/from.sql +1 -0
  14. package/lib/parser/visitors/clause/from/from.js +7 -0
  15. package/lib/parser/visitors/clause/index.js +21 -0
  16. package/lib/parser/visitors/clause/insert/fixture/insert-fix.js +3 -0
  17. package/lib/parser/visitors/clause/insert/fixture/insert-multi-fix.js +3 -0
  18. package/lib/parser/visitors/clause/insert/fixture/insert-multi.sql +1 -0
  19. package/lib/parser/visitors/clause/insert/fixture/insert-number-fix.js +3 -0
  20. package/lib/parser/visitors/clause/insert/fixture/insert-number.sql +1 -0
  21. package/lib/parser/visitors/clause/insert/fixture/insert-string-fix.js +3 -0
  22. package/lib/parser/visitors/clause/insert/fixture/insert-string.sql +1 -0
  23. package/lib/parser/visitors/clause/insert/fixture/insert.sql +1 -0
  24. package/lib/parser/visitors/clause/insert/insert.js +70 -0
  25. package/lib/parser/visitors/clause/on-conflict/fixture/on-conflict-fix.js +3 -0
  26. package/lib/parser/visitors/clause/on-conflict/fixture/on-conflict-nothing-fix.js +3 -0
  27. package/lib/parser/visitors/clause/on-conflict/fixture/on-conflict-nothing.sql +1 -0
  28. package/lib/parser/visitors/clause/on-conflict/fixture/on-conflict.sql +1 -0
  29. package/lib/parser/visitors/clause/select/fixture/select-alias-fix.js +3 -0
  30. package/lib/parser/visitors/clause/select/fixture/select-alias.sql +1 -0
  31. package/lib/parser/visitors/clause/select/fixture/select-fix.js +3 -0
  32. package/lib/parser/visitors/clause/select/fixture/select-number-fix.js +3 -0
  33. package/lib/parser/visitors/clause/select/fixture/select-number.sql +1 -0
  34. package/lib/parser/visitors/clause/select/fixture/select.sql +1 -0
  35. package/lib/parser/visitors/clause/select/select.js +55 -0
  36. package/lib/parser/visitors/clause/update/fixture/update-fix.js +3 -0
  37. package/lib/parser/visitors/clause/update/fixture/update-no-where-fix.js +3 -0
  38. package/lib/parser/visitors/clause/update/fixture/update-no-where.sql +1 -0
  39. package/lib/parser/visitors/clause/update/fixture/update-string-fix.js +3 -0
  40. package/lib/parser/visitors/clause/update/fixture/update-string.sql +1 -0
  41. package/lib/parser/visitors/clause/update/fixture/update.sql +1 -0
  42. package/lib/parser/visitors/clause/update/update.js +45 -0
  43. package/lib/parser/visitors/clause/where/fixture/where-fix.js +3 -0
  44. package/lib/parser/visitors/clause/where/fixture/where-number-fix.js +3 -0
  45. package/lib/parser/visitors/clause/where/fixture/where-number.sql +1 -0
  46. package/lib/parser/visitors/clause/where/fixture/where-or-fix.js +3 -0
  47. package/lib/parser/visitors/clause/where/fixture/where-or.sql +1 -0
  48. package/lib/parser/visitors/clause/where/fixture/where.sql +1 -0
  49. package/lib/parser/visitors/clause/where/where.js +41 -0
  50. package/lib/printer/fixture/select-fix.sql +2 -0
  51. package/lib/printer/printer.js +8 -0
  52. package/lib/printer/visitors/array-expression.js +11 -0
  53. package/lib/printer/visitors/assignment-expression.js +5 -0
  54. package/lib/printer/visitors/binary-expression.js +5 -0
  55. package/lib/printer/visitors/call-expression/call-expression.js +10 -0
  56. package/lib/printer/visitors/call-expression/clauses/clauses.js +23 -0
  57. package/lib/printer/visitors/call-expression/clauses/delete/delete.js +13 -0
  58. package/lib/printer/visitors/call-expression/clauses/delete/fixture/delete-fix.sql +2 -0
  59. package/lib/printer/visitors/call-expression/clauses/from/fixture/from-fix.sql +2 -0
  60. package/lib/printer/visitors/call-expression/clauses/from/from.js +5 -0
  61. package/lib/printer/visitors/call-expression/clauses/insert/fixture/insert-fix.sql +1 -0
  62. package/lib/printer/visitors/call-expression/clauses/insert/insert.js +9 -0
  63. package/lib/printer/visitors/call-expression/clauses/into/fixture/into-fix.sql +1 -0
  64. package/lib/printer/visitors/call-expression/clauses/into/into.js +26 -0
  65. package/lib/printer/visitors/call-expression/clauses/on-conflict/fixture/on-conflict-fix.sql +1 -0
  66. package/lib/printer/visitors/call-expression/clauses/on-conflict/on-conflict.js +29 -0
  67. package/lib/printer/visitors/call-expression/clauses/select/fixture/select-fix.sql +2 -0
  68. package/lib/printer/visitors/call-expression/clauses/select/select.js +19 -0
  69. package/lib/printer/visitors/call-expression/clauses/set/fixture/set-fix.sql +1 -0
  70. package/lib/printer/visitors/call-expression/clauses/set/set.js +10 -0
  71. package/lib/printer/visitors/call-expression/clauses/update/fixture/update-fix.sql +3 -0
  72. package/lib/printer/visitors/call-expression/clauses/update/update.js +16 -0
  73. package/lib/printer/visitors/call-expression/clauses/values/fixture/values-fix.sql +1 -0
  74. package/lib/printer/visitors/call-expression/clauses/values/values.js +12 -0
  75. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-fix.sql +3 -0
  76. package/lib/printer/visitors/call-expression/clauses/where/where.js +5 -0
  77. package/lib/printer/visitors/expression-statement.js +3 -0
  78. package/lib/printer/visitors/index.js +17 -0
  79. package/lib/printer/visitors/logical-expression.js +6 -0
  80. package/lib/printer/visitors/string-literal.js +8 -0
  81. 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
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from 'node:process';
4
+ import {readStdin} from 'redstd';
5
+ import {convert} from './convert.js';
6
+
7
+ const {stdout} = process;
8
+ const source = await readStdin();
9
+
10
+ stdout.write(convert(source));
@@ -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,3 @@
1
+ [
2
+ select('*', from(users)),
3
+ ];
@@ -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,3 @@
1
+ [
2
+ delete(from(t), where(id === ':id')),
3
+ ];
@@ -0,0 +1 @@
1
+ DELETE FROM t WHERE id = :id
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(users)),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM users
@@ -0,0 +1,7 @@
1
+ import {types} from '@putout/babel';
2
+
3
+ const {identifier, callExpression} = types;
4
+
5
+ export const convertFrom = (node) => callExpression(identifier('from'), [
6
+ identifier(node.expr.name),
7
+ ]);
@@ -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,3 @@
1
+ [
2
+ insert(into(t, x, values(':x'))),
3
+ ];
@@ -0,0 +1,3 @@
1
+ [
2
+ insert(into(t, [x, y], values(':x', ':y'))),
3
+ ];
@@ -0,0 +1 @@
1
+ INSERT INTO t (x, y) VALUES (:x, :y)
@@ -0,0 +1,3 @@
1
+ [
2
+ insert(into(t, x, values(':x')), onConflict(x, y, set(count === 1))),
3
+ ];
@@ -0,0 +1 @@
1
+ INSERT INTO t (x) VALUES (:x) ON CONFLICT (x, y) DO UPDATE SET count = 1
@@ -0,0 +1,3 @@
1
+ [
2
+ insert(into(t, x, values(':x')), onConflict(x, set(name === 'hello'))),
3
+ ];
@@ -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,3 @@
1
+ [
2
+ insert(into(t, x, values(':x')), onConflict(x, set(x === ':x'))),
3
+ ];
@@ -0,0 +1,3 @@
1
+ [
2
+ insert(into(t, x, values(':x')), onConflict(nothing)),
3
+ ];
@@ -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,3 @@
1
+ [
2
+ select(message = 'msg', num = 42, from(t)),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT 'msg' AS message, 42 AS num FROM t
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(users)),
3
+ ];
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(num === 42)),
3
+ ];
@@ -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,3 @@
1
+ [
2
+ update(t, set(x === ':x'), where(id === ':id')),
3
+ ];
@@ -0,0 +1,3 @@
1
+ [
2
+ update(t, set(x === ':x')),
3
+ ];
@@ -0,0 +1 @@
1
+ UPDATE t SET x = :x
@@ -0,0 +1,3 @@
1
+ [
2
+ update(t, set(name === 'hello'), where(id === ':id')),
3
+ ];
@@ -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,3 @@
1
+ [
2
+ select('*', from(users), where(id === ':id')),
3
+ ];
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(id === 42)),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM t WHERE id = 42
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(x === ':x' || y === ':y')),
3
+ ];
@@ -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,2 @@
1
+ SELECT *
2
+ FROM users
@@ -0,0 +1,8 @@
1
+ import {print} from '@putout/printer';
2
+ import {visitors} from './visitors/index.js';
3
+
4
+ export const printSql = (ast) => {
5
+ return print(ast, {
6
+ visitors,
7
+ });
8
+ };
@@ -0,0 +1,11 @@
1
+ export const ArrayExpression = (path, {write, traverse}) => {
2
+ const elements = path.get('elements');
3
+ const n = elements.length - 1;
4
+
5
+ for (const [i, el] of elements.entries()) {
6
+ traverse(el);
7
+
8
+ if (i < n)
9
+ write('\n');
10
+ }
11
+ };
@@ -0,0 +1,5 @@
1
+ export const AssignmentExpression = (path, {write, traverse}) => {
2
+ traverse(path.get('right'));
3
+ write(' AS ');
4
+ traverse(path.get('left'));
5
+ };
@@ -0,0 +1,5 @@
1
+ export const BinaryExpression = (path, {write, traverse}) => {
2
+ traverse(path.get('left'));
3
+ write(' = ');
4
+ traverse(path.get('right'));
5
+ };
@@ -0,0 +1,10 @@
1
+ import {clauses} from './clauses/clauses.js';
2
+
3
+ export const CallExpression = (path, printer) => {
4
+ const {name} = path.node.callee;
5
+
6
+ if (!clauses[name])
7
+ throw Error(`${name} not supported yet`);
8
+
9
+ clauses[name](path, printer);
10
+ };
@@ -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,2 @@
1
+ DELETE FROM t
2
+ WHERE id = :id
@@ -0,0 +1,5 @@
1
+ export const from = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+ write('FROM ');
4
+ traverse(args[0]);
5
+ };
@@ -0,0 +1 @@
1
+ INSERT INTO t (x) VALUES (:x)
@@ -0,0 +1,9 @@
1
+ export const insert = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+ traverse(args[0]);
4
+
5
+ if (args[1]) {
6
+ write(' ');
7
+ traverse(args[1]);
8
+ }
9
+ };
@@ -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,10 @@
1
+ export const set = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+ write('SET ');
4
+ for (let i = 0; i < args.length; i++) {
5
+ if (i > 0)
6
+ write(', ');
7
+
8
+ traverse(args[i]);
9
+ }
10
+ };
@@ -0,0 +1,3 @@
1
+ UPDATE t
2
+ SET x = :x
3
+ WHERE id = :id
@@ -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,12 @@
1
+ export const values = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+ write('VALUES (');
4
+ for (let i = 0; i < args.length; i++) {
5
+ if (i > 0)
6
+ write(', ');
7
+
8
+ traverse(args[i]);
9
+ }
10
+
11
+ write(')');
12
+ };
@@ -0,0 +1,3 @@
1
+ SELECT *
2
+ FROM users
3
+ WHERE id = :id
@@ -0,0 +1,5 @@
1
+ export const where = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+ write('WHERE ');
4
+ traverse(args[0]);
5
+ };
@@ -0,0 +1,3 @@
1
+ export const ExpressionStatement = (path, {print}) => {
2
+ print('__expression');
3
+ };
@@ -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
+ };
@@ -0,0 +1,6 @@
1
+ export const LogicalExpression = (path, {write, traverse}) => {
2
+ const op = path.node.operator === '&&' ? 'AND' : 'OR';
3
+ traverse(path.get('left'));
4
+ write(`\n${op} `);
5
+ traverse(path.get('right'));
6
+ };
@@ -0,0 +1,8 @@
1
+ export const StringLiteral = (path, {write}) => {
2
+ if (path.node.value === '*' || path.node.value.startsWith(':')) {
3
+ write(path.node.value);
4
+ return;
5
+ }
6
+
7
+ write(`'${path.node.value}'`);
8
+ };
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
+ }