@uwdata/mosaic-sql 0.16.2 → 0.17.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/package.json +5 -9
- package/src/ast/aggregate.js +50 -8
- package/src/ast/collate.js +33 -0
- package/src/ast/from.js +13 -4
- package/src/ast/node.js +13 -0
- package/src/ast/query.js +54 -22
- package/src/ast/sample.js +3 -3
- package/src/ast/subquery.js +23 -0
- package/src/ast/verbatim.js +8 -1
- package/src/ast/window-frame.js +107 -0
- package/src/ast/window.js +65 -99
- package/src/constants.js +5 -6
- package/src/functions/collate.js +16 -0
- package/src/functions/datetime.js +1 -11
- package/src/functions/interval.js +83 -0
- package/src/functions/literal.js +3 -2
- package/src/functions/window-frame.js +61 -0
- package/src/index.js +12 -4
- package/src/load/load.js +1 -1
- package/src/transforms/bin-date.js +2 -1
- package/src/transforms/filter-query.js +44 -0
- package/src/types.ts +9 -0
- package/src/visit/clone.js +53 -0
- package/src/visit/recurse.js +17 -5
- package/src/visit/rewrite.js +3 -11
- package/src/visit/walk.js +0 -1
- package/tsconfig.json +3 -7
- package/LICENSE +0 -47
- package/dist/types/ast/aggregate.d.ts +0 -71
- package/dist/types/ast/between-op.d.ts +0 -46
- package/dist/types/ast/binary-op.d.ts +0 -28
- package/dist/types/ast/case.d.ts +0 -67
- package/dist/types/ast/cast.d.ts +0 -21
- package/dist/types/ast/column-param.d.ts +0 -23
- package/dist/types/ast/column-ref.d.ts +0 -40
- package/dist/types/ast/fragment.d.ts +0 -14
- package/dist/types/ast/from.d.ts +0 -21
- package/dist/types/ast/function.d.ts +0 -21
- package/dist/types/ast/in-op.d.ts +0 -21
- package/dist/types/ast/interval.d.ts +0 -21
- package/dist/types/ast/literal.d.ts +0 -15
- package/dist/types/ast/logical-op.d.ts +0 -46
- package/dist/types/ast/node.d.ts +0 -24
- package/dist/types/ast/order-by.d.ts +0 -29
- package/dist/types/ast/param.d.ts +0 -20
- package/dist/types/ast/query.d.ts +0 -320
- package/dist/types/ast/sample.d.ts +0 -42
- package/dist/types/ast/select.d.ts +0 -22
- package/dist/types/ast/table-ref.d.ts +0 -25
- package/dist/types/ast/unary-op.d.ts +0 -39
- package/dist/types/ast/verbatim.d.ts +0 -9
- package/dist/types/ast/window.d.ts +0 -180
- package/dist/types/ast/with.d.ts +0 -32
- package/dist/types/constants.d.ts +0 -38
- package/dist/types/functions/aggregate.d.ts +0 -236
- package/dist/types/functions/case.d.ts +0 -13
- package/dist/types/functions/cast.d.ts +0 -26
- package/dist/types/functions/column.d.ts +0 -11
- package/dist/types/functions/cte.d.ts +0 -13
- package/dist/types/functions/datetime.d.ts +0 -45
- package/dist/types/functions/literal.d.ts +0 -16
- package/dist/types/functions/numeric.d.ts +0 -95
- package/dist/types/functions/operators.d.ts +0 -200
- package/dist/types/functions/order-by.d.ts +0 -18
- package/dist/types/functions/spatial.d.ts +0 -38
- package/dist/types/functions/sql-template-tag.d.ts +0 -15
- package/dist/types/functions/string.d.ts +0 -57
- package/dist/types/functions/table-ref.d.ts +0 -9
- package/dist/types/functions/util.d.ts +0 -8
- package/dist/types/functions/window.d.ts +0 -89
- package/dist/types/index-types.d.ts +0 -2
- package/dist/types/index.d.ts +0 -59
- package/dist/types/load/create.d.ts +0 -8
- package/dist/types/load/extension.d.ts +0 -1
- package/dist/types/load/load.d.ts +0 -12
- package/dist/types/load/sql-from.d.ts +0 -11
- package/dist/types/transforms/bin-1d.d.ts +0 -15
- package/dist/types/transforms/bin-2d.d.ts +0 -19
- package/dist/types/transforms/bin-date.d.ts +0 -44
- package/dist/types/transforms/bin-histogram.d.ts +0 -51
- package/dist/types/transforms/bin-linear-1d.d.ts +0 -12
- package/dist/types/transforms/bin-linear-2d.d.ts +0 -19
- package/dist/types/transforms/line-density.d.ts +0 -24
- package/dist/types/transforms/m4.d.ts +0 -21
- package/dist/types/transforms/scales.d.ts +0 -1
- package/dist/types/transforms/util/bin-step.d.ts +0 -61
- package/dist/types/transforms/util/time-interval.d.ts +0 -13
- package/dist/types/types.d.ts +0 -62
- package/dist/types/util/ast.d.ts +0 -61
- package/dist/types/util/function.d.ts +0 -56
- package/dist/types/util/string.d.ts +0 -3
- package/dist/types/util/type-check.d.ts +0 -22
- package/dist/types/visit/recurse.d.ts +0 -28
- package/dist/types/visit/rewrite.d.ts +0 -10
- package/dist/types/visit/visitors.d.ts +0 -34
- package/dist/types/visit/walk.d.ts +0 -10
- package/jsconfig.json +0 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uwdata/mosaic-sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "SQL query construction and analysis.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sql",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"author": "Jeffrey Heer (https://idl.uw.edu)",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"exports": {
|
|
14
|
-
"types": "./dist/
|
|
14
|
+
"types": "./dist/src/index-types.d.ts",
|
|
15
15
|
"default": "./src/index.js"
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
@@ -20,12 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"prebuild": "rimraf dist && mkdir dist",
|
|
23
|
-
"build": "npm run types",
|
|
24
|
-
"types": "tsc",
|
|
25
23
|
"lint": "eslint src test",
|
|
26
|
-
"test": "vitest run &&
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
"gitHead": "26d2719f4bcab471d2831145e1f03f39f3509869"
|
|
24
|
+
"test": "vitest run && tsc",
|
|
25
|
+
"prepublishOnly": "npm run test && npm run lint && tsc"
|
|
26
|
+
}
|
|
31
27
|
}
|
package/src/ast/aggregate.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { ExprVarArgs } from '../types.js'
|
|
2
|
+
* @import { ExprVarArgs, OrderByExpr } from '../types.js'
|
|
3
|
+
* @import { WindowFrameNode } from './window-frame.js'
|
|
3
4
|
*/
|
|
4
5
|
import { AGGREGATE } from '../constants.js';
|
|
5
6
|
import { asVerbatim } from '../util/ast.js';
|
|
7
|
+
import { exprList } from '../util/function.js';
|
|
6
8
|
import { isString } from '../util/type-check.js';
|
|
7
9
|
import { ExprNode } from './node.js';
|
|
8
10
|
import { WindowNode } from './window.js';
|
|
@@ -14,8 +16,9 @@ export class AggregateNode extends ExprNode {
|
|
|
14
16
|
* @param {ExprNode[]} args The aggregate function arguments.
|
|
15
17
|
* @param {boolean} [distinct] The distinct flag.
|
|
16
18
|
* @param {ExprNode} [filter] Filter expression.
|
|
19
|
+
* @param {OrderByExpr} [argOrder] Order by expression.
|
|
17
20
|
*/
|
|
18
|
-
constructor(name, args, distinct, filter) {
|
|
21
|
+
constructor(name, args, distinct = false, filter = null, argOrder = []) {
|
|
19
22
|
super(AGGREGATE);
|
|
20
23
|
/**
|
|
21
24
|
* The aggregate function name.
|
|
@@ -41,6 +44,12 @@ export class AggregateNode extends ExprNode {
|
|
|
41
44
|
* @readonly
|
|
42
45
|
*/
|
|
43
46
|
this.filter = filter;
|
|
47
|
+
/**
|
|
48
|
+
* Order by expression for order-sensitive aggregates.
|
|
49
|
+
* @type {ExprNode[]}
|
|
50
|
+
* @readonly
|
|
51
|
+
*/
|
|
52
|
+
this.order = exprList([argOrder]);
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
/**
|
|
@@ -49,7 +58,7 @@ export class AggregateNode extends ExprNode {
|
|
|
49
58
|
* @returns {AggregateNode} A new aggregate node.
|
|
50
59
|
*/
|
|
51
60
|
distinct(isDistinct = true) {
|
|
52
|
-
return new AggregateNode(this.name, this.args, isDistinct, this.filter);
|
|
61
|
+
return new AggregateNode(this.name, this.args, isDistinct, this.filter, this.order);
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
/**
|
|
@@ -59,7 +68,16 @@ export class AggregateNode extends ExprNode {
|
|
|
59
68
|
*/
|
|
60
69
|
where(filter) {
|
|
61
70
|
if (isString(filter)) filter = asVerbatim(filter);
|
|
62
|
-
return new AggregateNode(this.name, this.args, this.isDistinct, filter);
|
|
71
|
+
return new AggregateNode(this.name, this.args, this.isDistinct, filter, this.order);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Return a new derived aggregate function that sorts values prior to aggregation.
|
|
76
|
+
* @param {OrderByExpr} order The order by expression.
|
|
77
|
+
* @returns {AggregateNode} A new aggregate node.
|
|
78
|
+
*/
|
|
79
|
+
argOrder(order) {
|
|
80
|
+
return new AggregateNode(this.name, this.args, this.isDistinct, this.filter, order);
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
/**
|
|
@@ -88,19 +106,42 @@ export class AggregateNode extends ExprNode {
|
|
|
88
106
|
return this.window().orderby(...expr);
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Return a new window function over this aggregate with the given frame.
|
|
111
|
+
* @param {WindowFrameNode} framedef The window frame definition.
|
|
112
|
+
* @returns {WindowNode} A new window node.
|
|
113
|
+
*/
|
|
114
|
+
frame(framedef) {
|
|
115
|
+
return this.window().frame(framedef);
|
|
116
|
+
}
|
|
117
|
+
|
|
91
118
|
/**
|
|
92
119
|
* Generate a SQL query string for this node.
|
|
93
120
|
* @returns {string}
|
|
94
121
|
*/
|
|
95
122
|
toString() {
|
|
96
|
-
const { name, args, isDistinct, filter } = this;
|
|
97
|
-
const
|
|
98
|
-
|
|
123
|
+
const { name, args, isDistinct, filter, order } = this;
|
|
124
|
+
const arg = [
|
|
125
|
+
isDistinct ? 'DISTINCT' : '',
|
|
126
|
+
args?.length ? args.join(', ')
|
|
127
|
+
: name.toLowerCase() === 'count' ? '*'
|
|
128
|
+
: '',
|
|
129
|
+
order.length ? `ORDER BY ${order.join(', ')}` : ''
|
|
130
|
+
].filter(x => x).join(' ');
|
|
99
131
|
const filt = filter ? ` FILTER (WHERE ${filter})` : '';
|
|
100
|
-
return `${name}(${
|
|
132
|
+
return `${name}(${arg})${filt}`;
|
|
101
133
|
}
|
|
102
134
|
}
|
|
103
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Check if a function name corresponds to an aggregate function.
|
|
138
|
+
* @param {string} name The function name to check
|
|
139
|
+
* @returns {boolean} True if a known aggregate function, false otherwise.
|
|
140
|
+
*/
|
|
141
|
+
export function isAggregateFunction(name) {
|
|
142
|
+
return aggregateNames.includes(name.toLowerCase());
|
|
143
|
+
}
|
|
144
|
+
|
|
104
145
|
/**
|
|
105
146
|
* An array of known aggregate function names.
|
|
106
147
|
* From https://duckdb.org/docs/sql/functions/aggregates.html.
|
|
@@ -124,6 +165,7 @@ export const aggregateNames = [
|
|
|
124
165
|
'bool_or',
|
|
125
166
|
'corr',
|
|
126
167
|
'count',
|
|
168
|
+
'count_star',
|
|
127
169
|
'covar_pop',
|
|
128
170
|
'covar_samp',
|
|
129
171
|
'entropy',
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { COLLATE } from '../constants.js';
|
|
2
|
+
import { ExprNode } from './node.js';
|
|
3
|
+
|
|
4
|
+
export class CollateNode extends ExprNode {
|
|
5
|
+
/**
|
|
6
|
+
* Instantiate a collate node.
|
|
7
|
+
* @param {ExprNode} expr The expression to collate.
|
|
8
|
+
* @param {string} collation The collation type.
|
|
9
|
+
*/
|
|
10
|
+
constructor(expr, collation) {
|
|
11
|
+
super(COLLATE);
|
|
12
|
+
/**
|
|
13
|
+
* The expression to collate.
|
|
14
|
+
* @type {ExprNode}
|
|
15
|
+
* @readonly
|
|
16
|
+
*/
|
|
17
|
+
this.expr = expr;
|
|
18
|
+
/**
|
|
19
|
+
* The collation type.
|
|
20
|
+
* @type {string}
|
|
21
|
+
* @readonly
|
|
22
|
+
*/
|
|
23
|
+
this.collation = collation;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a SQL query string for this node.
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
toString() {
|
|
31
|
+
return `${this.expr} ${COLLATE} ${this.collation}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/ast/from.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @import { SampleClauseNode } from './sample.js' */
|
|
1
2
|
import { FROM_CLAUSE } from '../constants.js';
|
|
2
3
|
import { quoteIdentifier } from '../util/string.js';
|
|
3
4
|
import { SQLNode } from './node.js';
|
|
@@ -8,9 +9,10 @@ export class FromClauseNode extends SQLNode {
|
|
|
8
9
|
/**
|
|
9
10
|
* Instantiate a from node.
|
|
10
11
|
* @param {SQLNode} expr The from expression.
|
|
11
|
-
* @param {string} alias The output name.
|
|
12
|
+
* @param {string} [alias] The output name.
|
|
13
|
+
* @param {SampleClauseNode} [sample] The table sample.
|
|
12
14
|
*/
|
|
13
|
-
constructor(expr, alias) {
|
|
15
|
+
constructor(expr, alias, sample) {
|
|
14
16
|
super(FROM_CLAUSE);
|
|
15
17
|
/**
|
|
16
18
|
* The from expression.
|
|
@@ -24,6 +26,12 @@ export class FromClauseNode extends SQLNode {
|
|
|
24
26
|
* @readonly
|
|
25
27
|
*/
|
|
26
28
|
this.alias = alias;
|
|
29
|
+
/**
|
|
30
|
+
* The table sample.
|
|
31
|
+
* @type {SampleClauseNode}
|
|
32
|
+
* @readonly
|
|
33
|
+
*/
|
|
34
|
+
this.sample = sample;
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -31,10 +39,11 @@ export class FromClauseNode extends SQLNode {
|
|
|
31
39
|
* @returns {string}
|
|
32
40
|
*/
|
|
33
41
|
toString() {
|
|
34
|
-
const { expr, alias } = this;
|
|
42
|
+
const { expr, alias, sample } = this;
|
|
35
43
|
const ref = isQuery(expr) ? `(${expr})` : `${expr}`;
|
|
36
|
-
|
|
44
|
+
const from = alias && !(isTableRef(expr) && expr.table.join('.') === alias)
|
|
37
45
|
? `${ref} AS ${quoteIdentifier(alias)}`
|
|
38
46
|
: `${ref}`;
|
|
47
|
+
return `${from}${sample ? ` TABLESAMPLE ${sample}` : ''}`;
|
|
39
48
|
}
|
|
40
49
|
}
|
package/src/ast/node.js
CHANGED
|
@@ -20,6 +20,19 @@ export class SQLNode {
|
|
|
20
20
|
*/
|
|
21
21
|
this.type = type;
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a shallow clone of this SQL AST node.
|
|
26
|
+
* @returns {this} The shallow clone node.
|
|
27
|
+
*/
|
|
28
|
+
clone() {
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
const clone = new this.constructor();
|
|
31
|
+
for (const key in this) {
|
|
32
|
+
clone[key] = this[key];
|
|
33
|
+
}
|
|
34
|
+
return clone;
|
|
35
|
+
}
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
/**
|
package/src/ast/query.js
CHANGED
|
@@ -125,9 +125,11 @@ export class Query extends ExprNode {
|
|
|
125
125
|
this._with = [];
|
|
126
126
|
/** @type {ExprNode[]} */
|
|
127
127
|
this._orderby = [];
|
|
128
|
-
/** @type {
|
|
128
|
+
/** @type {boolean} */
|
|
129
|
+
this._limitPerc = false;
|
|
130
|
+
/** @type {ExprNode} */
|
|
129
131
|
this._limit = undefined;
|
|
130
|
-
/** @type {
|
|
132
|
+
/** @type {ExprNode} */
|
|
131
133
|
this._offset = undefined;
|
|
132
134
|
/** @type {Query | null} */
|
|
133
135
|
this.cteFor = null;
|
|
@@ -143,12 +145,20 @@ export class Query extends ExprNode {
|
|
|
143
145
|
|
|
144
146
|
/**
|
|
145
147
|
* Clone this query.
|
|
146
|
-
* @returns {
|
|
148
|
+
* @returns {this}
|
|
147
149
|
*/
|
|
148
150
|
clone() {
|
|
149
151
|
return this;
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Add a pointer to the query for which this query is a CTE.
|
|
156
|
+
* @param {Query | null} query
|
|
157
|
+
*/
|
|
158
|
+
setCteFor(query) {
|
|
159
|
+
this.cteFor = query;
|
|
160
|
+
}
|
|
161
|
+
|
|
152
162
|
/**
|
|
153
163
|
* Add WITH common table expressions (CTEs).
|
|
154
164
|
* @param {...WithExpr} expr Expressions to add.
|
|
@@ -159,7 +169,7 @@ export class Query extends ExprNode {
|
|
|
159
169
|
const list = [];
|
|
160
170
|
const add = (name, q) => {
|
|
161
171
|
const query = q.clone();
|
|
162
|
-
query.
|
|
172
|
+
query.setCteFor(this);
|
|
163
173
|
list.push(new WithClauseNode(name, query));
|
|
164
174
|
};
|
|
165
175
|
expr.flat().forEach(e => {
|
|
@@ -180,23 +190,35 @@ export class Query extends ExprNode {
|
|
|
180
190
|
return this;
|
|
181
191
|
}
|
|
182
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Set the query result LIMIT as a percentage value.
|
|
195
|
+
* @param {number | ExprNode} value The limit percentage value.
|
|
196
|
+
* @returns {this}
|
|
197
|
+
*/
|
|
198
|
+
limitPercent(value) {
|
|
199
|
+
this._limitPerc = true;
|
|
200
|
+
this._limit = asNode(value);
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
183
204
|
/**
|
|
184
205
|
* Set the query result LIMIT.
|
|
185
|
-
* @param {number} value The limit value.
|
|
206
|
+
* @param {number | ExprNode} value The limit value.
|
|
186
207
|
* @returns {this}
|
|
187
208
|
*/
|
|
188
209
|
limit(value) {
|
|
189
|
-
this.
|
|
210
|
+
this._limitPerc = false;
|
|
211
|
+
this._limit = asNode(value);
|
|
190
212
|
return this;
|
|
191
213
|
}
|
|
192
214
|
|
|
193
215
|
/**
|
|
194
216
|
* Set the query result OFFSET.
|
|
195
|
-
* @param {number} value The offset value.
|
|
217
|
+
* @param {number | ExprNode} value The offset value.
|
|
196
218
|
* @returns {this}
|
|
197
219
|
*/
|
|
198
220
|
offset(value) {
|
|
199
|
-
this._offset =
|
|
221
|
+
this._offset = asNode(value);
|
|
200
222
|
return this;
|
|
201
223
|
}
|
|
202
224
|
}
|
|
@@ -251,7 +273,7 @@ export class SelectQuery extends Query {
|
|
|
251
273
|
|
|
252
274
|
/**
|
|
253
275
|
* Clone this query.
|
|
254
|
-
* @returns {
|
|
276
|
+
* @returns {this}
|
|
255
277
|
*/
|
|
256
278
|
clone() {
|
|
257
279
|
return Object.assign(new SelectQuery(), this);
|
|
@@ -278,7 +300,7 @@ export class SelectQuery extends Query {
|
|
|
278
300
|
|
|
279
301
|
const keys = new Set(list.map(x => x.alias));
|
|
280
302
|
this._select = this._select
|
|
281
|
-
.filter(x => !keys.has(x.alias))
|
|
303
|
+
.filter(x => x.alias && !keys.has(x.alias))
|
|
282
304
|
.concat(list.filter(x => x.expr));
|
|
283
305
|
return this;
|
|
284
306
|
}
|
|
@@ -313,6 +335,7 @@ export class SelectQuery extends Query {
|
|
|
313
335
|
const add = (v, as) => list.push(new FromClauseNode(asTableRef(v), unquote(as)));
|
|
314
336
|
expr.flat().forEach(e => {
|
|
315
337
|
if (e == null) return;
|
|
338
|
+
else if (e instanceof FromClauseNode) list.push(e);
|
|
316
339
|
else if (isString(e)) add(e, e);
|
|
317
340
|
else if (isTableRef(e)) add(e, e.name);
|
|
318
341
|
else if (isNode(e)) add(e);
|
|
@@ -438,7 +461,7 @@ export class SelectQuery extends Query {
|
|
|
438
461
|
toString() {
|
|
439
462
|
const {
|
|
440
463
|
_with, _select, _distinct, _from, _sample, _where, _groupby,
|
|
441
|
-
_having, _window, _qualify, _orderby, _limit, _offset
|
|
464
|
+
_having, _window, _qualify, _orderby, _limitPerc, _limit, _offset
|
|
442
465
|
} = this;
|
|
443
466
|
const sql = [];
|
|
444
467
|
|
|
@@ -484,10 +507,10 @@ export class SelectQuery extends Query {
|
|
|
484
507
|
if (_orderby.length) sql.push(`ORDER BY ${_orderby.join(', ')}`);
|
|
485
508
|
|
|
486
509
|
// LIMIT
|
|
487
|
-
if (
|
|
510
|
+
if (_limit) sql.push(`LIMIT ${_limit}${_limitPerc ? '%' : ''}`);
|
|
488
511
|
|
|
489
512
|
// OFFSET
|
|
490
|
-
if (
|
|
513
|
+
if (_offset) sql.push(`OFFSET ${_offset}`);
|
|
491
514
|
|
|
492
515
|
return sql.join(' ');
|
|
493
516
|
}
|
|
@@ -504,9 +527,10 @@ export class DescribeQuery extends SQLNode {
|
|
|
504
527
|
|
|
505
528
|
/**
|
|
506
529
|
* Clone this describe query.
|
|
507
|
-
* @returns {
|
|
530
|
+
* @returns {this}
|
|
508
531
|
*/
|
|
509
532
|
clone() {
|
|
533
|
+
// @ts-expect-error
|
|
510
534
|
return new DescribeQuery(this.query.clone());
|
|
511
535
|
}
|
|
512
536
|
|
|
@@ -539,23 +563,31 @@ export class SetOperation extends Query {
|
|
|
539
563
|
this.queries = queries;
|
|
540
564
|
}
|
|
541
565
|
|
|
566
|
+
/**
|
|
567
|
+
* Add a pointer to the query for which this query is a CTE.
|
|
568
|
+
* @param {Query | null} query
|
|
569
|
+
*/
|
|
570
|
+
setCteFor(query) {
|
|
571
|
+
super.setCteFor(query);
|
|
572
|
+
const { queries, cteFor } = this;
|
|
573
|
+
if (cteFor) queries.forEach(q => q.setCteFor(cteFor));
|
|
574
|
+
}
|
|
575
|
+
|
|
542
576
|
/**
|
|
543
577
|
* Return a list of subqueries.
|
|
544
578
|
* @returns {Query[]}
|
|
545
579
|
*/
|
|
546
580
|
get subqueries() {
|
|
547
|
-
|
|
548
|
-
// TODO: revisit this?
|
|
549
|
-
if (cteFor) queries.forEach(q => q.cteFor = cteFor);
|
|
550
|
-
return queries;
|
|
581
|
+
return this.queries;
|
|
551
582
|
}
|
|
552
583
|
|
|
553
584
|
/**
|
|
554
585
|
* Clone this set operation.
|
|
555
|
-
* @returns {
|
|
586
|
+
* @returns {this}
|
|
556
587
|
*/
|
|
557
588
|
clone() {
|
|
558
589
|
const { op, queries, ...rest } = this;
|
|
590
|
+
// @ts-expect-error
|
|
559
591
|
return Object.assign(new SetOperation(op, queries), rest);
|
|
560
592
|
}
|
|
561
593
|
|
|
@@ -564,7 +596,7 @@ export class SetOperation extends Query {
|
|
|
564
596
|
* @returns {string}
|
|
565
597
|
*/
|
|
566
598
|
toString() {
|
|
567
|
-
const { op, queries, _with, _orderby, _limit, _offset } = this;
|
|
599
|
+
const { op, queries, _with, _orderby, _limitPerc, _limit, _offset } = this;
|
|
568
600
|
const sql = [];
|
|
569
601
|
|
|
570
602
|
// WITH
|
|
@@ -577,10 +609,10 @@ export class SetOperation extends Query {
|
|
|
577
609
|
if (_orderby.length) sql.push(`ORDER BY ${_orderby.join(', ')}`);
|
|
578
610
|
|
|
579
611
|
// LIMIT
|
|
580
|
-
if (
|
|
612
|
+
if (_limit) sql.push(`LIMIT ${_limit}${_limitPerc ? '%' : ''}`);
|
|
581
613
|
|
|
582
614
|
// OFFSET
|
|
583
|
-
if (
|
|
615
|
+
if (_offset) sql.push(`OFFSET ${_offset}`);
|
|
584
616
|
|
|
585
617
|
return sql.join(' ');
|
|
586
618
|
}
|
package/src/ast/sample.js
CHANGED
|
@@ -46,8 +46,8 @@ export class SampleClauseNode extends SQLNode {
|
|
|
46
46
|
|
|
47
47
|
toString() {
|
|
48
48
|
const { size, perc, method, seed } = this;
|
|
49
|
-
const
|
|
50
|
-
const s = seed != null ?
|
|
51
|
-
return `${
|
|
49
|
+
const m = method ? `${method} ` : '';
|
|
50
|
+
const s = seed != null ? ` REPEATABLE (${seed})` : '';
|
|
51
|
+
return `${m}(${size}${perc ? '%' : ' ROWS'})${s}`;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @import { Query } from './query.js' */
|
|
2
|
+
import { SCALAR_SUBQUERY } from '../constants.js';
|
|
3
|
+
import { ExprNode } from './node.js';
|
|
4
|
+
|
|
5
|
+
export class ScalarSubqueryNode extends ExprNode {
|
|
6
|
+
/**
|
|
7
|
+
* Instantiate a scalar subquery node.
|
|
8
|
+
* @param {Query} subquery The scalar subquery.
|
|
9
|
+
*/
|
|
10
|
+
constructor(subquery) {
|
|
11
|
+
super(SCALAR_SUBQUERY);
|
|
12
|
+
/**
|
|
13
|
+
* The scalar subquery.
|
|
14
|
+
* @type {Query}
|
|
15
|
+
* @readonly
|
|
16
|
+
*/
|
|
17
|
+
this.subquery = subquery;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
toString() {
|
|
21
|
+
return `(${this.subquery})`;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/ast/verbatim.js
CHANGED
|
@@ -5,8 +5,9 @@ export class VerbatimNode extends ExprNode {
|
|
|
5
5
|
/**
|
|
6
6
|
* Instantiate a raw node with verbatim content.
|
|
7
7
|
* @param {string} value The verbatim content to include.
|
|
8
|
+
* @param {string} [hint] A type hint for analyzing verbatim content.
|
|
8
9
|
*/
|
|
9
|
-
constructor(value) {
|
|
10
|
+
constructor(value, hint) {
|
|
10
11
|
super(VERBATIM);
|
|
11
12
|
/**
|
|
12
13
|
* The verbatim content to include.
|
|
@@ -14,6 +15,12 @@ export class VerbatimNode extends ExprNode {
|
|
|
14
15
|
* @readonly
|
|
15
16
|
*/
|
|
16
17
|
this.value = value;
|
|
18
|
+
/**
|
|
19
|
+
* A type hint for analyzing verbatim content.
|
|
20
|
+
* @type {string}
|
|
21
|
+
* @readonly
|
|
22
|
+
*/
|
|
23
|
+
this.hint = hint;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
/**
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { FrameExtent, FrameScope, FrameValue } from '../types.js'
|
|
3
|
+
*/
|
|
4
|
+
import { WINDOW_EXTENT_EXPR, WINDOW_FRAME } from '../constants.js';
|
|
5
|
+
import { isParamLike } from '../util/type-check.js';
|
|
6
|
+
import { isNode, SQLNode } from './node.js';
|
|
7
|
+
import { ParamNode } from './param.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {ROWS | RANGE | GROUPS} FrameType
|
|
11
|
+
* @typedef {'NO OTHERS' | 'CURRENT ROW' | 'TIES' | 'GROUP'} FrameExclude
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const ROWS = 'ROWS';
|
|
15
|
+
export const RANGE = 'RANGE';
|
|
16
|
+
export const GROUPS = 'GROUPS';
|
|
17
|
+
export const PRECEDING = 'PRECEDING';
|
|
18
|
+
export const FOLLOWING = 'FOLLOWING';
|
|
19
|
+
export const CURRENT_ROW = 'CURRENT ROW';
|
|
20
|
+
export const UNBOUNDED = 'UNBOUNDED';
|
|
21
|
+
|
|
22
|
+
export class WindowFrameNode extends SQLNode {
|
|
23
|
+
/**
|
|
24
|
+
* Instantiate a window frame definition node.
|
|
25
|
+
* @param {FrameType} frameType The frame type, one of ROWS, RANGE, or GROUPS.
|
|
26
|
+
* @param {FrameExtent} extent The window frame extent.
|
|
27
|
+
* @param {FrameExclude} [exclude] The window frame exclusion criteria.
|
|
28
|
+
*/
|
|
29
|
+
constructor(frameType, extent, exclude = undefined) {
|
|
30
|
+
super(WINDOW_FRAME);
|
|
31
|
+
/**
|
|
32
|
+
* The frame type, one of ROWS, RANGE, or GROUPS.
|
|
33
|
+
* @type {FrameType}
|
|
34
|
+
* @readonly
|
|
35
|
+
*/
|
|
36
|
+
this.frameType = frameType;
|
|
37
|
+
/**
|
|
38
|
+
* The window frame extent.
|
|
39
|
+
* @type {[any, any] | ParamNode}
|
|
40
|
+
* @readonly
|
|
41
|
+
*/
|
|
42
|
+
this.extent = isParamLike(extent) ? new ParamNode(extent) : extent;
|
|
43
|
+
/**
|
|
44
|
+
* The window frame exclusion criteria.
|
|
45
|
+
* @type {FrameExclude}
|
|
46
|
+
* @readonly
|
|
47
|
+
*/
|
|
48
|
+
this.exclude = exclude;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate a SQL query string for this node.
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
toString() {
|
|
56
|
+
const { frameType, exclude, extent } = this;
|
|
57
|
+
const [prev, next] = isNode(extent) ? extent.value : extent;
|
|
58
|
+
const a = asFrameExpr(prev, PRECEDING);
|
|
59
|
+
const b = asFrameExpr(next, FOLLOWING);
|
|
60
|
+
return `${frameType} BETWEEN ${a} AND ${b}${exclude ? ` ${exclude}` : ''}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function asFrameExpr(value, scope) {
|
|
65
|
+
return value instanceof WindowFrameExprNode ? value
|
|
66
|
+
: value != null && typeof value !== 'number' ? `${value} ${scope}`
|
|
67
|
+
: value === 0 ? CURRENT_ROW
|
|
68
|
+
: !(value && Number.isFinite(value)) ? `${UNBOUNDED} ${scope}`
|
|
69
|
+
: `${Math.abs(value)} ${scope}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class WindowFrameExprNode extends SQLNode {
|
|
73
|
+
/**
|
|
74
|
+
* Instantiate a window frame definition node.
|
|
75
|
+
* @param {FrameScope} scope The frame scope, one of PRECEDING, FOLLOWING, or CURRENT ROW.
|
|
76
|
+
* @param {FrameValue | null} [expr] The window frame extent expression.
|
|
77
|
+
*/
|
|
78
|
+
constructor(scope, expr = null) {
|
|
79
|
+
super(WINDOW_EXTENT_EXPR);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The window frame extent.
|
|
83
|
+
* @type {FrameScope}
|
|
84
|
+
* @readonly
|
|
85
|
+
*/
|
|
86
|
+
this.scope = scope;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The window frame extent expression. This value should be null
|
|
90
|
+
* in the case of current row or unbounded extent values.
|
|
91
|
+
* @type {FrameValue | null}
|
|
92
|
+
* @readonly
|
|
93
|
+
*/
|
|
94
|
+
this.expr = expr;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generate a SQL query string for this node.
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
toString() {
|
|
102
|
+
const { scope, expr } = this;
|
|
103
|
+
return scope === CURRENT_ROW
|
|
104
|
+
? scope
|
|
105
|
+
: `${expr ?? UNBOUNDED} ${scope}`;
|
|
106
|
+
}
|
|
107
|
+
}
|