@uwdata/mosaic-sql 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +2 -0
  2. package/dist/mosaic-sql.js +2242 -1064
  3. package/dist/mosaic-sql.min.js +1 -1
  4. package/dist/types/ast/aggregate.d.ts +70 -0
  5. package/dist/types/ast/between-op.d.ts +46 -0
  6. package/dist/types/ast/binary-op.d.ts +28 -0
  7. package/dist/types/ast/case.d.ts +68 -0
  8. package/dist/types/ast/cast.d.ts +21 -0
  9. package/dist/types/ast/column-param.d.ts +17 -0
  10. package/dist/types/ast/column-ref.d.ts +39 -0
  11. package/dist/types/ast/fragment.d.ts +14 -0
  12. package/dist/types/ast/from.d.ts +21 -0
  13. package/dist/types/ast/function.d.ts +21 -0
  14. package/dist/types/ast/in-op.d.ts +21 -0
  15. package/dist/types/ast/interval.d.ts +21 -0
  16. package/dist/types/ast/literal.d.ts +15 -0
  17. package/dist/types/ast/logical-op.d.ts +46 -0
  18. package/dist/types/ast/node.d.ts +24 -0
  19. package/dist/types/ast/order-by.d.ts +29 -0
  20. package/dist/types/ast/param.d.ts +19 -0
  21. package/dist/types/ast/query.d.ts +268 -0
  22. package/dist/types/ast/sample.d.ts +42 -0
  23. package/dist/types/ast/select.d.ts +22 -0
  24. package/dist/types/ast/table-ref.d.ts +25 -0
  25. package/dist/types/ast/unary-op.d.ts +39 -0
  26. package/dist/types/ast/verbatim.d.ts +9 -0
  27. package/dist/types/ast/window.d.ts +177 -0
  28. package/dist/types/ast/with.d.ts +22 -0
  29. package/dist/types/constants.d.ts +38 -0
  30. package/dist/types/functions/aggregate.d.ts +229 -0
  31. package/dist/types/functions/case.d.ts +15 -0
  32. package/dist/types/functions/cast.d.ts +26 -0
  33. package/dist/types/functions/column.d.ts +9 -0
  34. package/dist/types/functions/datetime.d.ts +44 -0
  35. package/dist/types/functions/literal.d.ts +16 -0
  36. package/dist/types/functions/numeric.d.ts +93 -0
  37. package/dist/types/functions/operators.d.ts +198 -0
  38. package/dist/types/functions/order-by.d.ts +17 -0
  39. package/dist/types/functions/spatial.d.ts +37 -0
  40. package/dist/types/functions/sql-template-tag.d.ts +16 -0
  41. package/dist/types/functions/string.d.ts +55 -0
  42. package/dist/types/functions/table-ref.d.ts +9 -0
  43. package/dist/types/functions/window.d.ts +87 -0
  44. package/dist/types/index-types.d.ts +2 -0
  45. package/dist/types/index.d.ts +53 -0
  46. package/dist/types/load/create.d.ts +8 -0
  47. package/dist/types/load/extension.d.ts +1 -0
  48. package/dist/types/load/load.d.ts +12 -0
  49. package/dist/types/load/sql-from.d.ts +11 -0
  50. package/dist/types/transforms/bin-1d.d.ts +14 -0
  51. package/dist/types/transforms/bin-2d.d.ts +18 -0
  52. package/dist/types/transforms/bin-linear-1d.d.ts +9 -0
  53. package/dist/types/transforms/bin-linear-2d.d.ts +18 -0
  54. package/dist/types/transforms/line-density.d.ts +23 -0
  55. package/dist/types/transforms/m4.d.ts +18 -0
  56. package/dist/types/transforms/scales.d.ts +1 -0
  57. package/dist/types/types.d.ts +59 -0
  58. package/dist/types/util/ast.d.ts +60 -0
  59. package/dist/types/util/function.d.ts +54 -0
  60. package/dist/types/util/string.d.ts +3 -0
  61. package/dist/types/util/type-check.d.ts +18 -0
  62. package/dist/types/visit/recurse.d.ts +28 -0
  63. package/dist/types/visit/rewrite.d.ts +10 -0
  64. package/dist/types/visit/visitors.d.ts +33 -0
  65. package/dist/types/visit/walk.d.ts +7 -0
  66. package/jsconfig.json +11 -0
  67. package/package.json +6 -4
  68. package/src/ast/aggregate.js +164 -0
  69. package/src/ast/between-op.js +75 -0
  70. package/src/ast/binary-op.js +40 -0
  71. package/src/ast/case.js +105 -0
  72. package/src/ast/cast.js +34 -0
  73. package/src/ast/column-param.js +29 -0
  74. package/src/ast/column-ref.js +72 -0
  75. package/src/ast/fragment.js +26 -0
  76. package/src/ast/from.js +40 -0
  77. package/src/ast/function.js +34 -0
  78. package/src/ast/in-op.js +33 -0
  79. package/src/ast/interval.js +33 -0
  80. package/src/ast/literal.js +55 -0
  81. package/src/ast/logical-op.js +67 -0
  82. package/src/ast/node.js +29 -0
  83. package/src/ast/order-by.js +48 -0
  84. package/src/ast/param.js +35 -0
  85. package/src/ast/query.js +578 -0
  86. package/src/ast/sample.js +53 -0
  87. package/src/ast/select.js +44 -0
  88. package/src/ast/table-ref.js +44 -0
  89. package/src/ast/unary-op.js +64 -0
  90. package/src/ast/verbatim.js +26 -0
  91. package/src/ast/window.js +290 -0
  92. package/src/ast/with.js +30 -0
  93. package/src/constants.js +44 -0
  94. package/src/functions/aggregate.js +335 -0
  95. package/src/functions/case.js +21 -0
  96. package/src/functions/cast.js +39 -0
  97. package/src/functions/column.js +20 -0
  98. package/src/functions/datetime.js +65 -0
  99. package/src/functions/literal.js +22 -0
  100. package/src/functions/numeric.js +139 -0
  101. package/src/functions/operators.js +298 -0
  102. package/src/functions/order-by.js +24 -0
  103. package/src/functions/spatial.js +56 -0
  104. package/src/functions/sql-template-tag.js +51 -0
  105. package/src/functions/string.js +82 -0
  106. package/src/functions/table-ref.js +14 -0
  107. package/src/functions/window.js +121 -0
  108. package/src/index-types.ts +2 -0
  109. package/src/index.js +57 -155
  110. package/src/load/create.js +10 -2
  111. package/src/load/load.js +4 -4
  112. package/src/load/sql-from.js +7 -6
  113. package/src/transforms/bin-1d.js +21 -0
  114. package/src/transforms/bin-2d.js +29 -0
  115. package/src/transforms/bin-linear-1d.js +26 -0
  116. package/src/transforms/bin-linear-2d.js +71 -0
  117. package/src/transforms/line-density.js +113 -0
  118. package/src/transforms/m4.js +38 -0
  119. package/src/{scales.js → transforms/scales.js} +31 -17
  120. package/src/types.ts +96 -0
  121. package/src/util/ast.js +96 -0
  122. package/src/util/function.js +78 -0
  123. package/src/util/string.js +16 -0
  124. package/src/util/type-check.js +29 -0
  125. package/src/visit/recurse.js +57 -0
  126. package/src/visit/rewrite.js +32 -0
  127. package/src/visit/visitors.js +108 -0
  128. package/src/visit/walk.js +30 -0
  129. package/tsconfig.json +12 -0
  130. package/src/Query.js +0 -593
  131. package/src/aggregates.js +0 -185
  132. package/src/cast.js +0 -19
  133. package/src/datetime.js +0 -31
  134. package/src/desc.js +0 -13
  135. package/src/expression.js +0 -170
  136. package/src/functions.js +0 -25
  137. package/src/literal.js +0 -6
  138. package/src/operators.js +0 -54
  139. package/src/ref.js +0 -109
  140. package/src/repeat.js +0 -3
  141. package/src/spatial.js +0 -10
  142. package/src/to-sql.js +0 -52
  143. package/src/windows.js +0 -239
