happy-sql 1.5.0 → 1.5.2

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 (57) hide show
  1. package/ChangeLog +11 -0
  2. package/lib/parser/visitors/clause/from/fixture/join-on-in-fix.js +3 -0
  3. package/lib/parser/visitors/clause/from/fixture/join-on-in.sql +1 -0
  4. package/lib/parser/visitors/clause/from/fixture/join-on-is-not-null-fix.js +3 -0
  5. package/lib/parser/visitors/clause/from/fixture/join-on-is-not-null.sql +1 -0
  6. package/lib/parser/visitors/clause/from/fixture/join-on-is-null-fix.js +3 -0
  7. package/lib/parser/visitors/clause/from/fixture/join-on-is-null.sql +1 -0
  8. package/lib/parser/visitors/clause/from/fixture/join-on-like-fix.js +3 -0
  9. package/lib/parser/visitors/clause/from/fixture/join-on-like.sql +1 -0
  10. package/lib/parser/visitors/clause/from/fixture/join-on-not-eq-fix.js +6 -0
  11. package/lib/parser/visitors/clause/from/fixture/join-on-not-eq.sql +1 -0
  12. package/lib/parser/visitors/clause/from/fixture/join-on-number-fix.js +6 -0
  13. package/lib/parser/visitors/clause/from/fixture/join-on-number.sql +1 -0
  14. package/lib/parser/visitors/clause/from/fixture/join-on-or-fix.js +6 -0
  15. package/lib/parser/visitors/clause/from/fixture/join-on-or.sql +1 -0
  16. package/lib/parser/visitors/clause/from/fixture/join-on-param-fix.js +6 -0
  17. package/lib/parser/visitors/clause/from/fixture/join-on-param.sql +1 -0
  18. package/lib/parser/visitors/clause/from/from.js +68 -1
  19. package/lib/parser/visitors/clause/insert/insert.js +2 -4
  20. package/lib/parser/visitors/clause/operand.js +8 -0
  21. package/lib/parser/visitors/clause/select/fixture/select-param-fix.js +3 -0
  22. package/lib/parser/visitors/clause/select/fixture/select-param.sql +1 -0
  23. package/lib/parser/visitors/clause/select/select.js +3 -0
  24. package/lib/parser/visitors/clause/where/fixture/where-compare-fix.js +3 -0
  25. package/lib/parser/visitors/clause/where/fixture/where-compare.sql +1 -0
  26. package/lib/parser/visitors/clause/where/fixture/where-gte-fix.js +3 -0
  27. package/lib/parser/visitors/clause/where/fixture/where-gte.sql +1 -0
  28. package/lib/parser/visitors/clause/where/fixture/where-in-fix.js +3 -0
  29. package/lib/parser/visitors/clause/where/fixture/where-in.sql +1 -0
  30. package/lib/parser/visitors/clause/where/fixture/where-is-not-null-fix.js +3 -0
  31. package/lib/parser/visitors/clause/where/fixture/where-is-not-null.sql +1 -0
  32. package/lib/parser/visitors/clause/where/fixture/where-is-null-fix.js +3 -0
  33. package/lib/parser/visitors/clause/where/fixture/where-is-null.sql +1 -0
  34. package/lib/parser/visitors/clause/where/fixture/where-like-fix.js +3 -0
  35. package/lib/parser/visitors/clause/where/fixture/where-like.sql +1 -0
  36. package/lib/parser/visitors/clause/where/fixture/where-not-eq-fix.js +3 -0
  37. package/lib/parser/visitors/clause/where/fixture/where-not-eq.sql +1 -0
  38. package/lib/parser/visitors/clause/where/where.js +59 -4
  39. package/lib/printer/visitors/array-expression.js +1 -1
  40. package/lib/printer/visitors/binary-expression.js +9 -1
  41. package/lib/printer/visitors/call-expression/clauses/clauses.js +7 -0
  42. package/lib/printer/visitors/call-expression/clauses/in-list/in-list.js +15 -0
  43. package/lib/printer/visitors/call-expression/clauses/is-null/is-null.js +11 -0
  44. package/lib/printer/visitors/call-expression/clauses/like/like.js +6 -0
  45. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-gte-fix.sql +3 -0
  46. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-gte.js +3 -0
  47. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-in-fix.sql +3 -0
  48. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-in.js +3 -0
  49. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-is-not-null-fix.sql +3 -0
  50. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-is-not-null.js +3 -0
  51. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-is-null-fix.sql +3 -0
  52. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-is-null.js +3 -0
  53. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-like-fix.sql +3 -0
  54. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-like.js +3 -0
  55. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-ne-fix.sql +3 -0
  56. package/lib/printer/visitors/call-expression/clauses/where/fixture/where-ne.js +3 -0
  57. package/package.json +1 -1
