@malloydata/malloy 0.0.95-dev231019211822 → 0.0.95
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/README.md +2 -2
- package/dist/lang/ast/elements/source-query-nodes.d.ts +3 -2
- package/dist/lang/ast/elements/source-query-nodes.js +7 -7
- package/dist/lang/ast/query-builders/project-builder.js +2 -2
- package/dist/lang/ast/query-items/typecheck_utils.js +3 -3
- package/dist/lang/ast/query-properties/nest.js +2 -2
- package/dist/lang/malloy-to-ast.d.ts +2 -1
- package/dist/lang/malloy-to-ast.js +47 -61
- package/dist/lang/parse-malloy.js +2 -2
- package/dist/lang/test/document-help-context-walker.spec.js +1 -1
- package/dist/lang/test/document-symbol-walker.spec.js +13 -13
- package/dist/lang/test/expressions.spec.js +11 -10
- package/dist/lang/test/parse.spec.js +14 -14
- package/dist/lang/test/query.spec.js +16 -16
- package/dist/lang/test/source.spec.js +4 -4
- package/dist/lang/test/sql-block.spec.js +2 -2
- package/dist/malloy.js +1 -1
- package/dist/model/malloy_query.js +2 -2
- package/dist/tags.js +20 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ const bigquery = require("@malloydata/db-bigquery")
|
|
|
14
14
|
|
|
15
15
|
const connection = new bigquery.BigQueryConnection("bigquery");
|
|
16
16
|
const runtime = new malloy.SingleConnectionRuntime(connection);
|
|
17
|
-
const model = runtime.loadModel("source: airports is table('malloytest.airports')")
|
|
18
|
-
const runner = model.loadQuery("
|
|
17
|
+
const model = runtime.loadModel("source: airports is bigquery.table('malloytest.airports')")
|
|
18
|
+
const runner = model.loadQuery("run: airports->{aggregate: airport_count is count()}")
|
|
19
19
|
runner.run().then((result) => {
|
|
20
20
|
console.log(result.data.value) // [ { airport_count: 19793 } ]
|
|
21
21
|
})
|
|
@@ -8,6 +8,7 @@ import { SourceDesc } from '../types/source-desc';
|
|
|
8
8
|
import { QOPDesc } from '../query-properties/qop-desc';
|
|
9
9
|
import { FullQuery } from '../query-elements/full-query';
|
|
10
10
|
import { ViewFieldReference } from '../query-items/field-references';
|
|
11
|
+
import { LogSeverity } from '../../parse-log';
|
|
11
12
|
export declare class SQReference extends SourceQueryNode {
|
|
12
13
|
readonly ref: ModelEntryReference;
|
|
13
14
|
elementType: string;
|
|
@@ -21,9 +22,9 @@ export declare class SQLegacyModify extends SourceQueryNode {
|
|
|
21
22
|
readonly sourceQuery: SourceQueryNode;
|
|
22
23
|
readonly plus: MalloyElement[];
|
|
23
24
|
readonly hasPlus: boolean;
|
|
24
|
-
readonly
|
|
25
|
+
readonly m4Severity: LogSeverity | false;
|
|
25
26
|
elementType: string;
|
|
26
|
-
constructor(sourceQuery: SourceQueryNode, plus: MalloyElement[], hasPlus: boolean,
|
|
27
|
+
constructor(sourceQuery: SourceQueryNode, plus: MalloyElement[], hasPlus: boolean, m4Severity: LogSeverity | false);
|
|
27
28
|
getQuery(): QueryElement | undefined;
|
|
28
29
|
getSource(): Source | undefined;
|
|
29
30
|
isSource(): boolean;
|
|
@@ -92,12 +92,12 @@ class SQReference extends source_query_1.SourceQueryNode {
|
|
|
92
92
|
}
|
|
93
93
|
exports.SQReference = SQReference;
|
|
94
94
|
class SQLegacyModify extends source_query_1.SourceQueryNode {
|
|
95
|
-
constructor(sourceQuery, plus, hasPlus,
|
|
95
|
+
constructor(sourceQuery, plus, hasPlus, m4Severity) {
|
|
96
96
|
super({ sourceQuery, plus });
|
|
97
97
|
this.sourceQuery = sourceQuery;
|
|
98
98
|
this.plus = plus;
|
|
99
99
|
this.hasPlus = hasPlus;
|
|
100
|
-
this.
|
|
100
|
+
this.m4Severity = m4Severity;
|
|
101
101
|
this.elementType = 'sqLogacyModify';
|
|
102
102
|
}
|
|
103
103
|
getQuery() {
|
|
@@ -105,8 +105,8 @@ class SQLegacyModify extends source_query_1.SourceQueryNode {
|
|
|
105
105
|
if (!asQuery) {
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
|
-
if (!this.hasPlus && this.
|
|
109
|
-
this.log('Implicit query refinement is deprecated, use the `+` operator',
|
|
108
|
+
if (!this.hasPlus && this.m4Severity) {
|
|
109
|
+
this.log('Implicit query refinement is deprecated, use the `+` operator', this.m4Severity);
|
|
110
110
|
}
|
|
111
111
|
const stmts = [];
|
|
112
112
|
for (const stmt of this.plus) {
|
|
@@ -127,12 +127,12 @@ class SQLegacyModify extends source_query_1.SourceQueryNode {
|
|
|
127
127
|
if (!asSource) {
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
|
-
if (this.
|
|
130
|
+
if (this.m4Severity) {
|
|
131
131
|
if (this.hasPlus) {
|
|
132
|
-
this.log('Source extension with "+" is deprecated, use the "extend" operator',
|
|
132
|
+
this.log('Source extension with "+" is deprecated, use the "extend" operator', this.m4Severity);
|
|
133
133
|
}
|
|
134
134
|
else {
|
|
135
|
-
this.log('Implicit source extension is deprecated, use the `extend` operator.',
|
|
135
|
+
this.log('Implicit source extension is deprecated, use the `extend` operator.', this.m4Severity);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
const stmts = [];
|
|
@@ -38,7 +38,7 @@ class ProjectBuilder extends reduce_builder_1.ReduceBuilder {
|
|
|
38
38
|
}
|
|
39
39
|
execute(qp) {
|
|
40
40
|
if (qp.elementType === 'having' || qp instanceof group_by_1.GroupBy) {
|
|
41
|
-
qp.log('Illegal statement in a
|
|
41
|
+
qp.log('Illegal statement in a select query operation');
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
44
44
|
super.execute(qp);
|
|
@@ -51,7 +51,7 @@ class ProjectBuilder extends reduce_builder_1.ReduceBuilder {
|
|
|
51
51
|
from = fromSeg;
|
|
52
52
|
}
|
|
53
53
|
else {
|
|
54
|
-
this.resultFS.log(`Can't refine
|
|
54
|
+
this.resultFS.log(`Can't refine select with ${fromSeg.type}`);
|
|
55
55
|
return error_factory_1.ErrorFactory.projectSegment;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -43,7 +43,7 @@ function typecheckProject(type, logTo) {
|
|
|
43
43
|
else {
|
|
44
44
|
throw new Error(`Unexpected expression type ${type.expressionType} not handled here`);
|
|
45
45
|
}
|
|
46
|
-
logTo.log(`Cannot use ${kind} field in a
|
|
46
|
+
logTo.log(`Cannot use ${kind} field in a select operation, did you mean to use ${useInstead} operation instead?`);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
exports.typecheckProject = typecheckProject;
|
|
@@ -117,7 +117,7 @@ function typecheckCalculate(type, logTo) {
|
|
|
117
117
|
kind = 'an aggregate';
|
|
118
118
|
}
|
|
119
119
|
else if ((0, model_1.expressionIsScalar)(type.expressionType)) {
|
|
120
|
-
useInstead = 'a group_by or
|
|
120
|
+
useInstead = 'a group_by or select';
|
|
121
121
|
kind = 'a scalar';
|
|
122
122
|
}
|
|
123
123
|
else {
|
|
@@ -141,7 +141,7 @@ function typecheckAggregate(type, logTo) {
|
|
|
141
141
|
kind = 'an analytic';
|
|
142
142
|
}
|
|
143
143
|
else if ((0, model_1.expressionIsScalar)(type.expressionType)) {
|
|
144
|
-
useInstead = 'a group_by or
|
|
144
|
+
useInstead = 'a group_by or select';
|
|
145
145
|
kind = 'a scalar';
|
|
146
146
|
}
|
|
147
147
|
else {
|
|
@@ -67,7 +67,7 @@ class TurtleDeclRoot extends pipeline_desc_1.TurtleHeadedPipe {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
else if (this.withRefinement) {
|
|
70
|
-
throw this.internalError("Can't refine the head of a
|
|
70
|
+
throw this.internalError("Can't refine the head of a view in its definition");
|
|
71
71
|
}
|
|
72
72
|
let pipeOutFS = fs;
|
|
73
73
|
if (modelPipe.pipeline.length > 0) {
|
|
@@ -193,7 +193,7 @@ class NestReference extends field_references_1.FieldReference {
|
|
|
193
193
|
kind = 'an analytic';
|
|
194
194
|
}
|
|
195
195
|
else if ((0, model_1.expressionIsScalar)(type.expressionType)) {
|
|
196
|
-
useInstead = 'a group_by or
|
|
196
|
+
useInstead = 'a group_by or select';
|
|
197
197
|
kind = 'a scalar';
|
|
198
198
|
}
|
|
199
199
|
else if ((0, model_1.expressionIsAggregate)(type.expressionType)) {
|
|
@@ -49,8 +49,9 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
49
49
|
* Log an error message relative to a parse node
|
|
50
50
|
*/
|
|
51
51
|
protected contextError(cx: ParserRuleContext, msg: string, sev?: LogSeverity): void;
|
|
52
|
+
protected m4Severity(): LogSeverity | false;
|
|
53
|
+
protected m4advisory(cx: ParserRuleContext, msg: string): void;
|
|
52
54
|
protected only<T extends ast.MalloyElement>(els: ast.MalloyElement[], isGood: (el: ast.MalloyElement) => T | false, desc: string): T[];
|
|
53
|
-
protected m4WarningsEnabled(): boolean;
|
|
54
55
|
protected getNumber(term: ParseTree): number;
|
|
55
56
|
protected getFieldName(cx: HasID): ast.FieldName;
|
|
56
57
|
protected getModelEntryName(cx: HasID): ast.ModelEntryReference;
|
|
@@ -67,7 +67,7 @@ class IgnoredElement extends ast.MalloyElement {
|
|
|
67
67
|
this.malloySrc = src;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
const DEFAULT_COMPILER_FLAGS = ['##! m4warnings'];
|
|
70
|
+
const DEFAULT_COMPILER_FLAGS = ['##! m4warnings=error'];
|
|
71
71
|
/**
|
|
72
72
|
* ANTLR visitor pattern parse tree traversal. Generates a Malloy
|
|
73
73
|
* AST from an ANTLR parse tree.
|
|
@@ -118,6 +118,19 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
118
118
|
severity: sev,
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
|
+
m4Severity() {
|
|
122
|
+
const m4severityTag = this.compilerFlags.tag('m4warnings');
|
|
123
|
+
if (m4severityTag) {
|
|
124
|
+
return m4severityTag.text() === 'warn' ? 'warn' : 'error';
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
m4advisory(cx, msg) {
|
|
129
|
+
const m4 = this.m4Severity();
|
|
130
|
+
if (m4) {
|
|
131
|
+
this.contextError(cx, msg, m4);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
121
134
|
only(els, isGood, desc) {
|
|
122
135
|
const acceptable = [];
|
|
123
136
|
for (const el of els) {
|
|
@@ -131,9 +144,6 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
131
144
|
}
|
|
132
145
|
return acceptable;
|
|
133
146
|
}
|
|
134
|
-
m4WarningsEnabled() {
|
|
135
|
-
return this.compilerFlags.has('m4warnings');
|
|
136
|
-
}
|
|
137
147
|
getNumber(term) {
|
|
138
148
|
return Number.parseInt(term.text);
|
|
139
149
|
}
|
|
@@ -179,9 +189,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
179
189
|
}
|
|
180
190
|
getFilterShortcut(cx) {
|
|
181
191
|
const el = this.getFilterElement(cx.fieldExpr());
|
|
182
|
-
|
|
183
|
-
this.astError(el, 'Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead', 'warn');
|
|
184
|
-
}
|
|
192
|
+
this.m4advisory(cx, 'Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead');
|
|
185
193
|
return new ast.Filter([el]);
|
|
186
194
|
}
|
|
187
195
|
getPlainString(cx) {
|
|
@@ -208,8 +216,8 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
208
216
|
}
|
|
209
217
|
makeSqlString(pcx, sqlStr) {
|
|
210
218
|
for (const part of pcx.sqlInterpolation()) {
|
|
211
|
-
if (part.CLOSE_CODE()
|
|
212
|
-
this.
|
|
219
|
+
if (part.CLOSE_CODE()) {
|
|
220
|
+
this.m4advisory(part, 'Use %{ ... } instead of %{ ... }%');
|
|
213
221
|
}
|
|
214
222
|
}
|
|
215
223
|
for (const part of (0, parse_utils_1.getStringParts)(pcx)) {
|
|
@@ -288,13 +296,11 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
288
296
|
getSourceExtensions(pcx) {
|
|
289
297
|
const extensions = pcx === null || pcx === void 0 ? void 0 : pcx.exploreProperties();
|
|
290
298
|
const sourceDesc = this.astAt(this.visitExploreProperties(extensions), pcx);
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
this.contextError(pcx, 'Implicit source extension is deprecated, use the `extend` operator.', 'warn');
|
|
297
|
-
}
|
|
299
|
+
if (pcx.refineOperator()) {
|
|
300
|
+
this.m4advisory(pcx, 'Source extension with "+" is deprecated, use the "extend" operator');
|
|
301
|
+
}
|
|
302
|
+
else if (pcx.EXTEND() === undefined) {
|
|
303
|
+
this.m4advisory(pcx, 'Implicit source extension is deprecated, use the `extend` operator.');
|
|
298
304
|
}
|
|
299
305
|
return sourceDesc;
|
|
300
306
|
}
|
|
@@ -310,9 +316,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
310
316
|
visitTableFunction(pcx) {
|
|
311
317
|
const tableURI = this.getPlainString(pcx.tableURI());
|
|
312
318
|
const el = this.astAt(new ast.TableFunctionSource(tableURI), pcx);
|
|
313
|
-
|
|
314
|
-
this.astError(el, "`table('connection_name:table_path')` is deprecated; use `connection_name.table('table_path')`", 'warn');
|
|
315
|
-
}
|
|
319
|
+
this.m4advisory(pcx, "`table('connection_name:table_path')` is deprecated; use `connection_name.table('table_path')`");
|
|
316
320
|
return el;
|
|
317
321
|
}
|
|
318
322
|
visitTableMethod(pcx) {
|
|
@@ -324,9 +328,6 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
324
328
|
getLegacySQLSouce(pcx) {
|
|
325
329
|
const name = this.getModelEntryName(pcx);
|
|
326
330
|
const res = this.astAt(new ast.FromSQLSource(name), pcx);
|
|
327
|
-
if (this.m4WarningsEnabled()) {
|
|
328
|
-
this.astError(res, '`from_sql` is deprecated; use `connection_name.sql(...)` as a source directly', 'warn');
|
|
329
|
-
}
|
|
330
331
|
return res;
|
|
331
332
|
}
|
|
332
333
|
visitSqlSource(pcx) {
|
|
@@ -411,9 +412,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
411
412
|
}
|
|
412
413
|
visitQueryJoinStatement(pcx) {
|
|
413
414
|
const result = this.astAt(this.visit(pcx.joinStatement()), pcx);
|
|
414
|
-
|
|
415
|
-
this.astError(result, 'Joins in queries are deprecated, move into an `extend:` block.', 'warn');
|
|
416
|
-
}
|
|
415
|
+
this.m4advisory(pcx, 'Joins in queries are deprecated, move into an `extend:` block.');
|
|
417
416
|
return result;
|
|
418
417
|
}
|
|
419
418
|
visitJoinOn(pcx) {
|
|
@@ -477,9 +476,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
477
476
|
const defs = this.getFieldDefs(pcx.defList().fieldDef(), ast.DeclareFieldDeclaration);
|
|
478
477
|
const stmt = new ast.DeclareFields(defs);
|
|
479
478
|
const result = this.astAt(stmt, pcx);
|
|
480
|
-
|
|
481
|
-
this.astError(result, '`declare:` is deprecated; use `dimension:` or `measure:` inside a source or `extend:` block', 'warn');
|
|
482
|
-
}
|
|
479
|
+
this.m4advisory(pcx, '`declare:` is deprecated; use `dimension:` or `measure:` inside a source or `extend:` block');
|
|
483
480
|
return result;
|
|
484
481
|
}
|
|
485
482
|
visitExploreRenameDef(pcx) {
|
|
@@ -500,9 +497,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
500
497
|
visitFilterByShortcut(pcx) {
|
|
501
498
|
const el = this.getFilterElement(pcx.fieldExpr());
|
|
502
499
|
const res = this.astAt(new ast.Filter([el]), pcx);
|
|
503
|
-
|
|
504
|
-
this.astError(el, 'Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead', 'warn');
|
|
505
|
-
}
|
|
500
|
+
this.m4advisory(pcx, 'Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead');
|
|
506
501
|
return res;
|
|
507
502
|
}
|
|
508
503
|
visitWhereStatement(pcx) {
|
|
@@ -525,8 +520,8 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
525
520
|
const queryDefs = this.visitSubQueryDefList(pcx.subQueryDefList());
|
|
526
521
|
const blockNotes = this.getNotes(pcx.tags());
|
|
527
522
|
queryDefs.extendNote({ blockNotes });
|
|
528
|
-
if (
|
|
529
|
-
this.
|
|
523
|
+
if (pcx.QUERY()) {
|
|
524
|
+
this.m4advisory(pcx, 'Use view: inside of a source instead of query:');
|
|
530
525
|
}
|
|
531
526
|
return queryDefs;
|
|
532
527
|
}
|
|
@@ -647,7 +642,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
647
642
|
}
|
|
648
643
|
throw this.internalError(pcx, 'Unexpected element in fieldCollectionMember');
|
|
649
644
|
}
|
|
650
|
-
// "FieldCollection" can only mean a
|
|
645
|
+
// "FieldCollection" can only mean a select statement today
|
|
651
646
|
visitFieldCollection(pcx) {
|
|
652
647
|
const fields = pcx
|
|
653
648
|
.collectionMember()
|
|
@@ -655,8 +650,8 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
655
650
|
return this.astAt(new ast.ProjectStatement(fields), pcx);
|
|
656
651
|
}
|
|
657
652
|
visitProjectStatement(pcx) {
|
|
658
|
-
if (
|
|
659
|
-
this.
|
|
653
|
+
if (pcx.PROJECT()) {
|
|
654
|
+
this.m4advisory(pcx, 'project: keyword is deprecated, use select:');
|
|
660
655
|
}
|
|
661
656
|
const stmt = this.visitFieldCollection(pcx.fieldCollection());
|
|
662
657
|
stmt.extendNote({ blockNotes: this.getNotes(pcx.tags()) });
|
|
@@ -724,9 +719,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
724
719
|
const topN = this.getNumber(pcx.INTEGER_LITERAL());
|
|
725
720
|
let top;
|
|
726
721
|
if (byCx) {
|
|
727
|
-
|
|
728
|
-
this.contextError(byCx, 'by clause of top statement unupported. Use order_by instead', 'warn');
|
|
729
|
-
}
|
|
722
|
+
this.m4advisory(byCx, 'by clause of top statement unupported. Use order_by instead');
|
|
730
723
|
const nameCx = byCx.fieldName();
|
|
731
724
|
if (nameCx) {
|
|
732
725
|
const name = this.getFieldName(nameCx);
|
|
@@ -747,8 +740,8 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
747
740
|
}
|
|
748
741
|
buildPipelineFromName(pipe, pipeCx) {
|
|
749
742
|
const firstCx = pipeCx.firstSegment();
|
|
750
|
-
if (
|
|
751
|
-
this.
|
|
743
|
+
if (firstCx.ARROW()) {
|
|
744
|
+
this.m4advisory(firstCx, "Leading '->' in a view or nest definition is no longer needed.");
|
|
752
745
|
}
|
|
753
746
|
const nameCx = firstCx.exploreQueryName();
|
|
754
747
|
if (nameCx) {
|
|
@@ -794,9 +787,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
794
787
|
const notes = this.getNotes(pcx.topLevelAnonQueryDef().tags());
|
|
795
788
|
const blockNotes = this.getNotes(pcx.tags());
|
|
796
789
|
theQuery.extendNote({ notes, blockNotes });
|
|
797
|
-
|
|
798
|
-
this.contextError(defCx, 'Anonymous `query:` statements are deprecated, use `run:` instead', 'warn');
|
|
799
|
-
}
|
|
790
|
+
this.m4advisory(defCx, 'Anonymous `query:` statements are deprecated, use `run:` instead');
|
|
800
791
|
return this.astAt(theQuery, pcx);
|
|
801
792
|
}
|
|
802
793
|
visitRunStatement(pcx) {
|
|
@@ -939,9 +930,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
939
930
|
throw this.internalError(pcx, `untranslatable comparison operator '${op}'`);
|
|
940
931
|
}
|
|
941
932
|
visitExprCountDisinct(pcx) {
|
|
942
|
-
|
|
943
|
-
this.contextError(pcx, '`count(distinct expression)` deprecated, use `count(expression)` instead', 'warn');
|
|
944
|
-
}
|
|
933
|
+
this.m4advisory(pcx, '`count(distinct expression)` deprecated, use `count(expression)` instead');
|
|
945
934
|
return this.astAt(new ast.ExprCountDistinct(this.getFieldExpr(pcx.fieldExpr())), pcx);
|
|
946
935
|
}
|
|
947
936
|
visitExprUngroup(pcx) {
|
|
@@ -1008,10 +997,8 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1008
997
|
const expr = exprDef ? this.getFieldExpr(exprDef) : undefined;
|
|
1009
998
|
const source = undefined;
|
|
1010
999
|
const aggFunc = pcx.aggregate().text.toLowerCase();
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1013
|
-
this.contextError(pcx, `* illegal inside ${aggFunc}()`, 'warn');
|
|
1014
|
-
}
|
|
1000
|
+
if (pcx.STAR()) {
|
|
1001
|
+
this.m4advisory(pcx, `* illegal inside ${aggFunc}()`);
|
|
1015
1002
|
}
|
|
1016
1003
|
if (aggFunc === 'count') {
|
|
1017
1004
|
return this.astAt(expr ? new ast.ExprCountDistinct(expr) : new ast.ExprCount(), pcx);
|
|
@@ -1214,9 +1201,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1214
1201
|
}
|
|
1215
1202
|
stmt.is = blockName;
|
|
1216
1203
|
const result = this.astAt(stmt, pcx);
|
|
1217
|
-
|
|
1218
|
-
this.astError(result, '`sql:` statement is deprecated, use `connection_name.sql(...)` instead', 'warn');
|
|
1219
|
-
}
|
|
1204
|
+
this.m4advisory(pcx, '`sql:` statement is deprecated, use `connection_name.sql(...)` instead');
|
|
1220
1205
|
return result;
|
|
1221
1206
|
}
|
|
1222
1207
|
visitSampleStatement(pcx) {
|
|
@@ -1266,11 +1251,11 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1266
1251
|
}
|
|
1267
1252
|
const sqExpr = this.getSqExpr(pcx.sqExpr());
|
|
1268
1253
|
const hasPlus = !!pcx.PLUS();
|
|
1269
|
-
return this.astAt(new ast.SQLegacyModify(sqExpr, plus, hasPlus, this.
|
|
1254
|
+
return this.astAt(new ast.SQLegacyModify(sqExpr, plus, hasPlus, this.m4Severity()), pcx);
|
|
1270
1255
|
}
|
|
1271
1256
|
visitSQID(pcx) {
|
|
1272
|
-
if (
|
|
1273
|
-
this.
|
|
1257
|
+
if (pcx.ARROW()) {
|
|
1258
|
+
this.m4advisory(pcx, 'Leading arrow (`->`) when referencing a query is deprecated; remove the arrow');
|
|
1274
1259
|
}
|
|
1275
1260
|
const ref = this.getModelEntryName(pcx);
|
|
1276
1261
|
return this.astAt(new ast.SQReference(ref), pcx.id());
|
|
@@ -1323,16 +1308,16 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1323
1308
|
visitSQRefinedQuery(pcx) {
|
|
1324
1309
|
const refineThis = this.getSqExpr(pcx.sqExpr());
|
|
1325
1310
|
const refine = pcx.queryRefinement();
|
|
1326
|
-
if (
|
|
1327
|
-
this.
|
|
1311
|
+
if (refine.REFINE()) {
|
|
1312
|
+
this.m4advisory(refine, 'The `refine` keyword is deprecated, use the `+` operator');
|
|
1328
1313
|
}
|
|
1329
1314
|
const refined = new ast.SQRefinedQuery(refineThis, this.getQueryRefinements(refine));
|
|
1330
1315
|
return this.astAt(refined, pcx);
|
|
1331
1316
|
}
|
|
1332
1317
|
visitSQFrom(pcx) {
|
|
1333
1318
|
const fromThis = this.getSqExpr(pcx.sqExpr());
|
|
1334
|
-
if (pcx.FROM()
|
|
1335
|
-
this.
|
|
1319
|
+
if (pcx.FROM()) {
|
|
1320
|
+
this.m4advisory(pcx, '`from(some_query)` is deprecated; use `some_query` directly');
|
|
1336
1321
|
}
|
|
1337
1322
|
return this.astAt(new ast.SQFrom(fromThis), pcx);
|
|
1338
1323
|
}
|
|
@@ -1347,6 +1332,7 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1347
1332
|
visitSQLegacySQLBlock(pcx) {
|
|
1348
1333
|
const theBlock = this.getLegacySQLSouce(pcx.sqlExploreNameRef());
|
|
1349
1334
|
const sqExpr = new ast.SQSourceWrapper(theBlock);
|
|
1335
|
+
this.m4advisory(pcx, '`from_sql` is deprecated; use `connection_name.sql(...)` as a source directly');
|
|
1350
1336
|
return this.astAt(sqExpr, pcx);
|
|
1351
1337
|
}
|
|
1352
1338
|
visitSQSQL(pcx) {
|
|
@@ -189,8 +189,8 @@ class ParseStep {
|
|
|
189
189
|
sourceURL: that.sourceURL,
|
|
190
190
|
importBaseURL: that.importBaseURL || that.sourceURL,
|
|
191
191
|
rangeFromContext: cx => that.rangeFromContext(cx),
|
|
192
|
-
// TODO
|
|
193
|
-
malloyVersion: '
|
|
192
|
+
// TODO some way to not forget to update this
|
|
193
|
+
malloyVersion: '4.0.0',
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
}
|
|
@@ -29,7 +29,7 @@ function testHelpContext(source, position, type, token) {
|
|
|
29
29
|
const helpContext = doc.helpContext(position).helpContext;
|
|
30
30
|
expect(helpContext).toEqual({ type, token });
|
|
31
31
|
}
|
|
32
|
-
const source = `source: foo is table('bar') {
|
|
32
|
+
const source = `source: foo is DB.table('bar') extend {
|
|
33
33
|
where: bazz ~ 'biff'
|
|
34
34
|
query: foo is {
|
|
35
35
|
group_by: bazz
|
|
@@ -61,7 +61,7 @@ function testLens(source, path) {
|
|
|
61
61
|
expect(current).toMatchObject(expected);
|
|
62
62
|
}
|
|
63
63
|
test('source symbols are included', () => {
|
|
64
|
-
testSymbol((0, test_translator_1.markSource) `source: ${"flights is table('my.table.flights')"}`, 'flights', 'explore', [0]);
|
|
64
|
+
testSymbol((0, test_translator_1.markSource) `source: ${"flights is DB.table('my.table.flights')"}`, 'flights', 'explore', [0]);
|
|
65
65
|
});
|
|
66
66
|
test('query symbols are included', () => {
|
|
67
67
|
testSymbol((0, test_translator_1.markSource) `query: ${'flights_by_carrier is flights -> by_carrier'}`, 'flights_by_carrier', 'query', [0]);
|
|
@@ -75,56 +75,56 @@ test('run (ref) symbols are included', () => {
|
|
|
75
75
|
});
|
|
76
76
|
test('expression field defs are included', () => {
|
|
77
77
|
testSymbol((0, test_translator_1.markSource) `
|
|
78
|
-
source: flights is table('my.table.flights') {
|
|
78
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
79
79
|
dimension: ${'one is 1'}
|
|
80
80
|
}
|
|
81
81
|
`, 'one', 'field', [0, 0]);
|
|
82
82
|
});
|
|
83
83
|
test('renamed fields are included', () => {
|
|
84
84
|
testSymbol((0, test_translator_1.markSource) `
|
|
85
|
-
source: flights is table('my.table.flights') {
|
|
85
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
86
86
|
rename: ${'field_two is field_2'}
|
|
87
87
|
}
|
|
88
88
|
`, 'field_two', 'field', [0, 0]);
|
|
89
89
|
});
|
|
90
90
|
test('name only fields are included', () => {
|
|
91
91
|
testSymbol((0, test_translator_1.markSource) `
|
|
92
|
-
source: flights is table('my.table.flights') {
|
|
92
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
93
93
|
dimension: ${'field_two is field_2'}
|
|
94
94
|
}
|
|
95
95
|
`, 'field_two', 'field', [0, 0]);
|
|
96
96
|
});
|
|
97
97
|
test('turtle fields are included', () => {
|
|
98
98
|
testSymbol((0, test_translator_1.markSource) `
|
|
99
|
-
source: flights is table('my.table.flights') {
|
|
99
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
100
100
|
query: ${'my_turtle is { group_by: a }'}
|
|
101
101
|
}
|
|
102
102
|
`, 'my_turtle', 'query', [0, 0]);
|
|
103
103
|
});
|
|
104
104
|
test('turtle children fields are included', () => {
|
|
105
105
|
testSymbol((0, test_translator_1.markSource) `
|
|
106
|
-
source: flights is table('my.table.flights') {
|
|
106
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
107
107
|
query: my_turtle is { group_by: ${'a'} }
|
|
108
108
|
}
|
|
109
109
|
`, 'a', 'field', [0, 0, 0]);
|
|
110
110
|
});
|
|
111
111
|
test('turtle children turtles are included', () => {
|
|
112
112
|
testSymbol((0, test_translator_1.markSource) `
|
|
113
|
-
source: flights is table('my.table.flights') {
|
|
113
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
114
114
|
query: my_turtle is { nest: ${'inner_turtle is { group_by: a }'} }
|
|
115
115
|
}
|
|
116
116
|
`, 'inner_turtle', 'query', [0, 0, 0]);
|
|
117
117
|
});
|
|
118
118
|
test('join withs are included', () => {
|
|
119
119
|
testSymbol((0, test_translator_1.markSource) `
|
|
120
|
-
source: flights is table('my.table.flights') {
|
|
120
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
121
121
|
join_one: ${'a is b with c'}
|
|
122
122
|
}
|
|
123
123
|
`, 'a', 'join', [0, 0]);
|
|
124
124
|
});
|
|
125
125
|
test('join ons are included', () => {
|
|
126
126
|
testSymbol((0, test_translator_1.markSource) `
|
|
127
|
-
source: flights is table('my.table.flights') {
|
|
127
|
+
source: flights is DB.table('my.table.flights') extend {
|
|
128
128
|
join_one: ${'a is b on c'}
|
|
129
129
|
}
|
|
130
130
|
`, 'a', 'join', [0, 0]);
|
|
@@ -134,15 +134,15 @@ test('source lenses go before block annotations when one source', () => {
|
|
|
134
134
|
# tag2
|
|
135
135
|
source:
|
|
136
136
|
# tag3
|
|
137
|
-
flights is table('my.table.flights')`}`, [0]);
|
|
137
|
+
flights is DB.table('my.table.flights')`}`, [0]);
|
|
138
138
|
});
|
|
139
139
|
test('source lenses go before individual annotations when more than one source', () => {
|
|
140
140
|
testLens((0, test_translator_1.markSource) `# tag
|
|
141
141
|
source:
|
|
142
142
|
${`# tag2
|
|
143
|
-
flights is table('my.table.flights')`}
|
|
143
|
+
flights is DB.table('my.table.flights')`}
|
|
144
144
|
|
|
145
|
-
flights2 is table('my.table.flights')`, [0]);
|
|
145
|
+
flights2 is DB.table('my.table.flights')`, [0]);
|
|
146
146
|
});
|
|
147
147
|
test('query lenses go before block annotations when one source', () => {
|
|
148
148
|
testLens((0, test_translator_1.markSource) `${`# tag
|
|
@@ -174,7 +174,7 @@ test('run lenses go before block annotations', () => {
|
|
|
174
174
|
flights -> by_carrier`}`, [0]);
|
|
175
175
|
});
|
|
176
176
|
test('(regression) query does not use source block range', () => {
|
|
177
|
-
testLens((0, test_translator_1.markSource) `source: a is table('b') {
|
|
177
|
+
testLens((0, test_translator_1.markSource) `source: a is DB.table('b') extend {
|
|
178
178
|
query:
|
|
179
179
|
${'x is {select: *}'}
|
|
180
180
|
y is {select: *}
|
|
@@ -152,7 +152,7 @@ describe('expressions', () => {
|
|
|
152
152
|
});
|
|
153
153
|
test('shortcut filtered measure m4warning', () => {
|
|
154
154
|
expect(`
|
|
155
|
-
##! m4warnings
|
|
155
|
+
##! m4warnings=warn
|
|
156
156
|
run: a -> {
|
|
157
157
|
group_by: ai
|
|
158
158
|
aggregate: x is avg(ai) {? astr = 'why?' }
|
|
@@ -173,43 +173,44 @@ describe('expressions', () => {
|
|
|
173
173
|
});
|
|
174
174
|
describe('aggregate forms', () => {
|
|
175
175
|
const m = (0, test_translator_1.model) `
|
|
176
|
-
source: root is a {
|
|
176
|
+
source: root is a extend {
|
|
177
177
|
rename: column is ai
|
|
178
178
|
rename: nested is astruct
|
|
179
179
|
rename: inline is aninline
|
|
180
180
|
dimension: field is column * 2
|
|
181
181
|
dimension: many_field is many.column * 2
|
|
182
182
|
dimension: many_one_field is many.column + one.column
|
|
183
|
-
join_one: one is a {
|
|
183
|
+
join_one: one is a extend {
|
|
184
184
|
rename: column is ai
|
|
185
185
|
dimension: field is column * 2
|
|
186
186
|
dimension: many_field is many.column * 2
|
|
187
|
-
join_many: many is a {
|
|
187
|
+
join_many: many is a extend {
|
|
188
188
|
rename: column is ai
|
|
189
189
|
dimension: field is column * 2
|
|
190
190
|
dimension: constant is 1
|
|
191
191
|
} on true
|
|
192
192
|
} on true
|
|
193
|
-
join_many: many is a {
|
|
193
|
+
join_many: many is a extend {
|
|
194
194
|
rename: column is ai
|
|
195
195
|
dimension: field is column * 2
|
|
196
196
|
dimension: constant is 1
|
|
197
|
-
join_one: one is a {
|
|
197
|
+
join_one: one is a extend {
|
|
198
198
|
rename: column is ai
|
|
199
199
|
dimension: field is column * 2
|
|
200
|
-
join_one: one is a {
|
|
200
|
+
join_one: one is a extend {
|
|
201
201
|
rename: column is ai
|
|
202
202
|
dimension: field is column * 2
|
|
203
203
|
} on true
|
|
204
204
|
} on true
|
|
205
205
|
} on true
|
|
206
|
-
join_cross: cross is a {
|
|
206
|
+
join_cross: cross is a extend {
|
|
207
207
|
rename: column is ai
|
|
208
208
|
dimension: field is column * 2
|
|
209
209
|
} on true
|
|
210
210
|
}
|
|
211
211
|
`;
|
|
212
212
|
m.translator.translate();
|
|
213
|
+
expect(m).toTranslate();
|
|
213
214
|
const modelX = (0, test_translator_1.makeModelFunc)({
|
|
214
215
|
model: m.translator.modelDef,
|
|
215
216
|
wrap: x => `run: root -> { aggregate: x is ${x} }`,
|
|
@@ -417,7 +418,7 @@ describe('expressions', () => {
|
|
|
417
418
|
});
|
|
418
419
|
test('lag(sum(output))', () => {
|
|
419
420
|
expect((0, test_translator_1.model) `
|
|
420
|
-
##! m4warnings
|
|
421
|
+
##! m4warnings=warn
|
|
421
422
|
run: a -> {
|
|
422
423
|
group_by: output is 1
|
|
423
424
|
calculate: bar is lag(sum(output))
|
|
@@ -425,7 +426,7 @@ describe('expressions', () => {
|
|
|
425
426
|
});
|
|
426
427
|
test('count(distinct column)', () => {
|
|
427
428
|
expect((0, test_translator_1.model) `
|
|
428
|
-
##! m4warnings
|
|
429
|
+
##! m4warnings=warn
|
|
429
430
|
run: a -> {
|
|
430
431
|
aggregate: x is count(distinct astr)
|
|
431
432
|
}`).toTranslateWithWarnings('`count(distinct expression)` deprecated, use `count(expression)` instead');
|
|
@@ -51,7 +51,7 @@ describe('model statements', () => {
|
|
|
51
51
|
expect(m).translationToFailWith('a1 is not a connection');
|
|
52
52
|
});
|
|
53
53
|
test('table function is deprecated', () => {
|
|
54
|
-
expect(`##! m4warnings
|
|
54
|
+
expect(`##! m4warnings=warn
|
|
55
55
|
source: testA is table('_db_:aTable')
|
|
56
56
|
`).toTranslateWithWarnings("`table('connection_name:table_path')` is deprecated; use `connection_name.table('table_path')`");
|
|
57
57
|
});
|
|
@@ -541,7 +541,7 @@ describe('sql expressions', () => {
|
|
|
541
541
|
}
|
|
542
542
|
});
|
|
543
543
|
test('sql statement deprecation warning', () => {
|
|
544
|
-
const m = new test_translator_1.TestTranslator(`##! m4warnings
|
|
544
|
+
const m = new test_translator_1.TestTranslator(`##! m4warnings=warn
|
|
545
545
|
sql: bad_sql is {select: """SELECT 1 as one"""}`);
|
|
546
546
|
const req = m.translate().compileSQL;
|
|
547
547
|
if (req) {
|
|
@@ -552,7 +552,7 @@ describe('sql expressions', () => {
|
|
|
552
552
|
expect(m).toTranslateWithWarnings('`sql:` statement is deprecated, use `connection_name.sql(...)` instead');
|
|
553
553
|
});
|
|
554
554
|
test('from_sql deprecation warning', () => {
|
|
555
|
-
const m = new test_translator_1.TestTranslator(`##! m4warnings
|
|
555
|
+
const m = new test_translator_1.TestTranslator(`##! m4warnings=warn
|
|
556
556
|
sql: bad_sql is {select: """SELECT 1 as one"""}
|
|
557
557
|
source: foo is from_sql(bad_sql)`);
|
|
558
558
|
const req = m.translate().compileSQL;
|
|
@@ -812,7 +812,7 @@ describe('extend and refine', () => {
|
|
|
812
812
|
});
|
|
813
813
|
describe('m4 warnings', () => {
|
|
814
814
|
test('implicit refine in nest', () => {
|
|
815
|
-
expect(`##! m4warnings
|
|
815
|
+
expect(`##! m4warnings=warn
|
|
816
816
|
source: c is a extend {
|
|
817
817
|
view: x is { select: * }
|
|
818
818
|
}
|
|
@@ -822,20 +822,20 @@ describe('extend and refine', () => {
|
|
|
822
822
|
}`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the `+` operator');
|
|
823
823
|
});
|
|
824
824
|
test('implicit refine in turtle works', () => {
|
|
825
|
-
expect(`##! m4warnings
|
|
825
|
+
expect(`##! m4warnings=warn
|
|
826
826
|
source: c is a extend {
|
|
827
827
|
view: x is { select: * }
|
|
828
828
|
view: y is x { limit: 1 }
|
|
829
829
|
}`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the `+` operator');
|
|
830
830
|
});
|
|
831
831
|
test('implicit query refinement', () => {
|
|
832
|
-
expect(`##! m4warnings
|
|
832
|
+
expect(`##! m4warnings=warn
|
|
833
833
|
query: q is a -> { group_by: ai }
|
|
834
834
|
run: q { group_by: three is 3 }
|
|
835
835
|
`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the `+` operator');
|
|
836
836
|
});
|
|
837
837
|
test('implicit source extension', () => {
|
|
838
|
-
expect(`##! m4warnings
|
|
838
|
+
expect(`##! m4warnings=warn
|
|
839
839
|
source: s is a { dimension: ai_2 is ai + ai }`).toTranslateWithWarnings('Implicit source extension is deprecated, use the `extend` operator.');
|
|
840
840
|
});
|
|
841
841
|
});
|
|
@@ -843,13 +843,13 @@ describe('extend and refine', () => {
|
|
|
843
843
|
describe('miscellaneous m4 warnings', () => {
|
|
844
844
|
test('from() is deprecated', () => {
|
|
845
845
|
expect(`
|
|
846
|
-
##! m4warnings
|
|
846
|
+
##! m4warnings=warn
|
|
847
847
|
query: q is a -> { select: * }
|
|
848
848
|
source: c is from(q) extend {
|
|
849
849
|
dimension: x is 1
|
|
850
850
|
}`).toTranslateWithWarnings('`from(some_query)` is deprecated; use `some_query` directly');
|
|
851
851
|
expect(`
|
|
852
|
-
##! m4warnings
|
|
852
|
+
##! m4warnings=warn
|
|
853
853
|
query: q is a -> { select: * }
|
|
854
854
|
source: c is q extend {
|
|
855
855
|
dimension: x is 1
|
|
@@ -857,28 +857,28 @@ describe('miscellaneous m4 warnings', () => {
|
|
|
857
857
|
});
|
|
858
858
|
test('project is deprecated', () => {
|
|
859
859
|
expect(`
|
|
860
|
-
##! m4warnings
|
|
860
|
+
##! m4warnings=warn
|
|
861
861
|
query: q is a -> { project: * }
|
|
862
862
|
`).toTranslateWithWarnings('project: keyword is deprecated, use select:');
|
|
863
863
|
});
|
|
864
864
|
test('query leading arrow', () => {
|
|
865
865
|
expect(`
|
|
866
|
-
##! m4warnings
|
|
866
|
+
##! m4warnings=warn
|
|
867
867
|
query: x is a -> { select: * }
|
|
868
868
|
run: x
|
|
869
869
|
`).toTranslate();
|
|
870
870
|
expect(`
|
|
871
|
-
##! m4warnings
|
|
871
|
+
##! m4warnings=warn
|
|
872
872
|
query: x is a -> { select: * }
|
|
873
873
|
run: x -> { select: * }
|
|
874
874
|
`).toTranslate();
|
|
875
875
|
expect(`
|
|
876
|
-
##! m4warnings
|
|
876
|
+
##! m4warnings=warn
|
|
877
877
|
query: x is a -> { select: * }
|
|
878
878
|
run: -> x
|
|
879
879
|
`).toTranslateWithWarnings('Leading arrow (`->`) when referencing a query is deprecated; remove the arrow');
|
|
880
880
|
expect(`
|
|
881
|
-
##! m4warnings
|
|
881
|
+
##! m4warnings=warn
|
|
882
882
|
query: x is a -> { select: * }
|
|
883
883
|
run: -> x -> { select: * }
|
|
884
884
|
`).toTranslateWithWarnings('Leading arrow (`->`) when referencing a query is deprecated; remove the arrow');
|
|
@@ -29,7 +29,7 @@ describe('query:', () => {
|
|
|
29
29
|
describe('basic query syntax', () => {
|
|
30
30
|
test('run:anonymous query', () => expect('run: a -> { group_by: astr }').toTranslate());
|
|
31
31
|
test('query:anonymous query m4 warning', () => {
|
|
32
|
-
expect((0, test_translator_1.markSource) `##! m4warnings
|
|
32
|
+
expect((0, test_translator_1.markSource) `##! m4warnings=warn
|
|
33
33
|
query: ${'a -> { group_by: astr }'}`).toTranslateWithWarnings('Anonymous `query:` statements are deprecated, use `run:` instead');
|
|
34
34
|
});
|
|
35
35
|
test('named query:', () => expect('query: aq is a -> { group_by: astr }').toTranslate());
|
|
@@ -67,7 +67,7 @@ describe('query:', () => {
|
|
|
67
67
|
});
|
|
68
68
|
test('query with shortcut filtered turtle', () => {
|
|
69
69
|
expect(`##! -m4warnings
|
|
70
|
-
query: allA is ab -> aturtle + {
|
|
70
|
+
query: allA is ab -> aturtle + { where: astr ~ 'a%' }`).toTranslate();
|
|
71
71
|
});
|
|
72
72
|
test('query with filtered turtle', () => {
|
|
73
73
|
expect("query: allA is ab -> aturtle + { where: astr ~ 'a%' }").toTranslate();
|
|
@@ -215,24 +215,24 @@ describe('query:', () => {
|
|
|
215
215
|
expect('source: a1 is a extend { measure: s is lag(count())}').translationToFailWith('Cannot use an analytic field in a measure declaration');
|
|
216
216
|
});
|
|
217
217
|
test('cannot use scalar in aggregate', () => {
|
|
218
|
-
expect('run: a -> { aggregate: s is 1}').translationToFailWith('Cannot use a scalar field in an aggregate operation, did you mean to use a group_by or
|
|
218
|
+
expect('run: a -> { aggregate: s is 1}').translationToFailWith('Cannot use a scalar field in an aggregate operation, did you mean to use a group_by or select operation instead?');
|
|
219
219
|
});
|
|
220
220
|
test('cannot use analytic in aggregate', () => {
|
|
221
221
|
expect('run: a -> { aggregate: s is lag(count())}').translationToFailWith('Cannot use an analytic field in an aggregate operation, did you mean to use a calculate operation instead?');
|
|
222
222
|
});
|
|
223
223
|
test('cannot use scalar in calculate', () => {
|
|
224
|
-
expect('run: a -> { group_by: a is 1; calculate: s is 1 }').translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or
|
|
224
|
+
expect('run: a -> { group_by: a is 1; calculate: s is 1 }').translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or select operation instead?');
|
|
225
225
|
});
|
|
226
226
|
test('cannot use aggregate in calculate', () => {
|
|
227
227
|
expect('run: a -> { group_by: a is 1; calculate: s is count() }').translationToFailWith('Cannot use an aggregate field in a calculate operation, did you mean to use an aggregate operation instead?');
|
|
228
228
|
});
|
|
229
229
|
test('cannot use aggregate in project', () => {
|
|
230
|
-
expect('run: a -> { select: s is count() }').translationToFailWith('Cannot use an aggregate field in a
|
|
230
|
+
expect('run: a -> { select: s is count() }').translationToFailWith('Cannot use an aggregate field in a select operation, did you mean to use an aggregate operation instead?');
|
|
231
231
|
});
|
|
232
232
|
test('cannot use analytic in project', () => {
|
|
233
|
-
expect('run: a -> { select: s is row_number() }').translationToFailWith('Cannot use an analytic field in a
|
|
233
|
+
expect('run: a -> { select: s is row_number() }').translationToFailWith('Cannot use an analytic field in a select operation, did you mean to use a calculate operation instead?');
|
|
234
234
|
});
|
|
235
|
-
test('cannot use analytic in
|
|
235
|
+
test('cannot use analytic in extended source', () => {
|
|
236
236
|
expect(`##! -m4warnings
|
|
237
237
|
run: a -> { group_by: a is 1; declare: s is row_number() }`).translationToFailWith('Analytic expressions can not be used in a declare block');
|
|
238
238
|
});
|
|
@@ -254,16 +254,16 @@ describe('query:', () => {
|
|
|
254
254
|
expect('run: a extend { view: q is { group_by: x is 1 } } -> { group_by: q }').translationToFailWith('Cannot use a view field in a group_by operation, did you mean to use a nest operation instead?');
|
|
255
255
|
});
|
|
256
256
|
test('cannot use scalar in aggregate', () => {
|
|
257
|
-
expect('run: a -> { extend: {dimension: aconst is 1} aggregate: aconst }').translationToFailWith('Cannot use a scalar field in an aggregate operation, did you mean to use a group_by or
|
|
257
|
+
expect('run: a -> { extend: {dimension: aconst is 1} aggregate: aconst }').translationToFailWith('Cannot use a scalar field in an aggregate operation, did you mean to use a group_by or select operation instead?');
|
|
258
258
|
});
|
|
259
259
|
test('cannot use scalar in calculate', () => {
|
|
260
|
-
expect('run: a -> { extend: {dimension: aconst is 1} group_by: x is 1; calculate: aconst }').translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or
|
|
260
|
+
expect('run: a -> { extend: {dimension: aconst is 1} group_by: x is 1; calculate: aconst }').translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or select operation instead?');
|
|
261
261
|
});
|
|
262
262
|
test('cannot use aggregate in calculate', () => {
|
|
263
263
|
expect('run: a -> { extend: {measure: acount is count()} group_by: x is 1; calculate: acount }').translationToFailWith('Cannot use an aggregate field in a calculate operation, did you mean to use an aggregate operation instead?');
|
|
264
264
|
});
|
|
265
265
|
test('cannot use query in project', () => {
|
|
266
|
-
expect('run: a extend { view: q is { group_by: x is 1 } } -> { select: q }').translationToFailWith('Cannot use a view field in a
|
|
266
|
+
expect('run: a extend { view: q is { group_by: x is 1 } } -> { select: q }').translationToFailWith('Cannot use a view field in a select operation, did you mean to use a nest operation instead?');
|
|
267
267
|
});
|
|
268
268
|
test('cannot use query in index', () => {
|
|
269
269
|
expect('run: a extend { view: q is { group_by: x is 1 } } -> { index: q }').translationToFailWith('Cannot use a view field in an index operation');
|
|
@@ -288,7 +288,7 @@ describe('query:', () => {
|
|
|
288
288
|
}
|
|
289
289
|
run: a1 + {
|
|
290
290
|
calculate: b is c
|
|
291
|
-
}`).translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or
|
|
291
|
+
}`).translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or select operation instead?');
|
|
292
292
|
});
|
|
293
293
|
test('cannot use analytic in group_by, preserved over refinement', () => {
|
|
294
294
|
expect(`query: a1 is a -> {
|
|
@@ -765,11 +765,11 @@ describe('query:', () => {
|
|
|
765
765
|
expect('run: a->{ top: 5; group_by: astr }').toTranslate();
|
|
766
766
|
});
|
|
767
767
|
test('top N by field', () => {
|
|
768
|
-
expect(`##! m4warnings
|
|
768
|
+
expect(`##! m4warnings=warn
|
|
769
769
|
run: a->{top: 5 ${'by astr'}; group_by: astr}`).toTranslateWithWarnings('by clause of top statement unupported. Use order_by instead');
|
|
770
770
|
});
|
|
771
771
|
test('top N by expression', () => {
|
|
772
|
-
expect(`##! m4warnings
|
|
772
|
+
expect(`##! m4warnings=warn
|
|
773
773
|
run: ab->{top: 5 by ai + 1; group_by: ai}`).toTranslateWithWarnings('by clause of top statement unupported. Use order_by instead');
|
|
774
774
|
});
|
|
775
775
|
test('limit N', () => {
|
|
@@ -895,15 +895,15 @@ describe('query:', () => {
|
|
|
895
895
|
});
|
|
896
896
|
describe('declare/query join warnings', () => {
|
|
897
897
|
test('declare warning in query', () => {
|
|
898
|
-
expect((0, test_translator_1.markSource) `##! m4warnings
|
|
898
|
+
expect((0, test_translator_1.markSource) `##! m4warnings=warn
|
|
899
899
|
run: a -> { ${'declare: x is 1'}; group_by: x }`).toTranslateWithWarnings('`declare:` is deprecated; use `dimension:` or `measure:` inside a source or `extend:` block');
|
|
900
900
|
});
|
|
901
901
|
test('declare warning in source', () => {
|
|
902
|
-
expect((0, test_translator_1.markSource) `##! m4warnings
|
|
902
|
+
expect((0, test_translator_1.markSource) `##! m4warnings=warn
|
|
903
903
|
source: a2 is a extend { ${'declare: x is 1'} }`).toTranslateWithWarnings('`declare:` is deprecated; use `dimension:` or `measure:` inside a source or `extend:` block');
|
|
904
904
|
});
|
|
905
905
|
test('joins in query', () => {
|
|
906
|
-
expect((0, test_translator_1.markSource) `##! m4warnings
|
|
906
|
+
expect((0, test_translator_1.markSource) `##! m4warnings=warn
|
|
907
907
|
run: a -> { ${'join_one: b on true'}; ${'join_many: c is b on true'}; ${'join_cross: d is b on true'}; group_by: b.astr }`).toTranslateWithWarnings('Joins in queries are deprecated, move into an `extend:` block.', 'Joins in queries are deprecated, move into an `extend:` block.', 'Joins in queries are deprecated, move into an `extend:` block.');
|
|
908
908
|
});
|
|
909
909
|
});
|
|
@@ -34,7 +34,7 @@ describe('source:', () => {
|
|
|
34
34
|
});
|
|
35
35
|
test('shorcut fitlered table m4warning', () => {
|
|
36
36
|
expect(`
|
|
37
|
-
##! m4warnings
|
|
37
|
+
##! m4warnings=warn
|
|
38
38
|
source: xA is _db_.table('aTable') extend {? astr ~ 'a%' }
|
|
39
39
|
`).toTranslateWithWarnings('Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead');
|
|
40
40
|
});
|
|
@@ -210,12 +210,12 @@ describe('source:', () => {
|
|
|
210
210
|
expect('source: c is a extend {view: q is { group_by: astr } }').toTranslate();
|
|
211
211
|
});
|
|
212
212
|
test('turtle in source can be called query with m4 warning', () => {
|
|
213
|
-
expect(`##! m4warnings
|
|
213
|
+
expect(`##! m4warnings=warn
|
|
214
214
|
source: c is a extend {query: q is { group_by: astr } }
|
|
215
215
|
`).toTranslateWithWarnings('Use view: inside of a source instead of query:');
|
|
216
216
|
});
|
|
217
217
|
test('turtle in source m4 warning on arrow', () => {
|
|
218
|
-
expect(`##! m4warnings
|
|
218
|
+
expect(`##! m4warnings=warn
|
|
219
219
|
source: c is a extend {view: q is -> { group_by: astr } }
|
|
220
220
|
`).toTranslateWithWarnings("Leading '->' in a view or nest definition is no longer needed.");
|
|
221
221
|
});
|
|
@@ -228,7 +228,7 @@ describe('source:', () => {
|
|
|
228
228
|
});
|
|
229
229
|
test('refined explore-query m4warning', () => {
|
|
230
230
|
expect(`
|
|
231
|
-
##! m4warnings
|
|
231
|
+
##! m4warnings=warn
|
|
232
232
|
source: abNew is ab extend {
|
|
233
233
|
view: for1 is aturtle + {? ai = 1 }
|
|
234
234
|
}
|
|
@@ -94,7 +94,7 @@ describe('sql:', () => {
|
|
|
94
94
|
const model = new test_translator_1.TestTranslator(`
|
|
95
95
|
##! -m4warnings
|
|
96
96
|
sql: users IS { select: """${selStmt}""" }
|
|
97
|
-
source: malloyUsers is from_sql(users) { primary_key: ai }
|
|
97
|
+
source: malloyUsers is from_sql(users) extend { primary_key: ai }
|
|
98
98
|
`);
|
|
99
99
|
expect(model).toParse();
|
|
100
100
|
const needReq = model.translate();
|
|
@@ -222,7 +222,7 @@ describe('sql:', () => {
|
|
|
222
222
|
const model = new test_translator_1.TestTranslator(`
|
|
223
223
|
##! -m4warnings
|
|
224
224
|
sql: sql_block IS { select: """${selStmt}""" }
|
|
225
|
-
source: malloy_source is from_sql(sql_block) { primary_key: ai }
|
|
225
|
+
source: malloy_source is from_sql(sql_block) extend { primary_key: ai }
|
|
226
226
|
`);
|
|
227
227
|
const sql = (0, sql_block_1.makeSQLBlock)([{ sql: selStmt }]);
|
|
228
228
|
model.update({ compileSQL: { [sql.name]: makeSchemaResponse(sql) } });
|
package/dist/malloy.js
CHANGED
|
@@ -1589,7 +1589,7 @@ class QueryQuery extends QueryField {
|
|
|
1589
1589
|
const { as, field } = this.expandField(f);
|
|
1590
1590
|
if (field instanceof QueryTurtle || field instanceof QueryQuery) {
|
|
1591
1591
|
if (this.firstSegment.type === 'project') {
|
|
1592
|
-
throw new Error(`
|
|
1592
|
+
throw new Error(`Nested views cannot be used in select - '${field.fieldDef.name}'`);
|
|
1593
1593
|
}
|
|
1594
1594
|
const fir = new FieldInstanceResult(field.fieldDef, resultStruct);
|
|
1595
1595
|
this.expandFields(fir);
|
|
@@ -1603,7 +1603,7 @@ class QueryQuery extends QueryField {
|
|
|
1603
1603
|
this.addDependancies(resultStruct, field);
|
|
1604
1604
|
if (isAggregateField(field)) {
|
|
1605
1605
|
if (this.firstSegment.type === 'project') {
|
|
1606
|
-
throw new Error(`Aggregate Fields cannot be used in
|
|
1606
|
+
throw new Error(`Aggregate Fields cannot be used in select - '${field.fieldDef.name}'`);
|
|
1607
1607
|
}
|
|
1608
1608
|
}
|
|
1609
1609
|
// } else if (field instanceof QueryStruct) {
|
package/dist/tags.js
CHANGED
|
@@ -272,20 +272,27 @@ function getBuildOn(ctx) {
|
|
|
272
272
|
}
|
|
273
273
|
return new Tag();
|
|
274
274
|
}
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
/**
|
|
276
|
+
* When chasing a path reference, the two interesting gestures are to
|
|
277
|
+
* find the path-ed tag so it can be extended, or to find the path tag
|
|
278
|
+
* so it can be deleted. This returns the parent and the final tag
|
|
279
|
+
* so that the caller can delete the tag with delete parent.tagName
|
|
280
|
+
* or assign to it with parent[tagName] = new_value
|
|
281
|
+
*/
|
|
282
|
+
function pathToAccess(buildOn, path) {
|
|
283
|
+
let parentPropertyObject = buildOn.getProperties();
|
|
277
284
|
for (const p of path.slice(0, path.length - 1)) {
|
|
278
285
|
let next;
|
|
279
|
-
if (
|
|
286
|
+
if (parentPropertyObject[p] === undefined) {
|
|
280
287
|
next = new Tag({});
|
|
281
|
-
|
|
288
|
+
parentPropertyObject[p] = next;
|
|
282
289
|
}
|
|
283
290
|
else {
|
|
284
|
-
next = Tag.tagFrom(
|
|
291
|
+
next = Tag.tagFrom(parentPropertyObject[p]);
|
|
285
292
|
}
|
|
286
|
-
|
|
293
|
+
parentPropertyObject = next.getProperties();
|
|
287
294
|
}
|
|
288
|
-
return [path[path.length - 1],
|
|
295
|
+
return [path[path.length - 1], parentPropertyObject];
|
|
289
296
|
}
|
|
290
297
|
function getString(ctx) {
|
|
291
298
|
if (ctx.SQ_STRING() || ctx.DQ_STRING()) {
|
|
@@ -399,7 +406,7 @@ class TaglineParser extends tree_1.AbstractParseTreeVisitor {
|
|
|
399
406
|
visitTagEq(ctx) {
|
|
400
407
|
const buildOn = getBuildOn(ctx);
|
|
401
408
|
const name = this.getPropName(ctx.propName());
|
|
402
|
-
const [writeKey, writeInto] =
|
|
409
|
+
const [writeKey, writeInto] = pathToAccess(buildOn, name);
|
|
403
410
|
const eq = this.visit(ctx.eqValue());
|
|
404
411
|
const propCx = ctx.properties();
|
|
405
412
|
if (propCx) {
|
|
@@ -422,7 +429,7 @@ class TaglineParser extends tree_1.AbstractParseTreeVisitor {
|
|
|
422
429
|
visitTagReplaceProperties(ctx) {
|
|
423
430
|
const buildOn = getBuildOn(ctx);
|
|
424
431
|
const name = this.getPropName(ctx.propName());
|
|
425
|
-
const [writeKey, writeInto] =
|
|
432
|
+
const [writeKey, writeInto] = pathToAccess(buildOn, name);
|
|
426
433
|
const propCx = ctx.properties();
|
|
427
434
|
const props = this.visitProperties(propCx);
|
|
428
435
|
if (ctx.DOTTY() === undefined) {
|
|
@@ -438,11 +445,11 @@ class TaglineParser extends tree_1.AbstractParseTreeVisitor {
|
|
|
438
445
|
visitTagUpdateProperties(ctx) {
|
|
439
446
|
const buildOn = getBuildOn(ctx);
|
|
440
447
|
const name = this.getPropName(ctx.propName());
|
|
441
|
-
const [writeKey, writeInto] =
|
|
448
|
+
const [writeKey, writeInto] = pathToAccess(buildOn, name);
|
|
442
449
|
const propCx = ctx.properties();
|
|
443
450
|
propCx['buildOn'] = Tag.tagFrom(writeInto[writeKey]);
|
|
444
451
|
const props = this.visitProperties(propCx);
|
|
445
|
-
const thisObj = writeInto[writeKey] || {};
|
|
452
|
+
const thisObj = writeInto[writeKey] || new Tag({});
|
|
446
453
|
const properties = { ...thisObj.properties, ...props.dict };
|
|
447
454
|
writeInto[writeKey] = { ...thisObj, properties };
|
|
448
455
|
return buildOn;
|
|
@@ -450,12 +457,12 @@ class TaglineParser extends tree_1.AbstractParseTreeVisitor {
|
|
|
450
457
|
visitTagDef(ctx) {
|
|
451
458
|
const buildOn = getBuildOn(ctx);
|
|
452
459
|
const path = this.getPropName(ctx.propName());
|
|
453
|
-
const [writeKey, writeInto] =
|
|
460
|
+
const [writeKey, writeInto] = pathToAccess(buildOn, path);
|
|
454
461
|
if (ctx.MINUS()) {
|
|
455
462
|
delete writeInto[writeKey];
|
|
456
463
|
}
|
|
457
464
|
else {
|
|
458
|
-
writeInto[writeKey] = {};
|
|
465
|
+
writeInto[writeKey] = new Tag({});
|
|
459
466
|
}
|
|
460
467
|
return buildOn;
|
|
461
468
|
}
|