@@ -1,6 +1,9 @@
1
- import { epoch_ms } from './datetime.js';
2
- import { sql } from './expression.js';
3
- import { asColumn } from './ref.js';
1
+ import { LiteralNode } from '../ast/literal.js';
2
+ import { epoch_ms } from '../functions/datetime.js';
3
+ import { literal } from '../functions/literal.js';
4
+ import { abs, exp, ln, log, sign, sqrt } from '../functions/numeric.js';
5
+ import { add, div, mul, pow, sub } from '../functions/operators.js';
6
+ import { asNode } from '../util/ast.js';
4
7
 
5
8
  const identity = x => x;
6
9
 
@@ -8,7 +11,7 @@ function scaleLinear() {
8
11
  return {
9
12
  apply: identity,
10
13
  invert: identity,
11
- sqlApply: asColumn,
14
+ sqlApply: asNode,
12
15
  sqlInvert: identity
13
16
  };
14
17
  }
@@ -18,23 +21,23 @@ function scaleLog({ base = null } = {}) {
18
21
  return {
19
22
  apply: Math.log,
20
23
  invert: Math.exp,
21
- sqlApply: c => sql`LN(${asColumn(c)})`,
22
- sqlInvert: c => sql`EXP(${c})`
24
+ sqlApply: c => ln(c),
25
+ sqlInvert: c => exp(c)
23
26
  };
24
27
  } else if (base === 10) {
25
28
  return {
26
29
  apply: Math.log10,
27
30
  invert: x => Math.pow(10, x),
28
- sqlApply: c => sql`LOG(${asColumn(c)})`,
29
- sqlInvert: c => sql`POW(10, ${c})`
31
+ sqlApply: c => log(c),
32
+ sqlInvert: c => pow(10, c)
30
33
  };
31
34
  } else {
32
35
  const b = +base;
33
36
  return {
34
37
  apply: x => Math.log(x) / Math.log(b),
35
38
  invert: x => Math.pow(b, x),
36
- sqlApply: c => sql`LN(${asColumn(c)}) / LN(${b})`,
37
- sqlInvert: c => sql`POW(${b}, ${c})`
39
+ sqlApply: c => div(ln(c), ln(b)),
40
+ sqlInvert: c => pow(b, c)
38
41
  };
39
42
  }
