@malloydata/malloy 0.0.123-dev240203154014 → 0.0.123-dev240205203117
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/dist/dialect/dialect_map.js +2 -0
- package/dist/dialect/functions/string_agg.js +12 -4
- package/dist/dialect/functions/util.d.ts +4 -2
- package/dist/dialect/functions/util.js +1 -0
- package/dist/dialect/postgres/functions/string_agg.js +10 -2
- package/dist/dialect/standardsql/functions/string_agg.js +14 -4
- package/dist/lang/ast/expressions/expr-aggregate-function.js +11 -2
- package/dist/lang/ast/expressions/expr-func.js +3 -1
- package/dist/lang/ast/expressions/function-ordering.d.ts +4 -4
- package/dist/lang/ast/expressions/function-ordering.js +26 -8
- package/dist/lang/ast/view-elements/qop-desc-view.d.ts +1 -0
- package/dist/lang/ast/view-elements/qop-desc-view.js +3 -0
- package/dist/lang/ast/view-elements/reference-view.d.ts +1 -0
- package/dist/lang/ast/view-elements/reference-view.js +3 -0
- package/dist/lang/ast/view-elements/view-arrow.d.ts +1 -0
- package/dist/lang/ast/view-elements/view-arrow.js +3 -0
- package/dist/lang/ast/view-elements/view-refine.d.ts +1 -0
- package/dist/lang/ast/view-elements/view-refine.js +3 -0
- package/dist/lang/ast/view-elements/view.d.ts +1 -0
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +3 -13
- package/dist/lang/lib/Malloy/MalloyParser.js +996 -993
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +0 -13
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +0 -8
- package/dist/lang/malloy-to-ast.d.ts +0 -1
- package/dist/lang/malloy-to-ast.js +17 -19
- package/dist/lang/parse-tree-walkers/document-symbol-walker.js +42 -31
- package/dist/lang/test/document-symbol-walker.spec.js +14 -0
- package/dist/lang/test/expressions.spec.js +53 -0
- package/dist/lang/test/lenses.spec.js +26 -0
- package/dist/lang/test/parse.spec.js +14 -0
- package/dist/model/malloy_query.d.ts +5 -2
- package/dist/model/malloy_query.js +96 -25
- package/dist/model/malloy_types.d.ts +9 -2
- package/dist/model/utils.js +7 -1
- package/package.json +1 -1
|
@@ -19,7 +19,6 @@ import { ExprRegexContext } from "./MalloyParser";
|
|
|
19
19
|
import { ExprNowContext } from "./MalloyParser";
|
|
20
20
|
import { TableFunctionContext } from "./MalloyParser";
|
|
21
21
|
import { TableMethodContext } from "./MalloyParser";
|
|
22
|
-
import { NestExistingContext } from "./MalloyParser";
|
|
23
22
|
import { NestDefContext } from "./MalloyParser";
|
|
24
23
|
import { ExprFieldPathContext } from "./MalloyParser";
|
|
25
24
|
import { ExprFieldPropsContext } from "./MalloyParser";
|
|
@@ -453,18 +452,6 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
453
452
|
* @param ctx the parse tree
|
|
454
453
|
*/
|
|
455
454
|
exitTableMethod?: (ctx: TableMethodContext) => void;
|
|
456
|
-
/**
|
|
457
|
-
* Enter a parse tree produced by the `nestExisting`
|
|
458
|
-
* labeled alternative in `MalloyParser.nestEntry`.
|
|
459
|
-
* @param ctx the parse tree
|
|
460
|
-
*/
|
|
461
|
-
enterNestExisting?: (ctx: NestExistingContext) => void;
|
|
462
|
-
/**
|
|
463
|
-
* Exit a parse tree produced by the `nestExisting`
|
|
464
|
-
* labeled alternative in `MalloyParser.nestEntry`.
|
|
465
|
-
* @param ctx the parse tree
|
|
466
|
-
*/
|
|
467
|
-
exitNestExisting?: (ctx: NestExistingContext) => void;
|
|
468
455
|
/**
|
|
469
456
|
* Enter a parse tree produced by the `nestDef`
|
|
470
457
|
* labeled alternative in `MalloyParser.nestEntry`.
|
|
@@ -19,7 +19,6 @@ import { ExprRegexContext } from "./MalloyParser";
|
|
|
19
19
|
import { ExprNowContext } from "./MalloyParser";
|
|
20
20
|
import { TableFunctionContext } from "./MalloyParser";
|
|
21
21
|
import { TableMethodContext } from "./MalloyParser";
|
|
22
|
-
import { NestExistingContext } from "./MalloyParser";
|
|
23
22
|
import { NestDefContext } from "./MalloyParser";
|
|
24
23
|
import { ExprFieldPathContext } from "./MalloyParser";
|
|
25
24
|
import { ExprFieldPropsContext } from "./MalloyParser";
|
|
@@ -356,13 +355,6 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
356
355
|
* @return the visitor result
|
|
357
356
|
*/
|
|
358
357
|
visitTableMethod?: (ctx: TableMethodContext) => Result;
|
|
359
|
-
/**
|
|
360
|
-
* Visit a parse tree produced by the `nestExisting`
|
|
361
|
-
* labeled alternative in `MalloyParser.nestEntry`.
|
|
362
|
-
* @param ctx the parse tree
|
|
363
|
-
* @return the visitor result
|
|
364
|
-
*/
|
|
365
|
-
visitNestExisting?: (ctx: NestExistingContext) => Result;
|
|
366
358
|
/**
|
|
367
359
|
* Visit a parse tree produced by the `nestDef`
|
|
368
360
|
* labeled alternative in `MalloyParser.nestEntry`.
|
|
@@ -144,7 +144,6 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
144
144
|
visitRunStatement(pcx: parse.RunStatementContext): ast.AnonymousQuery;
|
|
145
145
|
visitNestStatement(pcx: parse.NestStatementContext): ast.Nests;
|
|
146
146
|
visitNestedQueryList(pcx: parse.NestedQueryListContext): ast.Nests;
|
|
147
|
-
visitNestExisting(pcx: parse.NestExistingContext): ast.NestFieldDeclaration;
|
|
148
147
|
visitNestDef(pcx: parse.NestDefContext): ast.NestFieldDeclaration;
|
|
149
148
|
visitExploreQueryDef(pcx: parse.ExploreQueryDefContext): ast.ViewFieldDeclaration;
|
|
150
149
|
visitExprNot(pcx: parse.ExprNotContext): ast.ExprNot;
|
|
@@ -606,7 +606,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
606
606
|
expr = new ast.ExprSum(undefined, ref);
|
|
607
607
|
}
|
|
608
608
|
else {
|
|
609
|
-
this.contextError(agg,
|
|
609
|
+
this.contextError(agg, `\`${aggFunc}\` is not legal in a reference-only aggregation`);
|
|
610
610
|
return ref;
|
|
611
611
|
}
|
|
612
612
|
}
|
|
@@ -695,7 +695,8 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
695
695
|
}
|
|
696
696
|
visitAggregateOrderBySpec(pcx) {
|
|
697
697
|
const dir = pcx.ASC() ? 'asc' : pcx.DESC() ? 'desc' : undefined;
|
|
698
|
-
const
|
|
698
|
+
const fCx = pcx.fieldExpr();
|
|
699
|
+
const f = fCx ? this.getFieldExpr(fCx) : undefined;
|
|
699
700
|
return this.astAt(new ast.FunctionOrderBy(f, dir), pcx);
|
|
700
701
|
}
|
|
701
702
|
visitAggregateOrderByStatement(pcx) {
|
|
@@ -795,27 +796,24 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
795
796
|
visitNestedQueryList(pcx) {
|
|
796
797
|
return new ast.Nests(this.only(pcx.nestEntry().map(cx => this.visit(cx)), x => x instanceof ast.NestFieldDeclaration && x, 'query'));
|
|
797
798
|
}
|
|
798
|
-
visitNestExisting(pcx) {
|
|
799
|
-
const nameCx = pcx.fieldPath();
|
|
800
|
-
const name = this.getFieldPath(nameCx, ast.ViewOrScalarFieldReference);
|
|
801
|
-
const referenceView = this.astAt(new ast.ReferenceView(name), nameCx);
|
|
802
|
-
const refineCx = pcx.vExpr();
|
|
803
|
-
const notes = this.getNotes(pcx.tags());
|
|
804
|
-
if (refineCx) {
|
|
805
|
-
const nestRefine = new ast.NestFieldDeclaration(name.nameString, new ast.ViewRefine(referenceView, this.getVExpr(refineCx)));
|
|
806
|
-
nestRefine.extendNote({ notes });
|
|
807
|
-
return this.astAt(nestRefine, pcx);
|
|
808
|
-
}
|
|
809
|
-
const nestReference = new ast.NestFieldDeclaration(name.nameString, referenceView);
|
|
810
|
-
nestReference.extendNote({ notes });
|
|
811
|
-
return this.astAt(nestReference, pcx);
|
|
812
|
-
}
|
|
813
799
|
visitNestDef(pcx) {
|
|
814
|
-
const
|
|
800
|
+
const nameCx = pcx.queryName();
|
|
801
|
+
let name;
|
|
815
802
|
const vExpr = this.getVExpr(pcx.vExpr());
|
|
803
|
+
if (nameCx) {
|
|
804
|
+
name = (0, parse_utils_1.getId)(nameCx);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
const implicitName = vExpr.getImplicitName();
|
|
808
|
+
if (implicitName === undefined) {
|
|
809
|
+
this.contextError(pcx, '`nest:` view requires a name (add `nest_name is ...`)');
|
|
810
|
+
}
|
|
811
|
+
name = implicitName !== null && implicitName !== void 0 ? implicitName : '__unnamed__';
|
|
812
|
+
}
|
|
816
813
|
const nestDef = new ast.NestFieldDeclaration(name, vExpr);
|
|
814
|
+
const isDefineCx = pcx.isDefine();
|
|
817
815
|
nestDef.extendNote({
|
|
818
|
-
notes: this.getNotes(pcx.tags()).concat(this.getIsNotes(
|
|
816
|
+
notes: this.getNotes(pcx.tags()).concat(isDefineCx ? this.getIsNotes(isDefineCx) : []),
|
|
819
817
|
});
|
|
820
818
|
return this.astAt(nestDef, pcx);
|
|
821
819
|
}
|
|
@@ -21,33 +21,9 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
22
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
23
|
*/
|
|
24
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
|
-
if (k2 === undefined) k2 = k;
|
|
26
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
27
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
28
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
29
|
-
}
|
|
30
|
-
Object.defineProperty(o, k2, desc);
|
|
31
|
-
}) : (function(o, m, k, k2) {
|
|
32
|
-
if (k2 === undefined) k2 = k;
|
|
33
|
-
o[k2] = m[k];
|
|
34
|
-
}));
|
|
35
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
36
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
37
|
-
}) : function(o, v) {
|
|
38
|
-
o["default"] = v;
|
|
39
|
-
});
|
|
40
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
41
|
-
if (mod && mod.__esModule) return mod;
|
|
42
|
-
var result = {};
|
|
43
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
44
|
-
__setModuleDefault(result, mod);
|
|
45
|
-
return result;
|
|
46
|
-
};
|
|
47
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
25
|
exports.walkForDocumentSymbols = void 0;
|
|
49
26
|
const ParseTreeWalker_1 = require("antlr4ts/tree/ParseTreeWalker");
|
|
50
|
-
const parser = __importStar(require("../lib/Malloy/MalloyParser"));
|
|
51
27
|
const parse_utils_1 = require("../parse-utils");
|
|
52
28
|
class DocumentSymbolWalker {
|
|
53
29
|
constructor(translator, tokens, scopes, symbols) {
|
|
@@ -147,12 +123,48 @@ class DocumentSymbolWalker {
|
|
|
147
123
|
exitExploreQueryDef(_pcx) {
|
|
148
124
|
this.popScope();
|
|
149
125
|
}
|
|
126
|
+
getNestDefName(pcx) {
|
|
127
|
+
const nameCx = pcx.queryName();
|
|
128
|
+
if (nameCx) {
|
|
129
|
+
return nameCx.id().text;
|
|
130
|
+
}
|
|
131
|
+
let result = undefined;
|
|
132
|
+
let done = false;
|
|
133
|
+
const vExprListen = {
|
|
134
|
+
enterVArrow(pcx) {
|
|
135
|
+
pcx.vExpr().enterRule(vExprListen);
|
|
136
|
+
},
|
|
137
|
+
enterVSeg(pcx) {
|
|
138
|
+
pcx.segExpr().enterRule(segExprListen);
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
const segExprListen = {
|
|
142
|
+
enterSegField(pcx) {
|
|
143
|
+
const names = pcx.fieldPath().fieldName();
|
|
144
|
+
if (!done)
|
|
145
|
+
result !== null && result !== void 0 ? result : (result = names[names.length - 1].id().text);
|
|
146
|
+
},
|
|
147
|
+
enterSegParen(pcx) {
|
|
148
|
+
pcx.vExpr().enterRule(vExprListen);
|
|
149
|
+
},
|
|
150
|
+
enterSegRefine(pcx) {
|
|
151
|
+
pcx._lhs.enterRule(segExprListen);
|
|
152
|
+
},
|
|
153
|
+
enterSegOps() {
|
|
154
|
+
result = undefined;
|
|
155
|
+
done = true;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
pcx.vExpr().enterRule(vExprListen);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
150
161
|
handleNestEntry(pcx) {
|
|
162
|
+
const name = this.getNestDefName(pcx);
|
|
163
|
+
if (name === undefined)
|
|
164
|
+
return;
|
|
151
165
|
const symbol = {
|
|
152
166
|
range: this.translator.rangeFromContext(pcx),
|
|
153
|
-
name
|
|
154
|
-
? pcx.fieldPath().text
|
|
155
|
-
: pcx.queryName().id().text,
|
|
167
|
+
name,
|
|
156
168
|
type: 'query',
|
|
157
169
|
children: [],
|
|
158
170
|
};
|
|
@@ -162,12 +174,11 @@ class DocumentSymbolWalker {
|
|
|
162
174
|
}
|
|
163
175
|
return symbol;
|
|
164
176
|
}
|
|
165
|
-
enterNestExisting(pcx) {
|
|
166
|
-
this.handleNestEntry(pcx);
|
|
167
|
-
}
|
|
168
177
|
enterNestDef(pcx) {
|
|
169
178
|
const symbol = this.handleNestEntry(pcx);
|
|
170
|
-
|
|
179
|
+
if (symbol) {
|
|
180
|
+
this.scopes.push(symbol);
|
|
181
|
+
}
|
|
171
182
|
}
|
|
172
183
|
exitNestDef(_pcx) {
|
|
173
184
|
this.popScope();
|
|
@@ -115,6 +115,20 @@ test('turtle children turtles are included', () => {
|
|
|
115
115
|
}
|
|
116
116
|
`, 'inner_turtle', 'query', [0, 0, 0]);
|
|
117
117
|
});
|
|
118
|
+
test('refinement chain gets name correctly', () => {
|
|
119
|
+
testSymbol((0, test_translator_1.markSource) `
|
|
120
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
121
|
+
query: my_turtle is { nest: ${'something + something_else'} }
|
|
122
|
+
}
|
|
123
|
+
`, 'something', 'query', [0, 0, 0]);
|
|
124
|
+
});
|
|
125
|
+
test('arrow in nest infers name correctly', () => {
|
|
126
|
+
testSymbol((0, test_translator_1.markSource) `
|
|
127
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
128
|
+
query: my_turtle is { nest: ${'thingy -> something + something_else'} }
|
|
129
|
+
}
|
|
130
|
+
`, 'something', 'query', [0, 0, 0]);
|
|
131
|
+
});
|
|
118
132
|
test('join withs are included', () => {
|
|
119
133
|
testSymbol((0, test_translator_1.markSource) `
|
|
120
134
|
source: flights is DB.table('my.table.flights') extend {
|
|
@@ -202,6 +202,42 @@ describe('expressions', () => {
|
|
|
202
202
|
group_by: ai
|
|
203
203
|
calculate: x is lag(ai) { partition_by: ai; order_by: ai }
|
|
204
204
|
}
|
|
205
|
+
`).toTranslate();
|
|
206
|
+
});
|
|
207
|
+
test('analytics order_by requires expression', () => {
|
|
208
|
+
expect((0, test_translator_1.markSource) `
|
|
209
|
+
##! experimental { function_order_by }
|
|
210
|
+
run: a -> {
|
|
211
|
+
group_by: ai
|
|
212
|
+
calculate: x is lag(ai) { order_by: asc }
|
|
213
|
+
}
|
|
214
|
+
`).translationToFailWith('analytic `order_by` must specify an aggregate expression or output field reference');
|
|
215
|
+
});
|
|
216
|
+
test('string_agg_distinct order by cannot specify expression', () => {
|
|
217
|
+
expect((0, test_translator_1.markSource) `
|
|
218
|
+
##! experimental { function_order_by }
|
|
219
|
+
run: a -> {
|
|
220
|
+
group_by: ai
|
|
221
|
+
aggregate: x is string_agg_distinct(astr) { order_by: ai }
|
|
222
|
+
}
|
|
223
|
+
`).translationToFailWith('`order_by` must be only `asc` or `desc` with no expression');
|
|
224
|
+
});
|
|
225
|
+
test('string_agg_distinct order by can be just direction', () => {
|
|
226
|
+
expect((0, test_translator_1.markSource) `
|
|
227
|
+
##! experimental { function_order_by }
|
|
228
|
+
run: a -> {
|
|
229
|
+
group_by: ai
|
|
230
|
+
aggregate: x is string_agg_distinct(astr) { order_by: asc }
|
|
231
|
+
}
|
|
232
|
+
`).toTranslate();
|
|
233
|
+
});
|
|
234
|
+
test('string_agg order by can be just direction', () => {
|
|
235
|
+
expect((0, test_translator_1.markSource) `
|
|
236
|
+
##! experimental { function_order_by }
|
|
237
|
+
run: a -> {
|
|
238
|
+
group_by: ai
|
|
239
|
+
aggregate: x is string_agg(astr) { order_by: asc }
|
|
240
|
+
}
|
|
205
241
|
`).toTranslate();
|
|
206
242
|
});
|
|
207
243
|
test('can specify multiple partition_bys', () => {
|
|
@@ -439,6 +475,18 @@ describe('expressions', () => {
|
|
|
439
475
|
test('source.sum(nested.column)', () => {
|
|
440
476
|
expect(modelX `source.sum(nested.column)`).translationToFailWith('Cannot compute `sum` across repeated relationship `nested`; use `nested.column.sum()`');
|
|
441
477
|
});
|
|
478
|
+
test('can aggregate field defined with no join usage', () => {
|
|
479
|
+
expect((0, test_translator_1.markSource) `
|
|
480
|
+
##! experimental { sql_functions }
|
|
481
|
+
source: s is a extend {
|
|
482
|
+
measure: c is count()
|
|
483
|
+
dimension: f is 1
|
|
484
|
+
}
|
|
485
|
+
run: s -> {
|
|
486
|
+
aggregate: v is f.sum()
|
|
487
|
+
}
|
|
488
|
+
`).toTranslate();
|
|
489
|
+
});
|
|
442
490
|
test('sum(inline.column)', () => {
|
|
443
491
|
expect(modelX `sum(inline.column)`).toTranslateWithWarnings('Join path is required for this calculation; use `inline.column.sum()` or `source.sum(inline.column)` to get a result weighted with respect to `source`');
|
|
444
492
|
});
|
|
@@ -763,6 +811,11 @@ describe('unspported fields in schema', () => {
|
|
|
763
811
|
test('negative numbers are not tokens', () => {
|
|
764
812
|
expect((0, test_translator_1.expr) `ai-1`).toTranslate();
|
|
765
813
|
});
|
|
814
|
+
describe('sql functions', () => {
|
|
815
|
+
test('can aggregate a sql_ function', () => {
|
|
816
|
+
expect((0, test_translator_1.expr) `sum(sql_number("\${a} * 2"))`).toTranslate();
|
|
817
|
+
});
|
|
818
|
+
});
|
|
766
819
|
describe('cast', () => {
|
|
767
820
|
// The "+ 1"s are there to make sure the result is of type 'number'
|
|
768
821
|
test('sql cast', () => {
|
|
@@ -318,6 +318,32 @@ describe('partial views', () => {
|
|
|
318
318
|
}
|
|
319
319
|
`).toTranslate();
|
|
320
320
|
});
|
|
321
|
+
test('order by tacked on the end should work', () => {
|
|
322
|
+
expect((0, test_translator_1.markSource) `
|
|
323
|
+
run: a -> {
|
|
324
|
+
nest: astr + ai + { order_by: astr }
|
|
325
|
+
}
|
|
326
|
+
run: a -> {
|
|
327
|
+
nest: astr + { order_by: astr }
|
|
328
|
+
}
|
|
329
|
+
run: a -> astr + { order_by: astr }
|
|
330
|
+
run: a -> astr + ai + { order_by: astr }
|
|
331
|
+
`).toTranslate();
|
|
332
|
+
});
|
|
333
|
+
test('name can be inferred with arrow', () => {
|
|
334
|
+
expect((0, test_translator_1.markSource) `
|
|
335
|
+
run: a extend { view: foo is { group_by: astr } } -> {
|
|
336
|
+
nest: foo -> astr + { order_by: astr }
|
|
337
|
+
}
|
|
338
|
+
`).toTranslate();
|
|
339
|
+
});
|
|
340
|
+
test('nice error when nest has no name', () => {
|
|
341
|
+
expect((0, test_translator_1.markSource) `
|
|
342
|
+
run: a -> {
|
|
343
|
+
nest: { group_by: astr }
|
|
344
|
+
}
|
|
345
|
+
`).translationToFailWith('`nest:` view requires a name (add `nest_name is ...`)');
|
|
346
|
+
});
|
|
321
347
|
test.skip('partial with index', () => {
|
|
322
348
|
expect((0, test_translator_1.markSource) `
|
|
323
349
|
source: x is a extend {
|
|
@@ -875,5 +875,19 @@ describe('m3/m4 source query sentences', () => {
|
|
|
875
875
|
`).toTranslate();
|
|
876
876
|
});
|
|
877
877
|
});
|
|
878
|
+
describe('sql_functions', () => {
|
|
879
|
+
test('can aggregate sql function', () => {
|
|
880
|
+
expect((0, test_translator_1.markSource) `
|
|
881
|
+
##! experimental { sql_functions }
|
|
882
|
+
source: s is a extend {
|
|
883
|
+
measure: c is count()
|
|
884
|
+
dimension: sql_expr is sql_number("LENGTH(\${TABLE}.astr)")
|
|
885
|
+
}
|
|
886
|
+
run: s -> {
|
|
887
|
+
aggregate: v is sql_expr.sum()
|
|
888
|
+
}
|
|
889
|
+
`).toTranslate();
|
|
890
|
+
});
|
|
891
|
+
});
|
|
878
892
|
test('non breaking space in source', () => expect('source:\u00a0z\u00a0is\u00a0a').toParse());
|
|
879
893
|
//# sourceMappingURL=parse.spec.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Dialect, DialectFieldList } from '../dialect';
|
|
2
|
-
import { AggregateFragment, AggregateFunctionType, Annotation, CompiledQuery, DialectFragment, Expr, FieldDef, FieldFragment, Filtered, FilterExpression, FilterFragment, FunctionCallFragment, FunctionOverloadDef, JoinRelationship, ModelDef, OrderBy, OutputFieldFragment, Parameter, ParameterFragment, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, SourceReferenceFragment, SegmentFieldDef, SpreadFragment, SQLExpressionFragment, SqlStringFragment, StructDef, StructRef, TurtleDef, UngroupFragment } from './malloy_types';
|
|
2
|
+
import { AggregateFragment, AggregateFunctionType, Annotation, CompiledQuery, DialectFragment, Expr, FieldDef, FieldFragment, Filtered, FilterExpression, FilterFragment, FunctionCallFragment, FunctionOverloadDef, FunctionParameterDef, JoinRelationship, ModelDef, OrderBy, OutputFieldFragment, Parameter, ParameterFragment, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, SourceReferenceFragment, SegmentFieldDef, SpreadFragment, SQLExpressionFragment, SqlStringFragment, StructDef, StructRef, TurtleDef, UngroupFragment, FunctionOrderBy } from './malloy_types';
|
|
3
3
|
import { Connection } from '../runtime_types';
|
|
4
4
|
import { AndChain } from './utils';
|
|
5
5
|
import { QueryInfo } from '../dialect/dialect';
|
|
@@ -17,7 +17,7 @@ interface OutputPipelinedSQL {
|
|
|
17
17
|
sqlFieldName: string;
|
|
18
18
|
pipelineSQL: string;
|
|
19
19
|
}
|
|
20
|
-
type UniqueKeyPossibleUse = AggregateFunctionType | '
|
|
20
|
+
type UniqueKeyPossibleUse = AggregateFunctionType | 'generic_asymmetric_aggregate';
|
|
21
21
|
declare class UniqueKeyUse extends Set<UniqueKeyPossibleUse> {
|
|
22
22
|
add_use(k: UniqueKeyPossibleUse | undefined): this | undefined;
|
|
23
23
|
hasAsymetricFunctions(): boolean;
|
|
@@ -71,6 +71,9 @@ declare class QueryField extends QueryNode {
|
|
|
71
71
|
generateSQLExpression(resultSet: FieldInstanceResult, context: QueryStruct, frag: SQLExpressionFragment, state: GenerateState): string;
|
|
72
72
|
private getParameterMap;
|
|
73
73
|
private expandFunctionCall;
|
|
74
|
+
getFunctionOrderBy(resultSet: FieldInstanceResult, context: QueryStruct, state: GenerateState, orderBy: FunctionOrderBy[], args: Expr[], overload: FunctionOverloadDef): string | undefined;
|
|
75
|
+
generateAsymmetricStringAggExpression(resultSet: FieldInstanceResult, context: QueryStruct, value: Expr, separator: Expr | undefined, distinctKey: string, orderBy: FunctionOrderBy[] | undefined, dialectName: string, state: GenerateState): string;
|
|
76
|
+
getParamForArgIndex(params: FunctionParameterDef[], argIndex: number): FunctionParameterDef;
|
|
74
77
|
generateFunctionCallExpression(resultSet: FieldInstanceResult, context: QueryStruct, frag: FunctionCallFragment, state: GenerateState): string;
|
|
75
78
|
generateSpread(_resultSet: FieldInstanceResult, _context: QueryStruct, _frag: SpreadFragment, _state: GenerateState): string;
|
|
76
79
|
generateParameterFragment(resultSet: FieldInstanceResult, context: QueryStruct, expr: ParameterFragment, _state: GenerateState): string;
|
|
@@ -44,7 +44,10 @@ class UniqueKeyUse extends Set {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
hasAsymetricFunctions() {
|
|
47
|
-
return this.has('sum') ||
|
|
47
|
+
return (this.has('sum') ||
|
|
48
|
+
this.has('avg') ||
|
|
49
|
+
this.has('count') ||
|
|
50
|
+
this.has('generic_asymmetric_aggregate'));
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
class StageWriter {
|
|
@@ -295,34 +298,91 @@ class QueryField extends QueryNode {
|
|
|
295
298
|
return [fragment];
|
|
296
299
|
});
|
|
297
300
|
}
|
|
301
|
+
getFunctionOrderBy(resultSet, context, state, orderBy, args, overload) {
|
|
302
|
+
if (orderBy.length === 0)
|
|
303
|
+
return undefined;
|
|
304
|
+
return ('ORDER BY ' +
|
|
305
|
+
orderBy
|
|
306
|
+
.map(ob => {
|
|
307
|
+
var _a, _b;
|
|
308
|
+
const defaultOrderByArgIndex = (_a = overload.dialect[context.dialect.name].defaultOrderByArgIndex) !== null && _a !== void 0 ? _a : 0;
|
|
309
|
+
const expr = (_b = ob.e) !== null && _b !== void 0 ? _b : args[defaultOrderByArgIndex];
|
|
310
|
+
const osql = this.generateDimFragment(resultSet, context, expr, state);
|
|
311
|
+
const dirsql = ob.dir === 'asc' ? ' ASC' : ob.dir === 'desc' ? ' DESC' : '';
|
|
312
|
+
return `${osql}${dirsql}`;
|
|
313
|
+
})
|
|
314
|
+
.join(', '));
|
|
315
|
+
}
|
|
316
|
+
generateAsymmetricStringAggExpression(resultSet, context, value, separator, distinctKey, orderBy, dialectName, state) {
|
|
317
|
+
if (orderBy) {
|
|
318
|
+
throw new Error(`Function \`string_agg\` does not support fanning out with an order by in ${dialectName}`);
|
|
319
|
+
}
|
|
320
|
+
const valueSQL = this.generateDimFragment(resultSet, context, value, state);
|
|
321
|
+
const separatorSQL = separator
|
|
322
|
+
? ' ,' + this.generateDimFragment(resultSet, context, separator, state)
|
|
323
|
+
: '';
|
|
324
|
+
const keyStart = '__STRING_AGG_KS__';
|
|
325
|
+
const keyEnd = '__STRING_AGG_KE__';
|
|
326
|
+
const distinctValueSQL = `concat('${keyStart}', ${distinctKey}, '${keyEnd}', ${valueSQL})`;
|
|
327
|
+
return `REGEXP_REPLACE(
|
|
328
|
+
STRING_AGG(DISTINCT ${distinctValueSQL}${separatorSQL}),
|
|
329
|
+
'${keyStart}.*?${keyEnd}',
|
|
330
|
+
''
|
|
331
|
+
)`;
|
|
332
|
+
}
|
|
333
|
+
getParamForArgIndex(params, argIndex) {
|
|
334
|
+
const prevVariadic = params.slice(0, argIndex).find(p => p.isVariadic);
|
|
335
|
+
return prevVariadic !== null && prevVariadic !== void 0 ? prevVariadic : params[argIndex];
|
|
336
|
+
}
|
|
298
337
|
generateFunctionCallExpression(resultSet, context, frag, state) {
|
|
299
|
-
var _a, _b;
|
|
338
|
+
var _a, _b, _c;
|
|
300
339
|
const overload = frag.overload;
|
|
301
340
|
const args = frag.args;
|
|
302
341
|
const isSymmetric = (_a = frag.overload.isSymmetric) !== null && _a !== void 0 ? _a : false;
|
|
303
342
|
const distinctKey = (0, malloy_types_1.expressionIsAggregate)(overload.returnType.expressionType) &&
|
|
304
343
|
!isSymmetric &&
|
|
305
344
|
this.generateDistinctKeyIfNecessary(resultSet, context, frag.structPath);
|
|
306
|
-
const aggregateOrderBy = frag.orderBy
|
|
307
|
-
? 'ORDER BY ' +
|
|
308
|
-
frag.orderBy
|
|
309
|
-
.map(ob => {
|
|
310
|
-
const osql = this.generateDimFragment(resultSet, context, ob.e, state);
|
|
311
|
-
const dirsql = ob.dir === 'asc' ? ' ASC' : ob.dir === 'desc' ? ' DESC' : '';
|
|
312
|
-
return `${osql}${dirsql}`;
|
|
313
|
-
})
|
|
314
|
-
.join(', ')
|
|
315
|
-
: undefined;
|
|
316
345
|
const aggregateLimit = frag.limit ? `LIMIT ${frag.limit}` : undefined;
|
|
346
|
+
if (frag.name === 'string_agg' &&
|
|
347
|
+
distinctKey &&
|
|
348
|
+
!context.dialect.supportsAggDistinct) {
|
|
349
|
+
return this.generateAsymmetricStringAggExpression(resultSet, context, args[0], args[1], distinctKey, frag.orderBy, context.dialect.name, state);
|
|
350
|
+
}
|
|
317
351
|
if (distinctKey) {
|
|
318
352
|
if (!context.dialect.supportsAggDistinct) {
|
|
319
|
-
throw new Error(`
|
|
353
|
+
throw new Error(`Function \`${frag.name}\` does not support fanning out in ${context.dialect.name}`);
|
|
320
354
|
}
|
|
321
355
|
const argsExpressions = args.map(arg => {
|
|
322
356
|
return this.generateDimFragment(resultSet, context, arg, state);
|
|
323
357
|
});
|
|
324
|
-
|
|
325
|
-
|
|
358
|
+
const orderBys = (_b = frag.orderBy) !== null && _b !== void 0 ? _b : [];
|
|
359
|
+
const orderByExpressions = orderBys.map(ob => {
|
|
360
|
+
var _a, _b;
|
|
361
|
+
const defaultOrderByArgIndex = (_a = overload.dialect[context.dialect.name].defaultOrderByArgIndex) !== null && _a !== void 0 ? _a : 0;
|
|
362
|
+
const expr = (_b = ob.e) !== null && _b !== void 0 ? _b : args[defaultOrderByArgIndex];
|
|
363
|
+
return this.generateDimFragment(resultSet, context, expr, state);
|
|
364
|
+
});
|
|
365
|
+
return context.dialect.sqlAggDistinct(distinctKey, [...argsExpressions, ...orderByExpressions], valNames => {
|
|
366
|
+
const vals = valNames.map((v, i) => {
|
|
367
|
+
// Special case: the argument is required to be literal, so we use the actual argument
|
|
368
|
+
// rather than the packed value
|
|
369
|
+
// TODO don't even pack the value in the first place
|
|
370
|
+
if (i < args.length) {
|
|
371
|
+
const param = this.getParamForArgIndex(overload.params, i);
|
|
372
|
+
if (param.allowedTypes.every(t => (0, malloy_types_1.isLiteral)(t.evalSpace))) {
|
|
373
|
+
return args[i];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return [v];
|
|
377
|
+
});
|
|
378
|
+
const newArgs = vals.slice(0, argsExpressions.length);
|
|
379
|
+
const orderBy = vals
|
|
380
|
+
.slice(argsExpressions.length)
|
|
381
|
+
.map((e, i) => {
|
|
382
|
+
return { e, dir: orderBys[i].dir };
|
|
383
|
+
});
|
|
384
|
+
const orderBySQL = this.getFunctionOrderBy(resultSet, context, state, orderBy, newArgs, overload);
|
|
385
|
+
const funcCall = this.expandFunctionCall(context.dialect.name, overload, newArgs, orderBySQL, aggregateLimit);
|
|
326
386
|
return this.generateExpressionFromExpr(resultSet, context, funcCall, state);
|
|
327
387
|
});
|
|
328
388
|
}
|
|
@@ -337,7 +397,7 @@ class QueryField extends QueryNode {
|
|
|
337
397
|
// Update: Now we apply this only to arguments whose parameter is not constant-requiring.
|
|
338
398
|
// So in `string_agg(val, sep)`, `sep` does not get filters applied to it because
|
|
339
399
|
// it must be constant
|
|
340
|
-
const param = overload.params
|
|
400
|
+
const param = this.getParamForArgIndex(overload.params, index);
|
|
341
401
|
// TODO technically this should probably look at _which_ allowed param type was matched
|
|
342
402
|
// for this argument and see if that type is at most constant... but we lose type information
|
|
343
403
|
// by this point in the compilation, so that info would have to be passed into the func call
|
|
@@ -347,14 +407,15 @@ class QueryField extends QueryNode {
|
|
|
347
407
|
: [this.generateDimFragment(resultSet, context, arg, state)];
|
|
348
408
|
})
|
|
349
409
|
: args;
|
|
350
|
-
const
|
|
410
|
+
const orderBySql = frag.orderBy
|
|
411
|
+
? this.getFunctionOrderBy(resultSet, context, state, frag.orderBy, args, overload)
|
|
412
|
+
: '';
|
|
413
|
+
const funcCall = this.expandFunctionCall(context.dialect.name, overload, mappedArgs, orderBySql, aggregateLimit);
|
|
351
414
|
if ((0, malloy_types_1.expressionIsAnalytic)(overload.returnType.expressionType)) {
|
|
352
|
-
const extraPartitions = ((
|
|
415
|
+
const extraPartitions = ((_c = frag.partitionBy) !== null && _c !== void 0 ? _c : []).map(outputName => {
|
|
353
416
|
return `(${resultSet.getField(outputName).getAnalyticalSQL(false)})`;
|
|
354
417
|
});
|
|
355
|
-
|
|
356
|
-
// in order to generate parameter SQL correctly in BQ re: partition
|
|
357
|
-
return this.generateAnalyticFragment(resultSet, context, funcCall, overload, state, args, extraPartitions, aggregateOrderBy);
|
|
418
|
+
return this.generateAnalyticFragment(resultSet, context, funcCall, overload, state, args, extraPartitions, orderBySql);
|
|
358
419
|
}
|
|
359
420
|
return this.generateExpressionFromExpr(resultSet, context, funcCall, state);
|
|
360
421
|
}
|
|
@@ -1473,6 +1534,7 @@ class QueryQuery extends QueryField {
|
|
|
1473
1534
|
.addStructToJoin(struct.getJoinableParent(), this, uniqueKeyPossibleUse, joinStack);
|
|
1474
1535
|
}
|
|
1475
1536
|
addDependantExpr(resultStruct, context, e, joinStack) {
|
|
1537
|
+
var _a;
|
|
1476
1538
|
for (const expr of e) {
|
|
1477
1539
|
if ((0, malloy_types_1.isFunctionCallFragment)(expr) &&
|
|
1478
1540
|
(0, malloy_types_1.expressionIsAnalytic)(expr.overload.returnType.expressionType) &&
|
|
@@ -1564,11 +1626,18 @@ class QueryQuery extends QueryField {
|
|
|
1564
1626
|
this.addDependantExpr(resultStruct, context, expr.e, joinStack);
|
|
1565
1627
|
}
|
|
1566
1628
|
else if ((0, malloy_types_1.isFunctionCallFragment)(expr)) {
|
|
1629
|
+
const isSymmetric = (_a = expr.overload.isSymmetric) !== null && _a !== void 0 ? _a : false;
|
|
1630
|
+
const isAggregate = (0, malloy_types_1.expressionIsAggregate)(expr.overload.returnType.expressionType);
|
|
1631
|
+
const isAsymmetricAggregate = isAggregate && !isSymmetric;
|
|
1632
|
+
const uniqueKeyPossibleUse = isAsymmetricAggregate
|
|
1633
|
+
? 'generic_asymmetric_aggregate'
|
|
1634
|
+
: undefined;
|
|
1567
1635
|
if (expr.structPath) {
|
|
1568
|
-
this.addDependantPath(resultStruct, context, expr.structPath,
|
|
1636
|
+
this.addDependantPath(resultStruct, context, expr.structPath, uniqueKeyPossibleUse, joinStack);
|
|
1637
|
+
}
|
|
1638
|
+
else if (isAsymmetricAggregate) {
|
|
1639
|
+
resultStruct.addStructToJoin(context, this, uniqueKeyPossibleUse, joinStack);
|
|
1569
1640
|
}
|
|
1570
|
-
// TODO Do we need to call `addStructToJoin` here in the case when there is no `structPath`
|
|
1571
|
-
// and the function is an aggregate function?
|
|
1572
1641
|
for (const e of expr.args) {
|
|
1573
1642
|
this.addDependantExpr(resultStruct, context, e, joinStack);
|
|
1574
1643
|
}
|
|
@@ -1577,7 +1646,9 @@ class QueryQuery extends QueryField {
|
|
|
1577
1646
|
}
|
|
1578
1647
|
if (expr.orderBy) {
|
|
1579
1648
|
for (const ob of expr.orderBy) {
|
|
1580
|
-
|
|
1649
|
+
if (ob.e) {
|
|
1650
|
+
this.addDependantExpr(resultStruct, context, ob.e, joinStack);
|
|
1651
|
+
}
|
|
1581
1652
|
}
|
|
1582
1653
|
}
|
|
1583
1654
|
}
|
|
@@ -132,6 +132,7 @@ export interface AggregateLimitFragment {
|
|
|
132
132
|
}
|
|
133
133
|
export interface FunctionCallFragment {
|
|
134
134
|
type: 'function_call';
|
|
135
|
+
name: string;
|
|
135
136
|
overload: FunctionOverloadDef;
|
|
136
137
|
expressionType: ExpressionType;
|
|
137
138
|
args: Expr[];
|
|
@@ -386,10 +387,15 @@ export interface OrderBy {
|
|
|
386
387
|
field: string | number;
|
|
387
388
|
dir?: 'asc' | 'desc';
|
|
388
389
|
}
|
|
389
|
-
export interface
|
|
390
|
+
export interface FunctionOrderByExpression {
|
|
390
391
|
e: Expr;
|
|
391
392
|
dir?: 'asc' | 'desc';
|
|
392
393
|
}
|
|
394
|
+
export interface FunctionOrderByDefaultExpression {
|
|
395
|
+
e: undefined;
|
|
396
|
+
dir: 'asc' | 'desc';
|
|
397
|
+
}
|
|
398
|
+
export type FunctionOrderBy = FunctionOrderByExpression | FunctionOrderByDefaultExpression;
|
|
393
399
|
export interface ByName {
|
|
394
400
|
by: 'name';
|
|
395
401
|
name: string;
|
|
@@ -599,7 +605,8 @@ export interface FunctionOverloadDef {
|
|
|
599
605
|
dialect: {
|
|
600
606
|
[dialect: string]: {
|
|
601
607
|
e: Expr;
|
|
602
|
-
supportsOrderBy?: boolean;
|
|
608
|
+
supportsOrderBy?: boolean | 'only_default';
|
|
609
|
+
defaultOrderByArgIndex?: number;
|
|
603
610
|
supportsLimit?: boolean;
|
|
604
611
|
};
|
|
605
612
|
};
|
package/dist/model/utils.js
CHANGED
|
@@ -125,6 +125,9 @@ function exprMap(expr, func) {
|
|
|
125
125
|
case 'function_parameter':
|
|
126
126
|
case 'parameter':
|
|
127
127
|
case 'outputField':
|
|
128
|
+
case 'source-reference':
|
|
129
|
+
case 'aggregate_limit':
|
|
130
|
+
case 'aggregate_order_by':
|
|
128
131
|
return fragment;
|
|
129
132
|
case 'sql-string':
|
|
130
133
|
return {
|
|
@@ -231,6 +234,9 @@ function exprWalk(expr, func) {
|
|
|
231
234
|
case 'function_parameter':
|
|
232
235
|
case 'parameter':
|
|
233
236
|
case 'outputField':
|
|
237
|
+
case 'source-reference':
|
|
238
|
+
case 'aggregate_limit':
|
|
239
|
+
case 'aggregate_order_by':
|
|
234
240
|
return fragment;
|
|
235
241
|
case 'function_call':
|
|
236
242
|
return {
|
|
@@ -304,7 +310,7 @@ function exprWalk(expr, func) {
|
|
|
304
310
|
}
|
|
305
311
|
}
|
|
306
312
|
default:
|
|
307
|
-
throw new Error(
|
|
313
|
+
throw new Error(`unexpected fragment type ${fragment.type}`);
|
|
308
314
|
}
|
|
309
315
|
});
|
|
310
316
|
}
|