package/ChangeLog CHANGED
@@ -1,3 +1,14 @@
1
+ 2026.08.15, v1.5.2
2
+
3
+ feature:
4
+ - 6a7d43a happy-sql: improve
5
+ - f2f0f0e printer: array expression
6
+
7
+ 2026.08.15, v1.5.1
8
+
9
+ feature:
10
+ - 3f71b12 convertOperand: boolean
11
+
1
12
  2026.08.15, v1.5.0
2
13
 
3
14
  fix:
@@ -0,0 +1,3 @@
1
+ [
2
+ select(u.id, from(t, join(u, on(inList(u.id, 1, 2, 3))))),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT u.id FROM t JOIN u ON u.id IN (1, 2, 3)
@@ -0,0 +1,3 @@
1
+ [
2
+ select(u.id, from(t, join(u, on(isNotNull(u.x))))),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT u.id FROM t JOIN u ON u.x IS NOT NULL
@@ -0,0 +1,3 @@
1
+ [
2
+ select(u.id, from(t, join(u, on(isNull(u.x))))),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT u.id FROM t JOIN u ON u.x IS NULL
@@ -0,0 +1,3 @@
1
+ [
2
+ select(u.id, from(t, join(u, on(like(u.x, ':p'))))),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT u.id FROM t JOIN u ON u.x LIKE :p
@@ -0,0 +1,6 @@
1
+ [
2
+ select(bin.id, from(as(
3
+ BinaryExpression,
4
+ bin,
5
+ ), join(as(Identifier, arg_id), on(arg_id.parent_id === bin.id && arg_id.id !== bin.id)))),
6
+ ];
@@ -0,0 +1 @@
1
+ SELECT bin.id FROM BinaryExpression bin JOIN Identifier arg_id ON arg_id.parent_id = bin.id AND arg_id.id != bin.id
@@ -0,0 +1,6 @@
1
+ [
2
+ select(bin.id, from(as(
3
+ BinaryExpression,
4
+ bin,
5
+ ), join(as(Identifier, nan_id), on(nan_id.parent_id === 1)))),
6
+ ];
@@ -0,0 +1 @@
1
+ SELECT bin.id FROM BinaryExpression bin JOIN Identifier nan_id ON nan_id.parent_id = 1
@@ -0,0 +1,6 @@
1
+ [
2
+ select(bin.id, from(as(
3
+ BinaryExpression,
4
+ bin,
5
+ ), join(as(Identifier, nan_id), on(nan_id.parent_id === bin.id || nan_id.name === 'NaN')))),
6
+ ];
@@ -0,0 +1 @@
1
+ SELECT bin.id FROM BinaryExpression bin JOIN Identifier nan_id ON nan_id.parent_id = bin.id OR nan_id.name = 'NaN'
@@ -0,0 +1,6 @@
1
+ [
2
+ select(bin.id, from(as(
3
+ BinaryExpression,
4
+ bin,
5
+ ), join(as(Identifier, nan_id), on(nan_id.parent_id === ':param_id')))),
6
+ ];
@@ -0,0 +1 @@
1
+ SELECT bin.id FROM BinaryExpression bin JOIN Identifier nan_id ON nan_id.parent_id = :param_id
@@ -1,5 +1,7 @@
1
1
  import {types} from '@putout/babel';
2
2
 
3
+ const isString = (a) => typeof a === 'string';
4
+
3
5
  const {
4
6
  identifier,
5
7
  callExpression,
@@ -8,6 +10,8 @@ const {
8
10
  logicalExpression,
9
11
  stringLiteral,
10
12
  numericLiteral,
13
+ booleanLiteral,
14
+ nullLiteral,
11
15
  } = types;
12
16
 
13
17
  const convertOperand = (node) => {
@@ -23,9 +27,72 @@ const convertOperand = (node) => {
23
27
  if (node.type === 'string_literal')
24
28
  return stringLiteral(node.value);
25
29
 
30
+ if (node.type === 'boolean_literal')
31
+ return booleanLiteral(node.value);
32
+
33
+ if (node.type === 'null_literal')
34
+ return nullLiteral(node.value);
35
+
26
36
  return identifier(node.name);
27
37
  };
28
38
 
39
+ const opName = (op) => {
40
+ if (Array.isArray(op))
41
+ return op
42
+ .map((k) => k.name)
43
+ .join(' ');
44
+
45
+ if (isString(op))
46
+ return op;
47
+
48
+ return op?.name ?? '';
49
+ };
50
+
51
+ const SQL_TO_JS_OP = {
52
+ '=': '===',
53
+ '!=': '!==',
54
+ '>=': '>=',
55
+ '<=': '<=',
56
+ '>': '>',
57
+ '<': '<',
58
+ };
59
+
60
+ const convertBinary = (node) => {
61
+ const op = opName(node.operator);
62
+
63
+ if (op === 'LIKE')
64
+ return callExpression(identifier('like'), [
65
+ convertOperand(node.left),
66
+ convertOperand(node.right),
67
+ ]);
68
+
69
+ if (op === 'IS')
70
+ return callExpression(identifier('isNull'), [
71
+ convertOperand(node.left),
72
+ ]);
73
+
74
+ if (op === 'IS NOT')
75
+ return callExpression(identifier('isNotNull'), [
76
+ convertOperand(node.left),
77
+ ]);
78
+
79
+ if (op === 'IN') {
80
+ const items = node.right.expr.items.map(convertOperand);
81
+ return callExpression(identifier('inList'), [
82
+ convertOperand(node.left),
83
+ ...items,
84
+ ]);
85
+ }
86
+
87
+ const jsOp = SQL_TO_JS_OP[op] ?? '===';
88
+
89
+ return binaryExpression(
90
+ jsOp,
91
+ convertOperand(node.left),
92
+ convertOperand(node.right),
93
+ );
94
+ };
95
+
29
96
  const convertCondition = (node) => {
30
97
  if (node.operator?.name === 'AND')
31
98
  return logicalExpression('&&', convertCondition(node.left), convertCondition(node.right));
@@ -33,7 +100,7 @@ const convertCondition = (node) => {
33
100
  if (node.operator?.name === 'OR')
34
101
  return logicalExpression('||', convertCondition(node.left), convertCondition(node.right));
35
102
 
36
- return binaryExpression('===', convertOperand(node.left), convertOperand(node.right));
103
+ return convertBinary(node);
37
104
  };
38
105
 
39
106
  const convertOn = (spec) => callExpression(identifier('on'), [
@@ -1,4 +1,5 @@
1
1
  import {types} from '@putout/babel';
2
+ import {convertOperand} from '#parser/clause/operand';
2
3
  import {convertSet} from '#parser/clause/set';
3
4
  import {convertSelect} from '#parser/clause/select';
4
5
  import {findClause} from '#parser/clause/find';
@@ -38,10 +39,7 @@ const convertSource = (vc) => {
38
39
  return convertSelect(vc);
39
40
 
40
41
  const vals = vc.values.items[0].expr.items;
41
- const valArgs = [];
42
-
43
- for (const p of vals)
44
- valArgs.push(stringLiteral(p.text));
42
+ const valArgs = vals.map(convertOperand);
45
43
 
46
44
  return callExpression(identifier('values'), valArgs);
47
45
  };
@@ -4,6 +4,8 @@ const {
4
4
  identifier,
5
5
  stringLiteral,
6
6
  numericLiteral,
7
+ booleanLiteral,
8
+ nullLiteral,
7
9
  memberExpression,
8
10
  } = types;
9
11
 
@@ -20,5 +22,11 @@ export const convertOperand = (node) => {
20
22
  if (node.type === 'string_literal')
21
23
  return stringLiteral(node.value);
22
24
 
25
+ if (node.type === 'boolean_literal')
26
+ return booleanLiteral(node.value);
27
+
28
+ if (node.type === 'null_literal')
29
+ return nullLiteral(node.value);
30
+
23
31
  return identifier(node.name);
24
32
  };
@@ -0,0 +1,3 @@
1
+ [
2
+ select(':arg_name', id, from(t)),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT :arg_name, id FROM t
@@ -29,6 +29,9 @@ const convertColumn = (column) => {
29
29
  if (column.type === 'member_expr')
30
30
  return memberExpression(identifier(column.object.name), identifier(column.property.name));
31
31
 
32
+ if (column.type === 'parameter')
33
+ return stringLiteral(column.text);
34
+
32
35
  return identifier(column.name);
33
36
  };
34
37
 
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(x > 1 && y < 2)),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM t WHERE x > 1 AND y < 2
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(x >= 1)),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM t WHERE x >= 1
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(inList(x, 1, 2, 3))),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM t WHERE x IN (1, 2, 3)
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(isNotNull(x))),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM t WHERE x IS NOT NULL
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(isNull(x))),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM t WHERE x IS NULL
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(like(x, ':pattern'))),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM t WHERE x LIKE :pattern
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(x !== ':y')),
3
+ ];
@@ -0,0 +1 @@
1
+ SELECT * FROM t WHERE x != :y
@@ -1,18 +1,73 @@
1
1
  import {types} from '@putout/babel';
2
2
  import {convertOperand} from '#parser/clause/operand';
3
3
 
4
+ const isString = (a) => typeof a === 'string';
5
+
4
6
  const {
5
7
  logicalExpression,
6
8
  identifier,
7
9
  callExpression,
10
+ binaryExpression,
8
11
  } = types;
9
12
 
13
+ const opName = (op) => {
14
+ if (Array.isArray(op))
15
+ return op
16
+ .map((k) => k.name)
17
+ .join(' ');
18
+
19
+ if (isString(op))
20
+ return op;
21
+
22
+ return op?.name ?? '';
23
+ };
24
+
25
+ const SQL_TO_JS_OP = {
26
+ '=': '===',
27
+ '!=': '!==',
28
+ '>=': '>=',
29
+ '<=': '<=',
30
+ '>': '>',
31
+ '<': '<',
32
+ };
33
+
10
34
  const convertAnd = (node) => logicalExpression('&&', convertCondition(node.left), convertCondition(node.right));
11
35
  const convertOr = (node) => logicalExpression('||', convertCondition(node.left), convertCondition(node.right));
12
36
 
13
- const convertEq = (node) => {
14
- const {binaryExpression} = types;
15
- return binaryExpression('===', convertOperand(node.left), convertOperand(node.right));
37
+ const convertBinary = (node) => {
38
+ const op = opName(node.operator);
39
+
40
+ if (op === 'LIKE')
41
+ return callExpression(identifier('like'), [
42
+ convertOperand(node.left),
43
+ convertOperand(node.right),
44
+ ]);
45
+
46
+ if (op === 'IS')
47
+ return callExpression(identifier('isNull'), [
48
+ convertOperand(node.left),
49
+ ]);
50
+
51
+ if (op === 'IS NOT')
52
+ return callExpression(identifier('isNotNull'), [
53
+ convertOperand(node.left),
54
+ ]);
55
+
56
+ if (op === 'IN') {
57
+ const items = node.right.expr.items.map(convertOperand);
58
+ return callExpression(identifier('inList'), [
59
+ convertOperand(node.left),
60
+ ...items,
61
+ ]);
62
+ }
63
+
64
+ const jsOp = SQL_TO_JS_OP[op] ?? '===';
65
+
66
+ return binaryExpression(
67
+ jsOp,
68
+ convertOperand(node.left),
69
+ convertOperand(node.right),
70
+ );
16
71
  };
17
72
 
18
73
  const convertCondition = (node) => {
@@ -22,7 +77,7 @@ const convertCondition = (node) => {
22
77
  if (node.operator?.name === 'OR')
23
78
  return convertOr(node);
24
79
 
25
- return convertEq(node);
80
+ return convertBinary(node);
26
81
  };
27
82
 
28
83
  export const convertWhere = (node) => callExpression(identifier('where'), [
@@ -6,6 +6,6 @@ export const ArrayExpression = (path, {write, traverse}) => {
6
6
  traverse(el);
7
7
 
8
8
  if (i < n)
9
- write('\n');
9
+ write(';\n');
10
10
  }
11
11
  };
@@ -1,5 +1,13 @@
1
+ const JS_TO_SQL_OP = {
2
+ '===': '=',
3
+ '!==': '!=',
4
+ };
5
+
1
6
  export const BinaryExpression = (path, {write, traverse}) => {
2
7
  traverse(path.get('left'));
3
- write(' = ');
8
+
9
+ const op = path.node.operator;
10
+
11
+ write(` ${JS_TO_SQL_OP[op] ?? op} `);
4
12
  traverse(path.get('right'));
5
13
  };
@@ -22,6 +22,9 @@ import {serial} from './serial/serial.js';
22
22
  import {primaryKey} from './primary-key/primary-key.js';
23
23
  import {lastInsertRowid} from './last-insert-rowid/last-insert-rowid.js';
24
24
  import {lastval} from './lastval/lastval.js';
25
+ import {like} from './like/like.js';
26
+ import {isNull, isNotNull} from './is-null/is-null.js';
27
+ import {inList} from './in-list/in-list.js';
25
28
 
26
29
  export const clauses = {
27
30
  join,
@@ -48,4 +51,8 @@ export const clauses = {
48
51
  primaryKey,
49
52
  lastInsertRowid,
50
53
  lastval,
54
+ like,
55
+ isNull,
56
+ isNotNull,
57
+ inList,
51
58
  };
@@ -0,0 +1,15 @@
1
+ export const inList = (path, {write, traverse}) => {
2
+ const [head, ...items] = path.get('arguments');
3
+
4
+ traverse(head);
5
+ write(' IN (');
6
+
7
+ for (let i = 0; i < items.length; i++) {
8
+ if (i > 0)
9
+ write(', ');
10
+
11
+ traverse(items[i]);
12
+ }
13
+
14
+ write(')');
15
+ };
@@ -0,0 +1,11 @@
1
+ export const isNull = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+ traverse(args[0]);
4
+ write(' IS NULL');
5
+ };
6
+
7
+ export const isNotNull = (path, {write, traverse}) => {
8
+ const args = path.get('arguments');
9
+ traverse(args[0]);
10
+ write(' IS NOT NULL');
11
+ };
@@ -0,0 +1,6 @@
1
+ export const like = (path, {write, traverse}) => {
2
+ const args = path.get('arguments');
3
+ traverse(args[0]);
4
+ write(' LIKE ');
5
+ traverse(args[1]);
6
+ };
@@ -0,0 +1,3 @@
1
+ SELECT *
2
+ FROM t
3
+ WHERE x >= 1
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(x >= 1)),
3
+ ];
@@ -0,0 +1,3 @@
1
+ SELECT *
2
+ FROM t
3
+ WHERE x IN (1, 2, 3)
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(inList(x, 1, 2, 3))),
3
+ ];
@@ -0,0 +1,3 @@
1
+ SELECT *
2
+ FROM t
3
+ WHERE x IS NOT NULL
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(isNotNull(x))),
3
+ ];
@@ -0,0 +1,3 @@
1
+ SELECT *
2
+ FROM t
3
+ WHERE x IS NULL
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(isNull(x))),
3
+ ];
@@ -0,0 +1,3 @@
1
+ SELECT *
2
+ FROM t
3
+ WHERE x LIKE :pattern
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(like(x, ':pattern'))),
3
+ ];
@@ -0,0 +1,3 @@
1
+ SELECT *
2
+ FROM t
3
+ WHERE x != :y
@@ -0,0 +1,3 @@
1
+ [
2
+ select('*', from(t), where(x !== ':y')),
3
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-sql",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "type": "module",
5
5
  "description": "sql to js ast parser and printer",
6
6
  "exports": {