40
43
  }
@@ -44,8 +47,8 @@ function scaleSymlog({ constant = 1 } = {}) {
44
47
  return {
45
48
  apply: x => Math.sign(x) * Math.log1p(Math.abs(x)),
46
49
  invert: x => Math.sign(x) * Math.exp(Math.abs(x) - _),
47
- sqlApply: c => (c = asColumn(c), sql`SIGN(${c}) * LN(${_} + ABS(${c}))`),
48
- sqlInvert: c => sql`SIGN(${c}) * (EXP(ABS(${c})) - ${_})`
50
+ sqlApply: c => (c = asNode(c), mul(sign(c), ln(add(_, abs(c))))),
51
+ sqlInvert: c => mul(sign(c), sub(exp(abs(c)), _))
49
52
  };
50
53
  }
51
54
 
@@ -53,8 +56,8 @@ function scaleSqrt() {
53
56
  return {
54
57
  apply: x => Math.sign(x) * Math.sqrt(Math.abs(x)),
55
58
  invert: x => Math.sign(x) * x * x,
56
- sqlApply: c => (c = asColumn(c), sql`SIGN(${c}) * SQRT(ABS(${c}))`),
57
- sqlInvert: c => sql`SIGN(${c}) * (${c}) ** 2`
59
+ sqlApply: c => (c = asNode(c), mul(sign(c), sqrt(abs(c)))),
60
+ sqlInvert: c => mul(sign(c), pow(c, 2))
58
61
  };
59
62
  }
60
63
 
@@ -63,8 +66,8 @@ function scalePow({ exponent = 1 } = {}) {
63
66
  return {
64
67
  apply: x => Math.sign(x) * Math.pow(Math.abs(x), e),
65
68
  invert: x => Math.sign(x) * Math.pow(Math.abs(x), 1/e),
66
- sqlApply: c => (c = asColumn(c), sql`SIGN(${c}) * POW(ABS(${c}), ${e})`),
67
- sqlInvert: c => sql`SIGN(${c}) * POW(ABS(${c}), 1/${e})`
69
+ sqlApply: c => (c = asNode(c), mul(sign(c), pow(abs(c), e))),
70
+ sqlInvert: c => mul(sign(c), pow(abs(c), div(1, e)))
68
71
  };
69
72
  }
70
73
 
@@ -72,7 +75,9 @@ function scaleTime() {
72
75
  return {
73
76
  apply: x => +x,
74
77
  invert: x => new Date(x),
75
- sqlApply: c => c instanceof Date ? +c : epoch_ms(asColumn(c)),
78
+ sqlApply: c => c instanceof Date ? literal(+c)
79
+ : isDateLiteral(c) ? literal(+c.value)
80
+ : epoch_ms(c),
76
81
  sqlInvert: identity
77
82
  };
78
83
  }
@@ -92,3 +97,12 @@ export function scaleTransform(options) {
92
97
  const scale = scales[options.type];
93
98
  return scale ? { ...options, ...scale(options) } : null;
94
99
  }
