@cloudcare/codemirror-dql 0.0.57 → 0.0.61
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/cjs/complete/dql.terms.d.ts +6 -0
- package/dist/cjs/complete/dql.terms.js +60 -4
- package/dist/cjs/complete/dql.terms.js.map +1 -1
- package/dist/cjs/complete/hybrid.d.ts +2 -1
- package/dist/cjs/complete/hybrid.js +49 -5
- package/dist/cjs/complete/hybrid.js.map +1 -1
- package/dist/cjs/complete/studioql.d.ts +3 -0
- package/dist/cjs/complete/studioql.js +159 -0
- package/dist/cjs/complete/studioql.js.map +1 -0
- package/dist/cjs/parser/parser.d.ts +16 -0
- package/dist/cjs/parser/parser.js +625 -30
- package/dist/cjs/parser/parser.js.map +1 -1
- package/dist/esm/complete/dql.terms.d.ts +6 -0
- package/dist/esm/complete/dql.terms.js +22 -1
- package/dist/esm/complete/dql.terms.js.map +1 -1
- package/dist/esm/complete/hybrid.d.ts +2 -1
- package/dist/esm/complete/hybrid.js +51 -7
- package/dist/esm/complete/hybrid.js.map +1 -1
- package/dist/esm/complete/studioql.d.ts +3 -0
- package/dist/esm/complete/studioql.js +144 -0
- package/dist/esm/complete/studioql.js.map +1 -0
- package/dist/esm/parser/parser.d.ts +16 -0
- package/dist/esm/parser/parser.js +588 -28
- package/dist/esm/parser/parser.js.map +1 -1
- package/package.json +3 -3
|
@@ -14,6 +14,7 @@ const queryClauseLabels = new Map([
|
|
|
14
14
|
['TargetClause', 'projection'],
|
|
15
15
|
['FilterClause', 'WHERE'],
|
|
16
16
|
['TimeClause', 'time range'],
|
|
17
|
+
['ShiftClause', 'SHIFT'],
|
|
17
18
|
['ByClause', 'BY'],
|
|
18
19
|
['HavingClause', 'HAVING'],
|
|
19
20
|
['OrderClause', 'ORDER BY'],
|
|
@@ -23,12 +24,151 @@ const queryClauseLabels = new Map([
|
|
|
23
24
|
['SLimitClause', 'SLIMIT'],
|
|
24
25
|
['SOffsetClause', 'SOFFSET'],
|
|
25
26
|
]);
|
|
26
|
-
const expressionParents = new Set(['BinaryExpr', 'Expr', 'HavingExpr', 'ProjectionExpr']);
|
|
27
|
+
const expressionParents = new Set(['BinaryExpr', 'Expr', 'HavingExpr', 'ProjectionExpr', 'CaseArithmeticExpr']);
|
|
27
28
|
const commaContextBoundaries = new Set(['CaseExpr', 'Query']);
|
|
29
|
+
const documentedFunctionArities = new Map([
|
|
30
|
+
...[
|
|
31
|
+
'abs',
|
|
32
|
+
'any',
|
|
33
|
+
'avg',
|
|
34
|
+
'bool',
|
|
35
|
+
'ceil',
|
|
36
|
+
'count',
|
|
37
|
+
'count_distinct',
|
|
38
|
+
'count_series',
|
|
39
|
+
'deriv',
|
|
40
|
+
'derivative',
|
|
41
|
+
'difference',
|
|
42
|
+
'distinct',
|
|
43
|
+
'field_values',
|
|
44
|
+
'first',
|
|
45
|
+
'float',
|
|
46
|
+
'floor',
|
|
47
|
+
'histogram_auto',
|
|
48
|
+
'increase',
|
|
49
|
+
'int',
|
|
50
|
+
'irate',
|
|
51
|
+
'last',
|
|
52
|
+
'last_row',
|
|
53
|
+
'length',
|
|
54
|
+
'log',
|
|
55
|
+
'log2',
|
|
56
|
+
'log10',
|
|
57
|
+
'lower',
|
|
58
|
+
'ltrim',
|
|
59
|
+
'max',
|
|
60
|
+
'md5',
|
|
61
|
+
'median',
|
|
62
|
+
'min',
|
|
63
|
+
'mode',
|
|
64
|
+
'non_negative_derivative',
|
|
65
|
+
'non_negative_difference',
|
|
66
|
+
'rate',
|
|
67
|
+
'rate_over_count',
|
|
68
|
+
'rate_over_sum',
|
|
69
|
+
'rtrim',
|
|
70
|
+
'set',
|
|
71
|
+
'spread',
|
|
72
|
+
'stddev',
|
|
73
|
+
'string',
|
|
74
|
+
'sum',
|
|
75
|
+
'trim',
|
|
76
|
+
'uint',
|
|
77
|
+
'unwrap',
|
|
78
|
+
'upper',
|
|
79
|
+
].map((name) => [name, { min: 1, max: 1 }]),
|
|
80
|
+
['bottom', { min: 2, max: 2 }],
|
|
81
|
+
['cidr', { min: 1, max: 2 }],
|
|
82
|
+
['collect', { min: 1, max: 2 }],
|
|
83
|
+
['collect_distinct', { min: 1, max: 2 }],
|
|
84
|
+
['concat', { min: 1, max: Number.POSITIVE_INFINITY }],
|
|
85
|
+
['count_filter', { min: 2, max: 2 }],
|
|
86
|
+
['default', { min: 2, max: 2 }],
|
|
87
|
+
['distinct_by_collapse', { min: 1, max: 2 }],
|
|
88
|
+
['drain', { min: 2, max: 3 }],
|
|
89
|
+
['exists', { min: 0, max: 0 }],
|
|
90
|
+
['fill', { min: 2, max: 2 }],
|
|
91
|
+
['histogram', { min: 4, max: 5 }],
|
|
92
|
+
['histogram_quantile', { min: 2, max: 2 }],
|
|
93
|
+
['match', { min: 1, max: 2 }],
|
|
94
|
+
['now', { min: 0, max: 0 }],
|
|
95
|
+
['query_string', { min: 1, max: 2 }],
|
|
96
|
+
['querystring', { min: 1, max: 2 }],
|
|
97
|
+
['re', { min: 1, max: 2 }],
|
|
98
|
+
['regexp', { min: 1, max: 2 }],
|
|
99
|
+
['regex', { min: 1, max: 2 }],
|
|
100
|
+
['regexp_extract', { min: 2, max: 3 }],
|
|
101
|
+
['regexp_extract_all', { min: 2, max: 3 }],
|
|
102
|
+
['regexp_replace', { min: 3, max: 3 }],
|
|
103
|
+
['round', { min: 1, max: 2 }],
|
|
104
|
+
['search', { min: 1, max: 2 }],
|
|
105
|
+
['substr', { min: 2, max: 3 }],
|
|
106
|
+
['top', { min: 2, max: 2 }],
|
|
107
|
+
['wildcard', { min: 1, max: 2 }],
|
|
108
|
+
]);
|
|
109
|
+
const stableShiftAggregateFunctions = new Set([
|
|
110
|
+
'any',
|
|
111
|
+
'avg',
|
|
112
|
+
'change_score',
|
|
113
|
+
'collect',
|
|
114
|
+
'collect_distinct',
|
|
115
|
+
'corr',
|
|
116
|
+
'count',
|
|
117
|
+
'count_distinct',
|
|
118
|
+
'count_filter',
|
|
119
|
+
'count_series',
|
|
120
|
+
'deriv',
|
|
121
|
+
'derivative',
|
|
122
|
+
'difference',
|
|
123
|
+
'ewma',
|
|
124
|
+
'first',
|
|
125
|
+
'histogram_quantile',
|
|
126
|
+
'increase',
|
|
127
|
+
'irate',
|
|
128
|
+
'last',
|
|
129
|
+
'last_row',
|
|
130
|
+
'mad_score',
|
|
131
|
+
'max',
|
|
132
|
+
'median',
|
|
133
|
+
'min',
|
|
134
|
+
'mode',
|
|
135
|
+
'moving_average',
|
|
136
|
+
'non_negative_derivative',
|
|
137
|
+
'non_negative_difference',
|
|
138
|
+
'p50',
|
|
139
|
+
'p75',
|
|
140
|
+
'p90',
|
|
141
|
+
'p95',
|
|
142
|
+
'p99',
|
|
143
|
+
'percentile',
|
|
144
|
+
'rate',
|
|
145
|
+
'rate_over_count',
|
|
146
|
+
'rate_over_sum',
|
|
147
|
+
'slope',
|
|
148
|
+
'spread',
|
|
149
|
+
'stddev',
|
|
150
|
+
'sum',
|
|
151
|
+
'zscore',
|
|
152
|
+
]);
|
|
153
|
+
const unstableShiftAggregateFunctions = new Set([
|
|
154
|
+
'distinct',
|
|
155
|
+
'distinct_by_collapse',
|
|
156
|
+
'drain',
|
|
157
|
+
'field_values',
|
|
158
|
+
'histogram',
|
|
159
|
+
'histogram_auto',
|
|
160
|
+
'schema',
|
|
161
|
+
]);
|
|
162
|
+
const caseScalarFunctions = new Set(['int', 'float', 'string', 'md5', 'lower', 'upper', 'trim', 'ltrim', 'rtrim', 'regexp_replace', 'length']);
|
|
28
163
|
export const dqlSyntaxContexts = {
|
|
29
164
|
DQL: 'DQL query',
|
|
30
165
|
DQLConditions: 'DQL condition',
|
|
31
166
|
QueryExpr: 'DQL query',
|
|
167
|
+
StudioQuery: 'STUDIOQL query',
|
|
168
|
+
StudioModule: 'STUDIOQL module',
|
|
169
|
+
StudioFunction: 'STUDIOQL function',
|
|
170
|
+
StudioCallBody: 'function arguments',
|
|
171
|
+
StudioArgument: 'named function argument',
|
|
32
172
|
ShowExpr: 'SHOW expression',
|
|
33
173
|
QueryFragment: 'query fragment',
|
|
34
174
|
QueryFragmentClause: 'query fragment clause',
|
|
@@ -60,8 +200,15 @@ export const dqlSyntaxContexts = {
|
|
|
60
200
|
OuterArgItem: 'outer function argument',
|
|
61
201
|
Queted: 'quoted query delimiter',
|
|
62
202
|
EvalCallBody: 'eval function arguments',
|
|
203
|
+
EvalArgItem: 'eval expression argument',
|
|
204
|
+
EvalExpression: 'eval expression',
|
|
205
|
+
EvalAtom: 'eval expression value',
|
|
206
|
+
EvalFunctionCall: 'eval function call',
|
|
207
|
+
EvalFunctionCallBody: 'eval function arguments',
|
|
208
|
+
EvalFunctionArg: 'eval function argument',
|
|
63
209
|
FunctionCallBody: 'function arguments',
|
|
64
210
|
ArgItem: 'function argument',
|
|
211
|
+
EvalFieldReference: 'eval field reference',
|
|
65
212
|
InValue: 'argument value list',
|
|
66
213
|
SingleExpr: 'expression value',
|
|
67
214
|
Expr: 'expression',
|
|
@@ -78,6 +225,8 @@ export const dqlSyntaxContexts = {
|
|
|
78
225
|
ProjectionFieldFragment: 'projection field fragment',
|
|
79
226
|
ProjectionField: 'projection field',
|
|
80
227
|
SingleFieldExpr: 'projection expression value',
|
|
228
|
+
ShiftExpr: 'SHIFT expression',
|
|
229
|
+
ShiftOperand: 'SHIFT operand',
|
|
81
230
|
ProjectionExpr: 'projection expression',
|
|
82
231
|
AsExpr: 'alias',
|
|
83
232
|
FilterClause: 'filter clause',
|
|
@@ -91,10 +240,10 @@ export const dqlSyntaxContexts = {
|
|
|
91
240
|
CaseCondition: 'CASE condition',
|
|
92
241
|
CaseFilterExpr: 'CASE filter expression',
|
|
93
242
|
CasePredicate: 'CASE predicate',
|
|
243
|
+
CaseComparableValue: 'CASE comparison value',
|
|
94
244
|
CaseValue: 'CASE value',
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
CaseFunctionCallBody: 'CASE function arguments',
|
|
245
|
+
CaseArithmeticAtom: 'CASE arithmetic value',
|
|
246
|
+
CaseArithmeticExpr: 'CASE arithmetic expression',
|
|
98
247
|
FilterFragmentExpr: 'filter fragment',
|
|
99
248
|
ComparisonExpr: 'comparison expression',
|
|
100
249
|
LogicalFragmentExpr: 'logical filter expression',
|
|
@@ -105,6 +254,7 @@ export const dqlSyntaxContexts = {
|
|
|
105
254
|
FilterExpr: 'filter expression',
|
|
106
255
|
BinaryExpr: 'binary expression',
|
|
107
256
|
TimeClause: 'time range clause',
|
|
257
|
+
ShiftClause: 'SHIFT clause',
|
|
108
258
|
TimeRange: 'time range',
|
|
109
259
|
TimeExpr: 'time range value',
|
|
110
260
|
ByClause: 'BY clause',
|
|
@@ -123,7 +273,9 @@ export const dqlSyntaxContexts = {
|
|
|
123
273
|
NestedByMathExpr: 'nested grouping expression',
|
|
124
274
|
};
|
|
125
275
|
const functionCallBodyParents = new Set([
|
|
276
|
+
'StudioCallBody',
|
|
126
277
|
'FunctionCallBody',
|
|
278
|
+
'EvalFunctionCallBody',
|
|
127
279
|
'EvalCallBody',
|
|
128
280
|
'OuterFunctionCallBody',
|
|
129
281
|
'RawOuterFunctionCallBody',
|
|
@@ -132,7 +284,6 @@ const functionCallBodyParents = new Set([
|
|
|
132
284
|
'FillOuterFunctionCallBody',
|
|
133
285
|
'VariadicOuterFunctionCallBody',
|
|
134
286
|
'RollupFunctionCallBody',
|
|
135
|
-
'CaseFunctionCallBody',
|
|
136
287
|
]);
|
|
137
288
|
export class Parser {
|
|
138
289
|
constructor(state, deadline = Date.now() + analysisBudgetMs) {
|
|
@@ -149,6 +300,7 @@ export class Parser {
|
|
|
149
300
|
this.quotedQueryDelimiterInfo = undefined;
|
|
150
301
|
this.caseResultDiagnostics = undefined;
|
|
151
302
|
this.caseResultRecoveryErrors = new Set();
|
|
303
|
+
this.caseShiftPresence = new Map();
|
|
152
304
|
}
|
|
153
305
|
getDiagnostics() {
|
|
154
306
|
const seen = new Set();
|
|
@@ -401,7 +553,7 @@ export class Parser {
|
|
|
401
553
|
this.appendDiagnostics(diagnostics);
|
|
402
554
|
}
|
|
403
555
|
describeError(node) {
|
|
404
|
-
var _a;
|
|
556
|
+
var _a, _b, _c;
|
|
405
557
|
const delimiterIssue = this.getDelimiterIssue();
|
|
406
558
|
if (delimiterIssue && delimiterIssue.kind !== 'unclosed' && this.isRelatedDelimiterError(node, delimiterIssue)) {
|
|
407
559
|
return null;
|
|
@@ -483,6 +635,26 @@ export class Parser {
|
|
|
483
635
|
}
|
|
484
636
|
return this.makeDiagnostic(node, 'expected "AS" before projection alias', group);
|
|
485
637
|
}
|
|
638
|
+
const projectionField = (_a = this.findAncestor(parent, 'ProjectionField')) !== null && _a !== void 0 ? _a : this.findAncestor(parent, 'ProjectionFieldFragment');
|
|
639
|
+
if (projectionField) {
|
|
640
|
+
const shiftError = this.errorDescendantsWithText(projectionField, 'shift').find((error) => error.from <= node.from);
|
|
641
|
+
if (shiftError && this.containsNode(projectionField, 'ShiftExpr')) {
|
|
642
|
+
return this.makeDiagnosticAt(shiftError.from, shiftError.to, 'SHIFT expressions cannot be nested', `shift-nested-syntax:${projectionField.from}`);
|
|
643
|
+
}
|
|
644
|
+
if (shiftError) {
|
|
645
|
+
const rawOperand = this.state.sliceDoc(projectionField.from, shiftError.from);
|
|
646
|
+
const leading = rawOperand.length - rawOperand.trimStart().length;
|
|
647
|
+
const trailing = rawOperand.length - rawOperand.trimEnd().length;
|
|
648
|
+
return this.makeDiagnosticAt(projectionField.from + leading, Math.max(projectionField.from + leading + 1, projectionField.from + rawOperand.length - trailing), 'SHIFT operand must be a stable aggregate or subquery measure', `shift-operand-syntax:${projectionField.from}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const invalidShiftContext = (_b = this.findAncestor(parent, 'FilterClause')) !== null && _b !== void 0 ? _b : this.findAncestor(parent, 'HavingClause');
|
|
652
|
+
if (invalidShiftContext) {
|
|
653
|
+
const shiftError = this.errorDescendantsWithText(invalidShiftContext, 'shift').find((error) => error.from <= node.from);
|
|
654
|
+
if (shiftError) {
|
|
655
|
+
return this.makeDiagnosticAt(shiftError.from, shiftError.to, 'expression-level SHIFT is only allowed in the projection', `shift-context-syntax:${invalidShiftContext.from}`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
486
658
|
if (parentName === 'OuterFunctionCall') {
|
|
487
659
|
return this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.OuterFunctionCall, group);
|
|
488
660
|
}
|
|
@@ -507,7 +679,7 @@ export class Parser {
|
|
|
507
679
|
}
|
|
508
680
|
return this.makeUnexpectedTokenDiagnostic(node, 'function call', group);
|
|
509
681
|
}
|
|
510
|
-
if (parentName === 'ArgItem' && node.from === node.to) {
|
|
682
|
+
if ((parentName === 'ArgItem' || parentName === 'EvalFunctionArg') && node.from === node.to) {
|
|
511
683
|
if ((previous === null || previous === void 0 ? void 0 : previous.name) === 'EqlSingle') {
|
|
512
684
|
return this.makeDiagnostic(node, 'function argument value expected after "="', group);
|
|
513
685
|
}
|
|
@@ -553,19 +725,51 @@ export class Parser {
|
|
|
553
725
|
if (parentName === 'ByExpr' && errorText === ',') {
|
|
554
726
|
return this.makeDiagnostic(node, 'grouping field expected before ","', group);
|
|
555
727
|
}
|
|
556
|
-
if (expressionParents.has(parentName)) {
|
|
557
|
-
if (previous && this.isOperator(previous)) {
|
|
558
|
-
return this.makeDiagnostic(node, `right-hand expression expected after "${previousText}"`, group);
|
|
559
|
-
}
|
|
560
|
-
return this.makeUnexpectedTokenDiagnostic(node, 'expression', group);
|
|
561
|
-
}
|
|
562
728
|
const caseWhenClause = this.findAncestor(parent, 'CaseWhenClause');
|
|
563
729
|
if (caseWhenClause) {
|
|
730
|
+
const firstCaseError = this.firstErrorFrom(caseWhenClause);
|
|
564
731
|
const missingCondition = /^\s*when\s+(then)\b/i.exec(this.maskLineComments(this.nodeText(caseWhenClause)));
|
|
565
732
|
if (missingCondition) {
|
|
566
|
-
|
|
567
|
-
|
|
733
|
+
if (node.from === firstCaseError) {
|
|
734
|
+
const thenOffset = missingCondition.index + missingCondition[0].toLowerCase().lastIndexOf('then');
|
|
735
|
+
return this.makeDiagnosticAt(caseWhenClause.from + thenOffset, caseWhenClause.from + thenOffset + 4, 'CASE condition expected after "WHEN"', this.caseGroup(caseWhenClause));
|
|
736
|
+
}
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
if (!caseWhenClause.getChild('Then')) {
|
|
740
|
+
if (node.from === firstCaseError) {
|
|
741
|
+
return this.makeDiagnostic(node, 'expected "THEN" after CASE condition', this.caseGroup(caseWhenClause));
|
|
742
|
+
}
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
const missingResult = /\bthen\s+(else)\b/i.exec(this.maskLineComments(this.nodeText(caseWhenClause)));
|
|
746
|
+
if (missingResult) {
|
|
747
|
+
if (node.from === firstCaseError) {
|
|
748
|
+
const keyword = missingResult[1];
|
|
749
|
+
const keywordOffset = missingResult.index + missingResult[0].toLowerCase().lastIndexOf(keyword.toLowerCase());
|
|
750
|
+
return this.makeDiagnosticAt(caseWhenClause.from + keywordOffset, caseWhenClause.from + keywordOffset + keyword.length, 'CASE result expected after "THEN"', this.caseGroup(caseWhenClause));
|
|
751
|
+
}
|
|
752
|
+
return errorText ? this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.CaseExpr, group) : null;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (parentName === 'EvalAtom' && errorText === '=' && (previous === null || previous === void 0 ? void 0 : previous.name) === 'Identifier') {
|
|
756
|
+
return this.makeDiagnostic(node, 'function argument value expected after "="', group);
|
|
757
|
+
}
|
|
758
|
+
if (parentName === 'EvalExpression' && node.from === node.to && (previous === null || previous === void 0 ? void 0 : previous.name) === 'EvalAtom' && (next === null || next === void 0 ? void 0 : next.name) === 'EvalAtom') {
|
|
759
|
+
return this.makeDiagnostic(node, 'expected "," between function arguments', group);
|
|
760
|
+
}
|
|
761
|
+
if (parentName === 'CaseArithmeticExpr' && errorText && !(previous === null || previous === void 0 ? void 0 : previous.type.isError)) {
|
|
762
|
+
return this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.CaseExpr, group);
|
|
763
|
+
}
|
|
764
|
+
if (parentName === 'ShiftExpr' || parentName === 'ShiftClause') {
|
|
765
|
+
const message = errorText ? 'SHIFT duration must be a positive fixed duration' : 'duration expected after "SHIFT"';
|
|
766
|
+
return this.makeDiagnostic(node, message, `shift-duration:${parent.from}`);
|
|
767
|
+
}
|
|
768
|
+
if (expressionParents.has(parentName)) {
|
|
769
|
+
if (previous && this.isOperator(previous)) {
|
|
770
|
+
return this.makeDiagnostic(node, `right-hand expression expected after "${previousText}"`, group);
|
|
568
771
|
}
|
|
772
|
+
return this.makeUnexpectedTokenDiagnostic(node, 'expression', group);
|
|
569
773
|
}
|
|
570
774
|
if (parentName === 'CaseCondition') {
|
|
571
775
|
return this.makeDiagnostic(node, 'expected "THEN" after CASE condition', this.caseGroup(parent));
|
|
@@ -614,7 +818,7 @@ export class Parser {
|
|
|
614
818
|
}
|
|
615
819
|
if (parentName === 'RollupIdentifier') {
|
|
616
820
|
const timeClause = this.findAncestor(parent, 'TimeClause');
|
|
617
|
-
let child = (
|
|
821
|
+
let child = (_c = timeClause === null || timeClause === void 0 ? void 0 : timeClause.firstChild) !== null && _c !== void 0 ? _c : null;
|
|
618
822
|
while (child) {
|
|
619
823
|
if (child.type.isError)
|
|
620
824
|
return null;
|
|
@@ -1153,7 +1357,9 @@ export class Parser {
|
|
|
1153
1357
|
describeConsecutiveCommaError(node, parent) {
|
|
1154
1358
|
const contexts = {
|
|
1155
1359
|
Projection: 'projection item',
|
|
1360
|
+
StudioCallBody: 'function argument',
|
|
1156
1361
|
FunctionCallBody: 'function argument',
|
|
1362
|
+
EvalFunctionCallBody: 'function argument',
|
|
1157
1363
|
EvalCallBody: 'function argument',
|
|
1158
1364
|
OuterFunctionCallBody: 'function argument',
|
|
1159
1365
|
RawOuterFunctionCallBody: 'function argument',
|
|
@@ -1162,7 +1368,6 @@ export class Parser {
|
|
|
1162
1368
|
FillOuterFunctionCallBody: 'function argument',
|
|
1163
1369
|
VariadicOuterFunctionCallBody: 'function argument',
|
|
1164
1370
|
RollupFunctionCallBody: 'function argument',
|
|
1165
|
-
CaseFunctionCallBody: 'function argument',
|
|
1166
1371
|
InValue: 'argument value',
|
|
1167
1372
|
InFieldValue: 'filter value',
|
|
1168
1373
|
IndexExpr: 'namespace index',
|
|
@@ -1347,6 +1552,20 @@ export class Parser {
|
|
|
1347
1552
|
}
|
|
1348
1553
|
return false;
|
|
1349
1554
|
}
|
|
1555
|
+
firstErrorFrom(node) {
|
|
1556
|
+
let first = null;
|
|
1557
|
+
const visit = (current) => {
|
|
1558
|
+
let child = current.firstChild;
|
|
1559
|
+
while (child) {
|
|
1560
|
+
if (child.type.isError && (first === null || child.from < first))
|
|
1561
|
+
first = child.from;
|
|
1562
|
+
visit(child);
|
|
1563
|
+
child = child.nextSibling;
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
visit(node);
|
|
1567
|
+
return first;
|
|
1568
|
+
}
|
|
1350
1569
|
looksLikeProjectionAfterMissingColon(node) {
|
|
1351
1570
|
const querySource = node.getChild('QuerySource');
|
|
1352
1571
|
return Boolean(querySource && this.containsError(querySource) && !this.containsNode(querySource, 'DColon'));
|
|
@@ -2296,6 +2515,8 @@ export class Parser {
|
|
|
2296
2515
|
case 'Query':
|
|
2297
2516
|
case 'QueryFragment':
|
|
2298
2517
|
this.checkDuplicateQueryClauses(node);
|
|
2518
|
+
this.checkQueryShiftPlacement(node);
|
|
2519
|
+
this.checkExpressionShiftOffsets(node);
|
|
2299
2520
|
break;
|
|
2300
2521
|
case 'ShowExpr':
|
|
2301
2522
|
this.checkDuplicateShowClauses(node);
|
|
@@ -2304,6 +2525,19 @@ export class Parser {
|
|
|
2304
2525
|
case 'TimeRange':
|
|
2305
2526
|
this.checkTimeRangeShape(node);
|
|
2306
2527
|
break;
|
|
2528
|
+
case 'ShiftClause':
|
|
2529
|
+
this.checkShiftDuration(node);
|
|
2530
|
+
break;
|
|
2531
|
+
case 'ShiftExpr':
|
|
2532
|
+
this.checkShiftDuration(node);
|
|
2533
|
+
this.checkShiftExpression(node);
|
|
2534
|
+
break;
|
|
2535
|
+
case 'EvalExpr':
|
|
2536
|
+
this.checkEvalReferences(node);
|
|
2537
|
+
break;
|
|
2538
|
+
case 'EvalFieldReference':
|
|
2539
|
+
this.checkEvalFieldReferenceContext(node);
|
|
2540
|
+
break;
|
|
2307
2541
|
case 'RollupIdentifier':
|
|
2308
2542
|
this.checkBareRollup(node);
|
|
2309
2543
|
break;
|
|
@@ -2311,7 +2545,10 @@ export class Parser {
|
|
|
2311
2545
|
this.checkRollupFunctionCall(node);
|
|
2312
2546
|
break;
|
|
2313
2547
|
case 'FunctionCall':
|
|
2548
|
+
case 'EvalFunctionCall':
|
|
2314
2549
|
this.checkFunctionCall(node);
|
|
2550
|
+
if (node.name === 'FunctionCall')
|
|
2551
|
+
this.checkCaseFunction(node);
|
|
2315
2552
|
break;
|
|
2316
2553
|
case 'OuterFunctionCall':
|
|
2317
2554
|
this.checkOuterFunctionCall(node);
|
|
@@ -2331,9 +2568,6 @@ export class Parser {
|
|
|
2331
2568
|
case 'FieldName':
|
|
2332
2569
|
this.checkFieldName(node);
|
|
2333
2570
|
break;
|
|
2334
|
-
case 'CaseFunctionIdentifier':
|
|
2335
|
-
this.checkCaseFunction(node);
|
|
2336
|
-
break;
|
|
2337
2571
|
case 'CaseExpr':
|
|
2338
2572
|
this.checkCaseResults(node);
|
|
2339
2573
|
break;
|
|
@@ -2366,6 +2600,168 @@ export class Parser {
|
|
|
2366
2600
|
child = child.nextSibling;
|
|
2367
2601
|
}
|
|
2368
2602
|
}
|
|
2603
|
+
checkQueryShiftPlacement(query) {
|
|
2604
|
+
var _a;
|
|
2605
|
+
const clauses = [];
|
|
2606
|
+
let child = query.firstChild;
|
|
2607
|
+
while (child) {
|
|
2608
|
+
if (child.name === 'QueryClause' || child.name === 'QueryFragmentClause') {
|
|
2609
|
+
const clause = child.firstChild;
|
|
2610
|
+
if (clause)
|
|
2611
|
+
clauses.push(clause);
|
|
2612
|
+
}
|
|
2613
|
+
child = child.nextSibling;
|
|
2614
|
+
}
|
|
2615
|
+
const shifts = clauses.filter((clause) => clause.name === 'ShiftClause');
|
|
2616
|
+
const timeRanges = clauses.filter((clause) => clause.name === 'TimeClause');
|
|
2617
|
+
if (shifts.length !== 1 || timeRanges.length > 1)
|
|
2618
|
+
return;
|
|
2619
|
+
const shift = shifts[0];
|
|
2620
|
+
const shiftIndex = clauses.indexOf(shift);
|
|
2621
|
+
const laterTarget = clauses.slice(shiftIndex + 1).find((clause) => clause.name === 'TargetClause');
|
|
2622
|
+
if (laterTarget) {
|
|
2623
|
+
this.pushSemanticDiagnostic(shift, '"SHIFT" must appear after the projection', `shift-placement:${shift.from}`);
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
const laterFilter = clauses.slice(shiftIndex + 1).find((clause) => clause.name === 'FilterClause');
|
|
2627
|
+
if (laterFilter) {
|
|
2628
|
+
this.pushSemanticDiagnostic(shift, '"SHIFT" must appear after "WHERE"', `shift-placement:${shift.from}`);
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
if (timeRanges.length === 1) {
|
|
2632
|
+
const timeIndex = clauses.indexOf(timeRanges[0]);
|
|
2633
|
+
if (shiftIndex !== timeIndex + 1) {
|
|
2634
|
+
this.pushSemanticDiagnostic(shift, '"SHIFT" must immediately follow the time range', `shift-placement:${shift.from}`);
|
|
2635
|
+
}
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
const outputClause = clauses
|
|
2639
|
+
.slice(0, shiftIndex)
|
|
2640
|
+
.reverse()
|
|
2641
|
+
.find((clause) => ['ByClause', 'HavingClause', 'OrderClause', 'SOrderClause', 'LimitClause', 'OffsetClause', 'SLimitClause', 'SOffsetClause'].includes(clause.name));
|
|
2642
|
+
if (outputClause) {
|
|
2643
|
+
const label = (_a = queryClauseLabels.get(outputClause.name)) !== null && _a !== void 0 ? _a : outputClause.name;
|
|
2644
|
+
this.pushSemanticDiagnostic(shift, `"SHIFT" must appear before "${label}"`, `shift-placement:${shift.from}`);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
checkExpressionShiftOffsets(query) {
|
|
2648
|
+
const offsets = new Set();
|
|
2649
|
+
let overflowReported = false;
|
|
2650
|
+
const visit = (node) => {
|
|
2651
|
+
if (overflowReported || Date.now() >= this.deadline)
|
|
2652
|
+
return;
|
|
2653
|
+
if (node !== query && node.name === 'Query')
|
|
2654
|
+
return;
|
|
2655
|
+
if (node.name === 'ShiftExpr') {
|
|
2656
|
+
const duration = node.getChild('Duration');
|
|
2657
|
+
if (duration) {
|
|
2658
|
+
const rawOffset = this.nodeText(duration).toLowerCase();
|
|
2659
|
+
const milliseconds = this.fixedDurationMilliseconds(rawOffset);
|
|
2660
|
+
const offset = milliseconds !== null && Number.isSafeInteger(milliseconds) ? `${milliseconds}ms` : rawOffset;
|
|
2661
|
+
if (!offsets.has(offset)) {
|
|
2662
|
+
offsets.add(offset);
|
|
2663
|
+
if (offsets.size > 16) {
|
|
2664
|
+
this.pushSemanticDiagnostic(duration, 'a SELECT can use at most 16 distinct SHIFT offsets', `shift-offsets:${query.from}`);
|
|
2665
|
+
overflowReported = true;
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
let child = node.firstChild;
|
|
2672
|
+
while (child) {
|
|
2673
|
+
visit(child);
|
|
2674
|
+
child = child.nextSibling;
|
|
2675
|
+
}
|
|
2676
|
+
};
|
|
2677
|
+
visit(query);
|
|
2678
|
+
}
|
|
2679
|
+
checkShiftDuration(shift) {
|
|
2680
|
+
const duration = shift.getChild('Duration');
|
|
2681
|
+
if (!duration || this.isPositiveFixedDuration(this.nodeText(duration)))
|
|
2682
|
+
return;
|
|
2683
|
+
this.pushSemanticDiagnostic(duration, 'SHIFT duration must be a positive fixed duration', `shift-duration:${shift.from}`);
|
|
2684
|
+
}
|
|
2685
|
+
checkShiftExpression(shift) {
|
|
2686
|
+
var _a, _b;
|
|
2687
|
+
const nested = this.findAncestor(shift.parent, 'ShiftExpr');
|
|
2688
|
+
if (nested) {
|
|
2689
|
+
this.pushSemanticDiagnostic(shift, 'SHIFT expressions cannot be nested', `shift-nested:${shift.from}`);
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
if (!this.findAncestor(shift.parent, 'TargetClause') && !this.findAncestor(shift.parent, 'ProjectionFragment')) {
|
|
2693
|
+
this.pushSemanticDiagnostic(shift, 'expression-level SHIFT is only allowed in the projection', `shift-context:${shift.from}`);
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
const operand = shift.getChild('ShiftOperand');
|
|
2697
|
+
if (!operand)
|
|
2698
|
+
return;
|
|
2699
|
+
const calls = this.shiftOperandFunctionCalls(operand);
|
|
2700
|
+
const unstable = calls.find((call) => {
|
|
2701
|
+
const name = this.functionCallName(call);
|
|
2702
|
+
return name !== null && unstableShiftAggregateFunctions.has(name);
|
|
2703
|
+
});
|
|
2704
|
+
if (unstable) {
|
|
2705
|
+
const name = (_a = this.functionCallName(unstable)) !== null && _a !== void 0 ? _a : 'function';
|
|
2706
|
+
const identifier = (_b = unstable.getChild('FunctionIdentifier')) !== null && _b !== void 0 ? _b : unstable;
|
|
2707
|
+
this.pushSemanticDiagnostic(identifier, `function "${name}" cannot be used as a SHIFT operand`, `shift-operand:${shift.from}`);
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
const hasStableAggregate = calls.some((call) => {
|
|
2711
|
+
const name = this.functionCallName(call);
|
|
2712
|
+
return name !== null && stableShiftAggregateFunctions.has(name);
|
|
2713
|
+
});
|
|
2714
|
+
if (!hasStableAggregate) {
|
|
2715
|
+
this.pushSemanticDiagnostic(operand, 'SHIFT operand must be a stable aggregate or subquery measure', `shift-operand:${shift.from}`);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
checkEvalFieldReferenceContext(reference) {
|
|
2719
|
+
if (this.findAncestor(reference.parent, 'EvalExpr'))
|
|
2720
|
+
return;
|
|
2721
|
+
this.pushSemanticDiagnostic(reference, 'qualified field references are only supported inside eval', `eval-field-context:${reference.from}`);
|
|
2722
|
+
}
|
|
2723
|
+
checkEvalReferences(evalExpr) {
|
|
2724
|
+
const body = evalExpr.getChild('EvalCallBody');
|
|
2725
|
+
if (!body)
|
|
2726
|
+
return;
|
|
2727
|
+
const aliases = new Set();
|
|
2728
|
+
for (const argument of this.directChildren(body, 'OuterArgItem')) {
|
|
2729
|
+
const identifier = argument.getChild('Identifier');
|
|
2730
|
+
if (!identifier || !argument.getChild('EqlSingle'))
|
|
2731
|
+
continue;
|
|
2732
|
+
if (!argument.getChild('Query') && !argument.getChild('OuterFunctionCall'))
|
|
2733
|
+
continue;
|
|
2734
|
+
const name = this.nodeText(identifier).toLowerCase();
|
|
2735
|
+
if (aliases.has(name)) {
|
|
2736
|
+
this.pushSemanticDiagnostic(identifier, `duplicate eval subquery "${name}"`, `eval-alias-duplicate:${identifier.from}`);
|
|
2737
|
+
continue;
|
|
2738
|
+
}
|
|
2739
|
+
aliases.add(name);
|
|
2740
|
+
}
|
|
2741
|
+
const expression = body.getChild('EvalArgItem');
|
|
2742
|
+
if (!expression)
|
|
2743
|
+
return;
|
|
2744
|
+
for (const reference of this.descendantsNamed(expression, 'EvalFieldReference')) {
|
|
2745
|
+
const alias = reference.getChild('Identifier');
|
|
2746
|
+
if (!alias)
|
|
2747
|
+
continue;
|
|
2748
|
+
const name = this.nodeText(alias).toLowerCase();
|
|
2749
|
+
if (!aliases.has(name)) {
|
|
2750
|
+
this.pushSemanticDiagnostic(alias, `unknown eval subquery "${name}" in field reference`, `eval-field:${reference.from}`);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
if (aliases.size > 1) {
|
|
2754
|
+
for (const atom of this.descendantsNamed(expression, 'EvalAtom')) {
|
|
2755
|
+
const identifier = atom.getChild('Identifier');
|
|
2756
|
+
if (!identifier)
|
|
2757
|
+
continue;
|
|
2758
|
+
const name = this.nodeText(identifier).toLowerCase();
|
|
2759
|
+
if (!aliases.has(name)) {
|
|
2760
|
+
this.pushSemanticDiagnostic(identifier, `unknown eval subquery "${name}" in expression`, `eval-alias:${identifier.from}`);
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2369
2765
|
checkDuplicateShowClauses(show) {
|
|
2370
2766
|
const seen = new Set();
|
|
2371
2767
|
let child = show.firstChild;
|
|
@@ -2481,12 +2877,44 @@ export class Parser {
|
|
|
2481
2877
|
}
|
|
2482
2878
|
}
|
|
2483
2879
|
checkFunctionCall(call) {
|
|
2880
|
+
var _a;
|
|
2484
2881
|
const identifier = call.getChild('FunctionIdentifier');
|
|
2485
|
-
const body = call.getChild('FunctionCallBody');
|
|
2882
|
+
const body = (_a = call.getChild('FunctionCallBody')) !== null && _a !== void 0 ? _a : call.getChild('EvalFunctionCallBody');
|
|
2486
2883
|
if (!identifier || !body)
|
|
2487
2884
|
return;
|
|
2488
2885
|
const name = this.nodeText(identifier).toLowerCase();
|
|
2489
|
-
const
|
|
2886
|
+
const argumentName = body.name === 'EvalFunctionCallBody' ? 'EvalFunctionArg' : 'ArgItem';
|
|
2887
|
+
const args = this.directChildren(body, argumentName);
|
|
2888
|
+
const arity = documentedFunctionArities.get(name);
|
|
2889
|
+
if (arity && (args.length < arity.min || args.length > arity.max)) {
|
|
2890
|
+
this.pushSemanticDiagnostic(body, this.functionArityMessage(name, arity), `function-arity:${call.from}`);
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
if ((name === 'count_filter' || name === 'distinct_by_collapse') && args.length === 2 && !args[1].getChild('InValue')) {
|
|
2894
|
+
this.pushSemanticDiagnostic(args[1], `function "${name}" expects a value list as its second argument`, `function:${call.from}`);
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
if (name === 'histogram_quantile' && args.length === 2) {
|
|
2898
|
+
const quantile = this.numericValue(args[1]);
|
|
2899
|
+
if (quantile === null || quantile < 0 || quantile > 1) {
|
|
2900
|
+
this.pushSemanticDiagnostic(args[1], 'function "histogram_quantile" expects a quantile in range [0, 1]', `function:${call.from}`);
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
if (name === 'drain' && args.length >= 2) {
|
|
2905
|
+
const threshold = this.numericValue(args[1]);
|
|
2906
|
+
if (threshold === null || threshold <= 0 || threshold > 1) {
|
|
2907
|
+
this.pushSemanticDiagnostic(args[1], 'function "drain" expects a similarity threshold in range (0, 1]', `function:${call.from}`);
|
|
2908
|
+
return;
|
|
2909
|
+
}
|
|
2910
|
+
if (args.length === 3) {
|
|
2911
|
+
const maxClusters = this.numericValue(args[2]);
|
|
2912
|
+
if (maxClusters === null || !Number.isInteger(maxClusters) || maxClusters < 1 || maxClusters > 10000) {
|
|
2913
|
+
this.pushSemanticDiagnostic(args[2], 'function "drain" expects max_clusters to be an integer in range [1, 10000]', `function:${call.from}`);
|
|
2914
|
+
return;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2490
2918
|
if (name === 'ewma') {
|
|
2491
2919
|
const alpha = args.length === 2 ? this.numericValue(args[1]) : null;
|
|
2492
2920
|
if (alpha === null || alpha <= 0 || alpha > 1) {
|
|
@@ -2515,6 +2943,17 @@ export class Parser {
|
|
|
2515
2943
|
this.pushSemanticDiagnostic(body, `function "${name}" expects exactly one input field`, `function:${call.from}`);
|
|
2516
2944
|
}
|
|
2517
2945
|
}
|
|
2946
|
+
functionArityMessage(name, arity) {
|
|
2947
|
+
if (arity.min === arity.max) {
|
|
2948
|
+
if (arity.min === 0)
|
|
2949
|
+
return `function "${name}" does not accept arguments`;
|
|
2950
|
+
return `function "${name}" expects exactly ${arity.min} ${arity.min === 1 ? 'argument' : 'arguments'}`;
|
|
2951
|
+
}
|
|
2952
|
+
if (!Number.isFinite(arity.max)) {
|
|
2953
|
+
return `function "${name}" expects at least ${arity.min} ${arity.min === 1 ? 'argument' : 'arguments'}`;
|
|
2954
|
+
}
|
|
2955
|
+
return `function "${name}" expects between ${arity.min} and ${arity.max} arguments`;
|
|
2956
|
+
}
|
|
2518
2957
|
checkOuterFunctionCall(call) {
|
|
2519
2958
|
const identifier = call.firstChild;
|
|
2520
2959
|
if (!identifier || identifier.name === 'FunctionIdentifier')
|
|
@@ -2613,11 +3052,36 @@ export class Parser {
|
|
|
2613
3052
|
this.pushSemanticDiagnostic(field, 'field names containing "-" must be enclosed in backticks', `field-name:${field.from}`);
|
|
2614
3053
|
}
|
|
2615
3054
|
}
|
|
2616
|
-
checkCaseFunction(
|
|
3055
|
+
checkCaseFunction(call) {
|
|
3056
|
+
const caseExpr = this.findAncestor(call.parent, 'CaseExpr');
|
|
3057
|
+
if (!caseExpr)
|
|
3058
|
+
return;
|
|
3059
|
+
const shiftExpr = this.findAncestor(call.parent, 'ShiftExpr');
|
|
3060
|
+
if (shiftExpr && shiftExpr.from >= caseExpr.from && shiftExpr.to <= caseExpr.to)
|
|
3061
|
+
return;
|
|
3062
|
+
const identifier = call.getChild('FunctionIdentifier');
|
|
3063
|
+
const body = call.getChild('FunctionCallBody');
|
|
3064
|
+
if (!identifier || !body)
|
|
3065
|
+
return;
|
|
2617
3066
|
const name = this.nodeText(identifier).toLowerCase();
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3067
|
+
const args = this.directChildren(body, 'ArgItem');
|
|
3068
|
+
const arity = documentedFunctionArities.get(name);
|
|
3069
|
+
if (arity && (args.length < arity.min || args.length > arity.max))
|
|
3070
|
+
return;
|
|
3071
|
+
if (caseScalarFunctions.has(name))
|
|
3072
|
+
return;
|
|
3073
|
+
if (stableShiftAggregateFunctions.has(name) && this.caseContainsShift(caseExpr))
|
|
3074
|
+
return;
|
|
3075
|
+
this.pushSemanticDiagnostic(identifier, `function "${name}" is not supported inside CASE`, `case-function:${identifier.from}`);
|
|
3076
|
+
}
|
|
3077
|
+
caseContainsShift(caseExpr) {
|
|
3078
|
+
const key = this.nodeRangeKey(caseExpr);
|
|
3079
|
+
const cached = this.caseShiftPresence.get(key);
|
|
3080
|
+
if (cached !== undefined)
|
|
3081
|
+
return cached;
|
|
3082
|
+
const containsShift = this.containsNode(caseExpr, 'ShiftExpr');
|
|
3083
|
+
this.caseShiftPresence.set(key, containsShift);
|
|
3084
|
+
return containsShift;
|
|
2621
3085
|
}
|
|
2622
3086
|
checkCaseResults(caseExpr) {
|
|
2623
3087
|
let clause = caseExpr.firstChild;
|
|
@@ -2644,6 +3108,10 @@ export class Parser {
|
|
|
2644
3108
|
const hasFrom = /\bfrom\s*=/.test(bodyText.toLowerCase());
|
|
2645
3109
|
const filter = show.getChild('FilterClause');
|
|
2646
3110
|
const hasSourceFilter = Boolean(filter && /@__source__/.test(this.maskStringsAndLineComments(this.nodeText(filter))));
|
|
3111
|
+
if (/^show_[a-z0-9]+_index$/.test(name)) {
|
|
3112
|
+
this.pushSemanticDiagnostic(identifier, `"${name}" is not supported by the DQL parser`, `show:${show.from}:unsupported`);
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
2647
3115
|
if ((name === 'show_tag_value' || name === 'show_tag_value_cardinality' || name === 'show_series_count_by_tag_value') && !hasKeyin) {
|
|
2648
3116
|
this.pushSemanticDiagnostic(identifier, `"${name}" requires the named argument "keyin"`, `show:${show.from}:keyin`);
|
|
2649
3117
|
}
|
|
@@ -2669,13 +3137,105 @@ export class Parser {
|
|
|
2669
3137
|
}
|
|
2670
3138
|
return children;
|
|
2671
3139
|
}
|
|
3140
|
+
descendantsNamed(node, name) {
|
|
3141
|
+
const descendants = [];
|
|
3142
|
+
const visit = (current) => {
|
|
3143
|
+
let child = current.firstChild;
|
|
3144
|
+
while (child) {
|
|
3145
|
+
if (child.name === name)
|
|
3146
|
+
descendants.push(child);
|
|
3147
|
+
visit(child);
|
|
3148
|
+
child = child.nextSibling;
|
|
3149
|
+
}
|
|
3150
|
+
};
|
|
3151
|
+
visit(node);
|
|
3152
|
+
return descendants;
|
|
3153
|
+
}
|
|
3154
|
+
shiftOperandFunctionCalls(operand) {
|
|
3155
|
+
const querySource = operand.getChild('QuerySource');
|
|
3156
|
+
if (!querySource)
|
|
3157
|
+
return this.descendantsNamed(operand, 'FunctionCall');
|
|
3158
|
+
const query = querySource.getChild('Query');
|
|
3159
|
+
if (!query)
|
|
3160
|
+
return [];
|
|
3161
|
+
const calls = [];
|
|
3162
|
+
let clause = query.firstChild;
|
|
3163
|
+
while (clause) {
|
|
3164
|
+
if (clause.name === 'QueryClause') {
|
|
3165
|
+
const target = clause.getChild('TargetClause');
|
|
3166
|
+
if (target)
|
|
3167
|
+
calls.push(...this.descendantsNamed(target, 'FunctionCall'));
|
|
3168
|
+
}
|
|
3169
|
+
clause = clause.nextSibling;
|
|
3170
|
+
}
|
|
3171
|
+
return calls;
|
|
3172
|
+
}
|
|
3173
|
+
errorDescendantsWithText(node, text) {
|
|
3174
|
+
const errors = [];
|
|
3175
|
+
const expected = text.toLowerCase();
|
|
3176
|
+
const visit = (current) => {
|
|
3177
|
+
let child = current.firstChild;
|
|
3178
|
+
while (child) {
|
|
3179
|
+
if (child.type.isError && this.nodeText(child).toLowerCase() === expected)
|
|
3180
|
+
errors.push(child);
|
|
3181
|
+
visit(child);
|
|
3182
|
+
child = child.nextSibling;
|
|
3183
|
+
}
|
|
3184
|
+
};
|
|
3185
|
+
visit(node);
|
|
3186
|
+
return errors;
|
|
3187
|
+
}
|
|
3188
|
+
functionCallName(call) {
|
|
3189
|
+
const identifier = call.getChild('FunctionIdentifier');
|
|
3190
|
+
return identifier ? this.nodeText(identifier).toLowerCase() : null;
|
|
3191
|
+
}
|
|
3192
|
+
isPositiveFixedDuration(text) {
|
|
3193
|
+
const milliseconds = this.fixedDurationMilliseconds(text);
|
|
3194
|
+
return milliseconds !== null && milliseconds > 0;
|
|
3195
|
+
}
|
|
3196
|
+
fixedDurationMilliseconds(text) {
|
|
3197
|
+
const multipliers = {
|
|
3198
|
+
y: 365 * 24 * 60 * 60 * 1000,
|
|
3199
|
+
w: 7 * 24 * 60 * 60 * 1000,
|
|
3200
|
+
d: 24 * 60 * 60 * 1000,
|
|
3201
|
+
h: 60 * 60 * 1000,
|
|
3202
|
+
m: 60 * 1000,
|
|
3203
|
+
s: 1000,
|
|
3204
|
+
ms: 1,
|
|
3205
|
+
};
|
|
3206
|
+
const normalized = text.toLowerCase();
|
|
3207
|
+
const pattern = /(\d+)(ms|[ywdhms])/g;
|
|
3208
|
+
let total = 0;
|
|
3209
|
+
let consumed = 0;
|
|
3210
|
+
let part = pattern.exec(normalized);
|
|
3211
|
+
while (part) {
|
|
3212
|
+
if (part.index !== consumed)
|
|
3213
|
+
return null;
|
|
3214
|
+
const amount = Number(part[1]);
|
|
3215
|
+
const component = amount * multipliers[part[2]];
|
|
3216
|
+
if (!Number.isSafeInteger(amount) || !Number.isSafeInteger(component) || !Number.isSafeInteger(total + component))
|
|
3217
|
+
return null;
|
|
3218
|
+
total += component;
|
|
3219
|
+
consumed = pattern.lastIndex;
|
|
3220
|
+
part = pattern.exec(normalized);
|
|
3221
|
+
}
|
|
3222
|
+
return consumed === normalized.length ? total : null;
|
|
3223
|
+
}
|
|
2672
3224
|
numericValue(node) {
|
|
2673
3225
|
if (!node)
|
|
2674
3226
|
return null;
|
|
2675
|
-
|
|
3227
|
+
let valueNode = node;
|
|
3228
|
+
if (node.name === 'EvalFunctionArg') {
|
|
3229
|
+
const expression = node.getChild('EvalExpression');
|
|
3230
|
+
const atom = expression === null || expression === void 0 ? void 0 : expression.firstChild;
|
|
3231
|
+
if (!expression || (atom === null || atom === void 0 ? void 0 : atom.name) !== 'EvalAtom' || atom.nextSibling)
|
|
3232
|
+
return null;
|
|
3233
|
+
valueNode = atom;
|
|
3234
|
+
}
|
|
3235
|
+
const number = valueNode.name === 'Number' ? valueNode : valueNode.getChild('Number');
|
|
2676
3236
|
if (!number)
|
|
2677
3237
|
return null;
|
|
2678
|
-
const sign =
|
|
3238
|
+
const sign = valueNode.name !== 'Number' && valueNode.getChild('Sub') ? -1 : 1;
|
|
2679
3239
|
const value = sign * Number(this.nodeText(number).replace(/_/g, ''));
|
|
2680
3240
|
return Number.isFinite(value) ? value : null;
|
|
2681
3241
|
}
|