100
+
101
+ /**
102
+ * Check if a value is a date-valued literal SQL AST node.
103
+ * @param {*} x The value to test.
104
+ * @returns {x is LiteralNode}
105
+ */
106
+ function isDateLiteral(x) {
107
+ return x instanceof LiteralNode && x.value instanceof Date;
108
+ }
package/src/types.ts ADDED
@@ -0,0 +1,96 @@
1
+ import { ColumnRefNode } from './ast/column-ref.js';
2
+ import { ExprNode, SQLNode } from './ast/node.js';
3
+ import { TableRefNode } from './ast/table-ref.js';
4
+ import { Query } from './ast/query.js';
5
+
6
+ /**
7
+ * Interface representing a dynamic parameter value.
8
+ */
9
+ export interface ParamLike {
10
+ /** The current parameter value. */
11
+ value: any;
12
+ /** Add an event listener callback. */
13
+ addEventListener(type: string, callback: EventCallback): void;
14
+ /** Remove an event listener callback. */
15
+ removeEventListener(type: string, callback: EventCallback): void;
16
+ }
17
+
18
+ /**
19
+ * Expression value input to SQL builder method.
20
+ */
21
+ export type ExprValue = ExprNode | ParamLike | string | number | boolean | Date;
22
+
23
+ /**
24
+ * Expression values that may be nested in arrays.
25
+ */
26
+ export type ExprVarArgs = MaybeArray<ExprValue>;
27
+
28
+ /**
29
+ * String-typed expression value.
30
+ */
31
+ export type StringValue = ExprNode | ParamLike | string;
32
+
33
+ /**
34
+ * Number-typed expression value.
35
+ */
36
+ export type NumberValue = ExprNode | ParamLike | number;
37
+
38
+ /**
39
+ * Event listener callback function.
40
+ */
41
+ export type EventCallback = <T>(value: any) => Promise<T> | undefined;
42
+
43
+ /**
44
+ * SQL AST traversal visitor callback result.
45
+ * A falsy value (including `undefined`, `null`, `false`, and `0`) indicates
46
+ * that traversal should continue.
47
+ * A negative number values indicates that traversal should stop immediately.
48
+ * Any other truthy value indicates that traversal should not recurse on the
49
+ * current node, but should otherwise continue.
50
+ */
51
+ export type VisitorResult = boolean | number | null | undefined | void;
52
+
53
+ /**
54
+ * SQL AST traversal callback function.
55
+ */
56
+ export type VisitorCallback = (node: SQLNode) => VisitorResult;
57
+
58
+ /** Valid window function names. */
59
+ export type WindowFunctionName =
60
+ | 'cume_dist'
61
+ | 'dense_rank'
62
+ | 'first_value'
63
+ | 'lag'
64
+ | 'last_value'
65
+ | 'lead'
66
+ | 'nth_value'
67
+ | 'ntile'
68
+ | 'percent_rank'
69
+ | 'rank_dense'
70
+ | 'rank'
71
+ | 'row_number';
72
+
73
+ export type MaybeArray<T> = T | T[];
74
+
75
+ export type SelectEntry =
76
+ | string
77
+ | ColumnRefNode
78
+ | [string, ExprNode]
79
+ | Record<string, ExprValue>;
80
+
81
+ export type SelectExpr = MaybeArray<SelectEntry>;
82
+
83
+ export type WithExpr = MaybeArray<Record<string, Query>>;
84
+
85
+ export type FromEntry =
86
+ | string
87
+ | TableRefNode
88
+ | SQLNode
89
+ | [string, SQLNode]
90
+ | Record<string, string | SQLNode>;
91
+
92
+ export type FromExpr = MaybeArray<FromEntry>;
93
+
94
+ export type FilterExpr = MaybeArray<string | ExprNode>;
95
+ export type GroupByExpr = MaybeArray<string | ExprNode>;
96
+ export type OrderByExpr = MaybeArray<string | ExprNode>;
@@ -0,0 +1,96 @@
1
+ import { ExprNode } from '../ast/node.js';
2
+ import { ParamNode } from '../ast/param.js';
3
+ import { TableRefNode } from '../ast/table-ref.js';
4
+ import { WindowDefNode } from '../ast/window.js';
5
+ import { column } from '../functions/column.js';
6
+ import { literal, verbatim } from '../functions/literal.js';
7
+ import { tableRef } from '../functions/table-ref.js';
8
+ import { parseIdentifier } from './string.js';
9
+ import { isArray, isParamLike, isString } from './type-check.js';
10
+
11
+ /**
12
+ * Interpret a value as a SQL AST node. String values are assumed to be
13
+ * column references. All other primitive values are interpreted as
14
+ * SQL literals. Dynamic parameters are interpreted as param AST nodes,
15
+ * while existing AST nodes are left as-is.
16
+ * @param {*} value The value to interpret as a SQL AST node.
17
+ * @returns {ExprNode}
18
+ */
19
+ export function asNode(value) {
20
+ return isString(value)
21
+ ? parseColumnRef(value)
22
+ : asLiteral(value);
23
+ }
24
+
25
+ /**
26
+ * Interpret a value as a verbatim SQL AST node. String values will be
27
+ * passed through to queries as verbatim text. All other primitive values
28
+ * are interpreted as SQL literals. Dynamic parameters are interpreted
29
+ * as param AST nodes, while existing AST nodes are left as-is.
30
+ * @param {*} value The value to interpret as a verbatim AST node.
31
+ * @returns {ExprNode}
32
+ */
33
+ export function asVerbatim(value) {
34
+ return isString(value)
35
+ ? verbatim(value)
36
+ : asLiteral(value);
37
+ }
38
+
39
+ /**
40
+ * Interpret a value as a literal AST node. All other primitive values
41
+ * are interpreted as SQL literals. Dynamic parameters are interpreted
42
+ * as param AST nodes, while existing AST nodes are left as-is.
43
+ * @param {*} value The value to interpret as a literal AST node.
44
+ * @returns {ExprNode}
45
+ */
46
+ export function asLiteral(value) {
47
+ return value instanceof ExprNode ? value
48
+ : isParamLike(value) ? new ParamNode(value)
49
+ : literal(value);
50
+ }
51
+
52
+ /**
53
+ * Interpret a value as a table reference AST node. String values are parsed
54
+ * assuming dot ('.') delimiters (as in `schema.table`). Array values are
55
+ * interpreted as pre-parsed name paths (as in `['schema', 'table']`). Any
56
+ * other values are left as-is.
57
+ * @param {string | string[] | TableRefNode} value The value to interpret as a
58
+ * table reference AST node.
59
+ * @returns {TableRefNode}
60
+ */
61
+ export function asTableRef(value) {
62
+ return isString(value) ? parseTableRef(value)
63
+ : isArray(value) ? tableRef(value)
64
+ : value;
65
+ }
66
+
67
+ /**
68
+ * Parse a string as a column reference, potentially with
69
+ * dot ('.') delimited table, schema, and database references.
70
+ * @param {string} ref The column reference string.
71
+ * @returns {import('../ast/column-ref.js').ColumnRefNode}
72
+ */
73
+ export function parseColumnRef(ref) {
74
+ const ids = parseIdentifier(ref);
75
+ return column(ids.pop(), tableRef(ids));
76
+ }
77
+
78
+ /**
79
+ * Parse a string as a table reference, potentially with
80
+ * dot ('.') delimited schema and database references.
81
+ * @param {string} ref The table reference string.
82
+ * @returns {import('../ast/table-ref.js').TableRefNode}
83
+ */
84
+ export function parseTableRef(ref) {
85
+ return tableRef(parseIdentifier(ref));
86
+ }
87
+
88
+ /**
89
+ * Create a new window definition node. The return value is an empty
90
+ * window definition. Use chained calls such as `partitionby` and `orderby`
91
+ * to specify the window settings.
92
+ * @returns {WindowDefNode}
93
+ */
94
+ export function over() {
95
+ return new WindowDefNode();
96
+ }
@@ -0,0 +1,78 @@
1
+ import { AggregateNode } from '../ast/aggregate.js';
2
+ import { FunctionNode } from '../ast/function.js';
3
+ import { WindowFunctionNode, WindowNode } from '../ast/window.js';
4
+ import { asNode } from './ast.js';
5
+
6
+ /**
7
+ * Test if an AST node is a specific function call.
8
+ * @param {import('../ast/node.js').SQLNode} node The SQL AST node to test.
9
+ * @param {string} name The function name.
10
+ * @returns {node is FunctionNode}
11
+ */
12
+ export function isFunctionCall(node, name) {
13
+ return node instanceof FunctionNode && node.name === name;
14
+ }
15
+
16
+ /**
17
+ * Create a new function call AST node.
18
+ * @param {string} name The function name.
19
+ * @param {...any} args The function arguments.
20
+ * @returns {FunctionNode}
21
+ */
22
+ export function fn(name, ...args) {
23
+ return new FunctionNode(name, argsList(args).map(asNode));
24
+ }
25
+
26
+ /**
27
+ * Create a new aggregate function AST node.
28
+ * @param {string} name The function name.
29
+ * @param {...any} args The function arguments.
30
+ * @returns {AggregateNode}
31
+ */
32
+ export function aggFn(name, ...args) {
33
+ return new AggregateNode(name, argsList(args).map(asNode));
34
+ }
35
+
36
+ /**
37
+ * Create a new window AST node. The output node has an empty window
38
+ * definition. Use chained calls such as `partitionby` and `orderby`
39
+ * to specify the window settings.
40
+ * @param {import('../types.js').WindowFunctionName} name The function name.
41
+ * @param {...any} args The function arguments.
42
+ * @returns {WindowNode}
43
+ */
44
+ export function winFn(name, ...args) {
45
+ return new WindowNode(
46
+ new WindowFunctionNode(name, argsList(args).map(asNode))
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Process a list of expression inputs. Nested arrays are flattened,
52
+ * null results are removed, and each input is cast (as needed) to
53
+ * be a proper SQL AST node. By default, strings are assumed to be
54
+ * column names, while other primitive values map to SQL literals.
55
+ * Use an alternative *cast* function to change this behavior.
56
+ * @param {any[]} list The list of expression inputs.
57
+ * @param {function} [cast] A function that casts an input value
58
+ * to a desired type. By default, `asNode` is used to coerce
59
+ * inputs to AST nodes as needed.
60
+ * @returns {ReturnType<cast>[]}
61
+ */
62
+ export function exprList(list, cast = asNode) {
63
+ return list.flat().filter(x => x != null).map(x => cast(x));
64
+ }
65
+
66
+ /**
67
+ * Process a list of function arguments, stripping any undefined
68
+ * values from the end of the list.
69
+ * @template T
70
+ * @param {T[]} list The input function arguments.
71
+ * @returns {T[]} The prepared argument list.
72
+ */
73
+ export function argsList(list) {
74
+ const n = list.length;
75
+ let i = n;
76
+ for (; i > 0 && list[i - 1] === undefined; --i);
77
+ return i < n ? list.slice(0, i) : list;
78
+ }
@@ -0,0 +1,16 @@
1
+ // TODO: handle case where identifiers are already quoted?
2
+ export function parseIdentifier(id) {
3
+ return id.split('.');
4
+ }
5
+
6
+ export function quoteIdentifier(value) {
7
+ return `"${value}"`;
8
+ }
9
+
10
+ export function unquote(s) {
11
+ return s && isDoubleQuoted(s) ? s.slice(1, -1) : s;
12
+ }
13
+
14
+ function isDoubleQuoted(s) {
15
+ return s[0] === '"' && s[s.length-1] === '"';
16
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Check if a value is a string.
3
+ * @param {*} value The value to check.
4
+ * @returns {value is string}
5
+ */
6
+ export function isString(value) {
7
+ return typeof value === 'string';
8
+ }
9
+
10
+ /**
11
+ * Check if a value is an array.
12
+ * @param {*} value The value to check.
13
+ * @returns {value is Array}
14
+ */
15
+ export function isArray(value) {
16
+ return Array.isArray(value);
17
+ }
18
+
19
+ /**
20
+ * Check if a value is a dynamic parameter.
21
+ * @param {*} value The value to check.
22
+ * @returns {value is import('../types.js').ParamLike}
23
+ */
24
+ export function isParamLike(value) {
25
+ return value
26
+ && typeof value.addEventListener === 'function'
27
+ && value.dynamic !== false
28
+ && 'value' in value;
29
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ AGGREGATE,
3
+ BETWEEN_OPERATOR,
4
+ BINARY_OPERATOR,
5
+ CASE,
6
+ CAST,
7
+ COLUMN_PARAM,
8
+ COLUMN_REF,
9
+ DESCRIBE_QUERY,
10
+ EXPRESSION,
11
+ FRAGMENT,
12
+ FROM_CLAUSE,
13
+ FUNCTION,
14
+ IN_OPERATOR,
15
+ LOGICAL_OPERATOR,
16
+ NOT_BETWEEN_OPERATOR,
17
+ ORDER_BY,
18
+ PARAM,
19
+ SELECT_CLAUSE,
20
+ SELECT_QUERY,
21
+ SET_OPERATION,
22
+ UNARY_OPERATOR,
23
+ WHEN,
24
+ WINDOW,
25
+ WINDOW_CLAUSE,
26
+ WINDOW_DEF,
27
+ WINDOW_FRAME
28
+ } from '../constants.js';
29
+
30
+ export const recurse = {
31
+ [AGGREGATE]: ['args', 'filter'],
32
+ [BETWEEN_OPERATOR]: ['expr', 'extent'],
33
+ [BINARY_OPERATOR]: ['left', 'right'],
34
+ [CASE]: ['expr', '_when', '_else'],
35
+ [CAST]: ['expr'],
36
+ [COLUMN_PARAM]: ['param', 'table'],
37
+ [COLUMN_REF]: ['table'],
38
+ [DESCRIBE_QUERY]: ['query'],
39
+ [EXPRESSION]: ['node'],
40
+ [FRAGMENT]: ['spans'],
41
+ [FROM_CLAUSE]: ['expr'],
42
+ [FUNCTION]: ['args'],
43
+ [IN_OPERATOR]: ['expr', 'values'],
44
+ [LOGICAL_OPERATOR]: ['clauses'],
45
+ [NOT_BETWEEN_OPERATOR]: ['expr', 'extent'],
46
+ [ORDER_BY]: ['expr'],
47
+ [PARAM]: ['value'],
48
+ [SELECT_CLAUSE]: ['expr'],
49
+ [SELECT_QUERY]: ['_with', '_select', '_from', '_where', '_sample', '_groupby', '_having', '_window', '_qualify', '_orderby'],
50
+ [SET_OPERATION]: ['subqueries', '_orderby'],
51
+ [UNARY_OPERATOR]: ['expr'],
52
+ [WHEN]: ['when', 'then'],
53
+ [WINDOW]: ['func', 'def'],
54
+ [WINDOW_CLAUSE]: ['def'],
55
+ [WINDOW_DEF]: ['partition', 'order', 'frame'],
56
+ [WINDOW_FRAME]: ['extent']
57
+ };
@@ -0,0 +1,32 @@
1
+ import { ExprNode, isNode } from '../ast/node.js';
2
+ import { recurse } from './recurse.js';
3
+
4
+ /**
5
+ * Rewrite a SQL expression, based on a map of nodes to replace.
6
+ * This method updates the expression in place.
7
+ * @param {ExprNode} node The root AST node of the expression.
8
+ * @param {Map<ExprNode, ExprNode>} map The rewrite map.
9
+ * When encountered, key nodes are replaced by value nodes.
10
+ * @returns {ExprNode}
11
+ */
12
+ export function rewrite(node, map) {
13
+ if (map.has(node)) {
14
+ return map.get(node);
15
+ } else if (isNode(node)) {
16
+ const props = recurse[node.type];
17
+ const n = props?.length ?? 0;
18
+ for (let i = 0; i < n; ++i) {
19
+ const prop = props[i];
20
+ const child = node[prop];
21
+ if (Array.isArray(child)) {
22
+ const m = child.length;
23
+ for (let j = 0; j < m; ++j) {
24
+ child[j] = rewrite(child[j], map);
25
+ }
26
+ } else if (child) {
27
+ node[prop] = rewrite(child, map);
28
+ }
29
+ }
30
+ }
31
+ return node;
32
+ }
@@ -0,0 +1,108 @@
1
+ import { AGGREGATE, COLUMN_REF, FRAGMENT, PARAM, VERBATIM, WINDOW } from '../constants.js';
2
+ import { aggregateNames, AggregateNode } from '../ast/aggregate.js';
3
+ import { ColumnRefNode } from '../ast/column-ref.js';
4
+ import { SQLNode } from '../ast/node.js';
5
+ import { walk } from './walk.js';
6
+
7
+ // regexp to match valid aggregate function names
8
+ const aggrRegExp = new RegExp(`^(${aggregateNames.join('|')})$`);
9
+
10
+ // regexp to tokenize sql text in order to find function calls
11
+ // includes checks to avoid analyzing text within quoted strings
12
+ // function call tokens will have a pattern like "name(".
13
+ const funcRegExp = /(\\'|\\"|"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\w+\()/g;
14
+
15
+ function hasVerbatimAggregate(s) {
16
+ return s
17
+ .split(funcRegExp)
18
+ .some(tok => tok.endsWith('(') && aggrRegExp.test(tok.slice(0, -1)));
19
+ }
20
+
21
+ /**
22
+ * Indicate if the input AST contains an aggregate expression.
23
+ * The string content of verbatim nodes is analyzed to try to identify
24
+ * unparsed aggregate functions calls within SQL strings.
25
+ * @param {SQLNode} root The root of the AST to search.
26
+ * @returns {number} Return 0 if no aggregate functions are found.
27
+ * Sets bit 1 if an AggregateFunction instance is found.
28
+ * Sets bit 2 if an aggregate embedded in verbatim text is found.
29
+ */
30
+ export function isAggregateExpression(root) {
31
+ let agg = 0;
32
+ walk(root, (node) => {
33
+ switch (node.type) {
34
+ case WINDOW:
35
+ return -1; // aggs can't include windows
36
+ case AGGREGATE:
37
+ agg |= 1;
38
+ return -1;
39
+ case FRAGMENT:
40
+ case VERBATIM: {
41
+ let s = `${node}`.toLowerCase();
42
+
43
+ // strip away scalar subquery content
44
+ const sub = s.indexOf('(select ');
45
+ if (sub >= 0) s = s.slice(0, sub);
46
+
47
+ // exit if expression includes windowing
48
+ if (s.includes(') over ')) return -1;
49
+ if (hasVerbatimAggregate(s)) {
50
+ agg |= 2;
51
+ return -1;
52
+ }
53
+ return 1; // don't recurse
54
+ }
55
+ }
56
+ });
57
+ return agg;
58
+ }
59
+
60
+ /**
61
+ * Collect all aggregate function nodes.
62
+ * @param {SQLNode} root The root of the AST to search.
63
+ * @returns {AggregateNode[]}
64
+ */
65
+ export function collectAggregates(root) {
66
+ const aggs = new Set();
67
+ walk(root, (node) => {
68
+ if (node.type === AGGREGATE) {
69
+ aggs.add(node);
70
+ }
71
+ });
72
+ return Array.from(aggs);
73
+ }
74
+
75
+ /**
76
+ * Collect all unique column references.
77
+ * Multiple references to the same column are de-duplicated, even if
78
+ * they are not object-equal node instances.
79
+ * @param {SQLNode} root The root of the AST to search.
80
+ * @returns {ColumnRefNode[]}
81
+ */
82
+ export function collectColumns(root) {
83
+ const cols = {};
84
+ walk(root, (node) => {
85
+ if (node.type === COLUMN_REF) {
86
+ cols[node] = node; // key on string-coerced node
87
+ }
88
+ });
89
+ return Object.values(cols);
90
+ }
91
+
92
+ /**
93
+ * Collect all unique dynamic parameter instances.
94
+ * @param {SQLNode} root The root of the AST to search.
95
+ * @returns {import('../types.js').ParamLike[]}
96
+ */
97
+ export function collectParams(root) {
98
+ const params = new Set;
99
+ walk(root, (node) => {
100
+ if (node.type === PARAM) {
101
+ params.add(
102
+ /** @type {import('../ast/param.js').ParamNode} */
103
+ (node).param
104
+ );
105
+ }
106
+ });
107
+ return Array.from(params);
108
+ }
@@ -0,0 +1,30 @@
1
+ import { isNode } from '../ast/node.js';
2
+ import { recurse } from './recurse.js';
3
+
4
+ /**
5
+ * Perform a traversal of a SQL expression AST.
6
+ * @param {import('../ast/node.js').SQLNode} node Root node for AST traversal.
7
+ * @param {import('../types.js').VisitorCallback} visit Visitor callback function.
8
+ * @return {import('../types.js').VisitorResult}
9
+ */
10
+ export function walk(node, visit) {
11
+ if (!isNode(node)) return;
12
+ const result = visit(node);
13
+ if (result) return result;
14
+
15
+ const props = recurse[node.type];
16
+ const n = props?.length ?? 0;
17
+ for (let i = 0; i < n; ++i) {
18
+ const value = node[props[i]];
19
+ if (Array.isArray(value)) {
20
+ const m = value.length;
21
+ for (let j = 0; j < m; ++j) {
22
+ if (value[j] && +walk(value[j], visit) < 0) {
23
+ return result;
24
+ }
25
+ }
26
+ } else if (value && +walk(value, visit) < 0) {
27
+ return -1;
28
+ }
29
+ }
30
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "include": ["src/index-types.ts"],
3
+ "compilerOptions": {
4
+ "allowJs": true,
5
+ "declaration": true,
6
+ "emitDeclarationOnly": true,
7
+ "outDir": "dist/types",
8
+ "module": "node16",
9
+ "skipLibCheck": true,
10
+ "types": []
11
+ }
12
+ }