@cloudcare/codemirror-dql 0.0.56 → 0.0.58

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.
@@ -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,8 +24,142 @@ 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',
@@ -60,8 +195,15 @@ export const dqlSyntaxContexts = {
60
195
  OuterArgItem: 'outer function argument',
61
196
  Queted: 'quoted query delimiter',
62
197
  EvalCallBody: 'eval function arguments',
198
+ EvalArgItem: 'eval expression argument',
199
+ EvalExpression: 'eval expression',
200
+ EvalAtom: 'eval expression value',
201
+ EvalFunctionCall: 'eval function call',
202
+ EvalFunctionCallBody: 'eval function arguments',
203
+ EvalFunctionArg: 'eval function argument',
63
204
  FunctionCallBody: 'function arguments',
64
205
  ArgItem: 'function argument',
206
+ EvalFieldReference: 'eval field reference',
65
207
  InValue: 'argument value list',
66
208
  SingleExpr: 'expression value',
67
209
  Expr: 'expression',
@@ -78,6 +220,8 @@ export const dqlSyntaxContexts = {
78
220
  ProjectionFieldFragment: 'projection field fragment',
79
221
  ProjectionField: 'projection field',
80
222
  SingleFieldExpr: 'projection expression value',
223
+ ShiftExpr: 'SHIFT expression',
224
+ ShiftOperand: 'SHIFT operand',
81
225
  ProjectionExpr: 'projection expression',
82
226
  AsExpr: 'alias',
83
227
  FilterClause: 'filter clause',
@@ -91,10 +235,10 @@ export const dqlSyntaxContexts = {
91
235
  CaseCondition: 'CASE condition',
92
236
  CaseFilterExpr: 'CASE filter expression',
93
237
  CasePredicate: 'CASE predicate',
238
+ CaseComparableValue: 'CASE comparison value',
94
239
  CaseValue: 'CASE value',
95
- CaseFunctionCall: 'CASE function call',
96
- CaseFunctionIdentifier: 'CASE function name',
97
- CaseFunctionCallBody: 'CASE function arguments',
240
+ CaseArithmeticAtom: 'CASE arithmetic value',
241
+ CaseArithmeticExpr: 'CASE arithmetic expression',
98
242
  FilterFragmentExpr: 'filter fragment',
99
243
  ComparisonExpr: 'comparison expression',
100
244
  LogicalFragmentExpr: 'logical filter expression',
@@ -105,6 +249,7 @@ export const dqlSyntaxContexts = {
105
249
  FilterExpr: 'filter expression',
106
250
  BinaryExpr: 'binary expression',
107
251
  TimeClause: 'time range clause',
252
+ ShiftClause: 'SHIFT clause',
108
253
  TimeRange: 'time range',
109
254
  TimeExpr: 'time range value',
110
255
  ByClause: 'BY clause',
@@ -124,6 +269,7 @@ export const dqlSyntaxContexts = {
124
269
  };
125
270
  const functionCallBodyParents = new Set([
126
271
  'FunctionCallBody',
272
+ 'EvalFunctionCallBody',
127
273
  'EvalCallBody',
128
274
  'OuterFunctionCallBody',
129
275
  'RawOuterFunctionCallBody',
@@ -132,7 +278,6 @@ const functionCallBodyParents = new Set([
132
278
  'FillOuterFunctionCallBody',
133
279
  'VariadicOuterFunctionCallBody',
134
280
  'RollupFunctionCallBody',
135
- 'CaseFunctionCallBody',
136
281
  ]);
137
282
  export class Parser {
138
283
  constructor(state, deadline = Date.now() + analysisBudgetMs) {
@@ -149,6 +294,7 @@ export class Parser {
149
294
  this.quotedQueryDelimiterInfo = undefined;
150
295
  this.caseResultDiagnostics = undefined;
151
296
  this.caseResultRecoveryErrors = new Set();
297
+ this.caseShiftPresence = new Map();
152
298
  }
153
299
  getDiagnostics() {
154
300
  const seen = new Set();
@@ -401,7 +547,7 @@ export class Parser {
401
547
  this.appendDiagnostics(diagnostics);
402
548
  }
403
549
  describeError(node) {
404
- var _a;
550
+ var _a, _b, _c;
405
551
  const delimiterIssue = this.getDelimiterIssue();
406
552
  if (delimiterIssue && delimiterIssue.kind !== 'unclosed' && this.isRelatedDelimiterError(node, delimiterIssue)) {
407
553
  return null;
@@ -483,6 +629,26 @@ export class Parser {
483
629
  }
484
630
  return this.makeDiagnostic(node, 'expected "AS" before projection alias', group);
485
631
  }
632
+ const projectionField = (_a = this.findAncestor(parent, 'ProjectionField')) !== null && _a !== void 0 ? _a : this.findAncestor(parent, 'ProjectionFieldFragment');
633
+ if (projectionField) {
634
+ const shiftError = this.errorDescendantsWithText(projectionField, 'shift').find((error) => error.from <= node.from);
635
+ if (shiftError && this.containsNode(projectionField, 'ShiftExpr')) {
636
+ return this.makeDiagnosticAt(shiftError.from, shiftError.to, 'SHIFT expressions cannot be nested', `shift-nested-syntax:${projectionField.from}`);
637
+ }
638
+ if (shiftError) {
639
+ const rawOperand = this.state.sliceDoc(projectionField.from, shiftError.from);
640
+ const leading = rawOperand.length - rawOperand.trimStart().length;
641
+ const trailing = rawOperand.length - rawOperand.trimEnd().length;
642
+ 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}`);
643
+ }
644
+ }
645
+ const invalidShiftContext = (_b = this.findAncestor(parent, 'FilterClause')) !== null && _b !== void 0 ? _b : this.findAncestor(parent, 'HavingClause');
646
+ if (invalidShiftContext) {
647
+ const shiftError = this.errorDescendantsWithText(invalidShiftContext, 'shift').find((error) => error.from <= node.from);
648
+ if (shiftError) {
649
+ return this.makeDiagnosticAt(shiftError.from, shiftError.to, 'expression-level SHIFT is only allowed in the projection', `shift-context-syntax:${invalidShiftContext.from}`);
650
+ }
651
+ }
486
652
  if (parentName === 'OuterFunctionCall') {
487
653
  return this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.OuterFunctionCall, group);
488
654
  }
@@ -507,7 +673,7 @@ export class Parser {
507
673
  }
508
674
  return this.makeUnexpectedTokenDiagnostic(node, 'function call', group);
509
675
  }
510
- if (parentName === 'ArgItem' && node.from === node.to) {
676
+ if ((parentName === 'ArgItem' || parentName === 'EvalFunctionArg') && node.from === node.to) {
511
677
  if ((previous === null || previous === void 0 ? void 0 : previous.name) === 'EqlSingle') {
512
678
  return this.makeDiagnostic(node, 'function argument value expected after "="', group);
513
679
  }
@@ -553,19 +719,51 @@ export class Parser {
553
719
  if (parentName === 'ByExpr' && errorText === ',') {
554
720
  return this.makeDiagnostic(node, 'grouping field expected before ","', group);
555
721
  }
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
722
  const caseWhenClause = this.findAncestor(parent, 'CaseWhenClause');
563
723
  if (caseWhenClause) {
724
+ const firstCaseError = this.firstErrorFrom(caseWhenClause);
564
725
  const missingCondition = /^\s*when\s+(then)\b/i.exec(this.maskLineComments(this.nodeText(caseWhenClause)));
565
726
  if (missingCondition) {
566
- const thenOffset = missingCondition.index + missingCondition[0].toLowerCase().lastIndexOf('then');
567
- return this.makeDiagnosticAt(caseWhenClause.from + thenOffset, caseWhenClause.from + thenOffset + 4, 'CASE condition expected after "WHEN"', this.caseGroup(caseWhenClause));
727
+ if (node.from === firstCaseError) {
728
+ const thenOffset = missingCondition.index + missingCondition[0].toLowerCase().lastIndexOf('then');
729
+ return this.makeDiagnosticAt(caseWhenClause.from + thenOffset, caseWhenClause.from + thenOffset + 4, 'CASE condition expected after "WHEN"', this.caseGroup(caseWhenClause));
730
+ }
731
+ return null;
732
+ }
733
+ if (!caseWhenClause.getChild('Then')) {
734
+ if (node.from === firstCaseError) {
735
+ return this.makeDiagnostic(node, 'expected "THEN" after CASE condition', this.caseGroup(caseWhenClause));
736
+ }
737
+ return null;
738
+ }
739
+ const missingResult = /\bthen\s+(else)\b/i.exec(this.maskLineComments(this.nodeText(caseWhenClause)));
740
+ if (missingResult) {
741
+ if (node.from === firstCaseError) {
742
+ const keyword = missingResult[1];
743
+ const keywordOffset = missingResult.index + missingResult[0].toLowerCase().lastIndexOf(keyword.toLowerCase());
744
+ return this.makeDiagnosticAt(caseWhenClause.from + keywordOffset, caseWhenClause.from + keywordOffset + keyword.length, 'CASE result expected after "THEN"', this.caseGroup(caseWhenClause));
745
+ }
746
+ return errorText ? this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.CaseExpr, group) : null;
747
+ }
748
+ }
749
+ if (parentName === 'EvalAtom' && errorText === '=' && (previous === null || previous === void 0 ? void 0 : previous.name) === 'Identifier') {
750
+ return this.makeDiagnostic(node, 'function argument value expected after "="', group);
751
+ }
752
+ 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') {
753
+ return this.makeDiagnostic(node, 'expected "," between function arguments', group);
754
+ }
755
+ if (parentName === 'CaseArithmeticExpr' && errorText && !(previous === null || previous === void 0 ? void 0 : previous.type.isError)) {
756
+ return this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.CaseExpr, group);
757
+ }
758
+ if (parentName === 'ShiftExpr' || parentName === 'ShiftClause') {
759
+ const message = errorText ? 'SHIFT duration must be a positive fixed duration' : 'duration expected after "SHIFT"';
760
+ return this.makeDiagnostic(node, message, `shift-duration:${parent.from}`);
761
+ }
762
+ if (expressionParents.has(parentName)) {
763
+ if (previous && this.isOperator(previous)) {
764
+ return this.makeDiagnostic(node, `right-hand expression expected after "${previousText}"`, group);
568
765
  }
766
+ return this.makeUnexpectedTokenDiagnostic(node, 'expression', group);
569
767
  }
570
768
  if (parentName === 'CaseCondition') {
571
769
  return this.makeDiagnostic(node, 'expected "THEN" after CASE condition', this.caseGroup(parent));
@@ -614,7 +812,7 @@ export class Parser {
614
812
  }
615
813
  if (parentName === 'RollupIdentifier') {
616
814
  const timeClause = this.findAncestor(parent, 'TimeClause');
617
- let child = (_a = timeClause === null || timeClause === void 0 ? void 0 : timeClause.firstChild) !== null && _a !== void 0 ? _a : null;
815
+ let child = (_c = timeClause === null || timeClause === void 0 ? void 0 : timeClause.firstChild) !== null && _c !== void 0 ? _c : null;
618
816
  while (child) {
619
817
  if (child.type.isError)
620
818
  return null;
@@ -1154,6 +1352,7 @@ export class Parser {
1154
1352
  const contexts = {
1155
1353
  Projection: 'projection item',
1156
1354
  FunctionCallBody: 'function argument',
1355
+ EvalFunctionCallBody: 'function argument',
1157
1356
  EvalCallBody: 'function argument',
1158
1357
  OuterFunctionCallBody: 'function argument',
1159
1358
  RawOuterFunctionCallBody: 'function argument',
@@ -1162,7 +1361,6 @@ export class Parser {
1162
1361
  FillOuterFunctionCallBody: 'function argument',
1163
1362
  VariadicOuterFunctionCallBody: 'function argument',
1164
1363
  RollupFunctionCallBody: 'function argument',
1165
- CaseFunctionCallBody: 'function argument',
1166
1364
  InValue: 'argument value',
1167
1365
  InFieldValue: 'filter value',
1168
1366
  IndexExpr: 'namespace index',
@@ -1347,6 +1545,20 @@ export class Parser {
1347
1545
  }
1348
1546
  return false;
1349
1547
  }
1548
+ firstErrorFrom(node) {
1549
+ let first = null;
1550
+ const visit = (current) => {
1551
+ let child = current.firstChild;
1552
+ while (child) {
1553
+ if (child.type.isError && (first === null || child.from < first))
1554
+ first = child.from;
1555
+ visit(child);
1556
+ child = child.nextSibling;
1557
+ }
1558
+ };
1559
+ visit(node);
1560
+ return first;
1561
+ }
1350
1562
  looksLikeProjectionAfterMissingColon(node) {
1351
1563
  const querySource = node.getChild('QuerySource');
1352
1564
  return Boolean(querySource && this.containsError(querySource) && !this.containsNode(querySource, 'DColon'));
@@ -2294,7 +2506,10 @@ export class Parser {
2294
2506
  if (!node.type.isError && !this.containsError(node)) {
2295
2507
  switch (node.name) {
2296
2508
  case 'Query':
2509
+ case 'QueryFragment':
2297
2510
  this.checkDuplicateQueryClauses(node);
2511
+ this.checkQueryShiftPlacement(node);
2512
+ this.checkExpressionShiftOffsets(node);
2298
2513
  break;
2299
2514
  case 'ShowExpr':
2300
2515
  this.checkDuplicateShowClauses(node);
@@ -2303,6 +2518,19 @@ export class Parser {
2303
2518
  case 'TimeRange':
2304
2519
  this.checkTimeRangeShape(node);
2305
2520
  break;
2521
+ case 'ShiftClause':
2522
+ this.checkShiftDuration(node);
2523
+ break;
2524
+ case 'ShiftExpr':
2525
+ this.checkShiftDuration(node);
2526
+ this.checkShiftExpression(node);
2527
+ break;
2528
+ case 'EvalExpr':
2529
+ this.checkEvalReferences(node);
2530
+ break;
2531
+ case 'EvalFieldReference':
2532
+ this.checkEvalFieldReferenceContext(node);
2533
+ break;
2306
2534
  case 'RollupIdentifier':
2307
2535
  this.checkBareRollup(node);
2308
2536
  break;
@@ -2310,7 +2538,10 @@ export class Parser {
2310
2538
  this.checkRollupFunctionCall(node);
2311
2539
  break;
2312
2540
  case 'FunctionCall':
2541
+ case 'EvalFunctionCall':
2313
2542
  this.checkFunctionCall(node);
2543
+ if (node.name === 'FunctionCall')
2544
+ this.checkCaseFunction(node);
2314
2545
  break;
2315
2546
  case 'OuterFunctionCall':
2316
2547
  this.checkOuterFunctionCall(node);
@@ -2330,9 +2561,6 @@ export class Parser {
2330
2561
  case 'FieldName':
2331
2562
  this.checkFieldName(node);
2332
2563
  break;
2333
- case 'CaseFunctionIdentifier':
2334
- this.checkCaseFunction(node);
2335
- break;
2336
2564
  case 'CaseExpr':
2337
2565
  this.checkCaseResults(node);
2338
2566
  break;
@@ -2348,7 +2576,7 @@ export class Parser {
2348
2576
  const seen = new Set();
2349
2577
  let child = query.firstChild;
2350
2578
  while (child) {
2351
- if (child.name === 'QueryClause') {
2579
+ if (child.name === 'QueryClause' || child.name === 'QueryFragmentClause') {
2352
2580
  const clause = child.firstChild;
2353
2581
  if (clause) {
2354
2582
  const label = queryClauseLabels.get(clause.name);
@@ -2365,6 +2593,168 @@ export class Parser {
2365
2593
  child = child.nextSibling;
2366
2594
  }
2367
2595
  }
2596
+ checkQueryShiftPlacement(query) {
2597
+ var _a;
2598
+ const clauses = [];
2599
+ let child = query.firstChild;
2600
+ while (child) {
2601
+ if (child.name === 'QueryClause' || child.name === 'QueryFragmentClause') {
2602
+ const clause = child.firstChild;
2603
+ if (clause)
2604
+ clauses.push(clause);
2605
+ }
2606
+ child = child.nextSibling;
2607
+ }
2608
+ const shifts = clauses.filter((clause) => clause.name === 'ShiftClause');
2609
+ const timeRanges = clauses.filter((clause) => clause.name === 'TimeClause');
2610
+ if (shifts.length !== 1 || timeRanges.length > 1)
2611
+ return;
2612
+ const shift = shifts[0];
2613
+ const shiftIndex = clauses.indexOf(shift);
2614
+ const laterTarget = clauses.slice(shiftIndex + 1).find((clause) => clause.name === 'TargetClause');
2615
+ if (laterTarget) {
2616
+ this.pushSemanticDiagnostic(shift, '"SHIFT" must appear after the projection', `shift-placement:${shift.from}`);
2617
+ return;
2618
+ }
2619
+ const laterFilter = clauses.slice(shiftIndex + 1).find((clause) => clause.name === 'FilterClause');
2620
+ if (laterFilter) {
2621
+ this.pushSemanticDiagnostic(shift, '"SHIFT" must appear after "WHERE"', `shift-placement:${shift.from}`);
2622
+ return;
2623
+ }
2624
+ if (timeRanges.length === 1) {
2625
+ const timeIndex = clauses.indexOf(timeRanges[0]);
2626
+ if (shiftIndex !== timeIndex + 1) {
2627
+ this.pushSemanticDiagnostic(shift, '"SHIFT" must immediately follow the time range', `shift-placement:${shift.from}`);
2628
+ }
2629
+ return;
2630
+ }
2631
+ const outputClause = clauses
2632
+ .slice(0, shiftIndex)
2633
+ .reverse()
2634
+ .find((clause) => ['ByClause', 'HavingClause', 'OrderClause', 'SOrderClause', 'LimitClause', 'OffsetClause', 'SLimitClause', 'SOffsetClause'].includes(clause.name));
2635
+ if (outputClause) {
2636
+ const label = (_a = queryClauseLabels.get(outputClause.name)) !== null && _a !== void 0 ? _a : outputClause.name;
2637
+ this.pushSemanticDiagnostic(shift, `"SHIFT" must appear before "${label}"`, `shift-placement:${shift.from}`);
2638
+ }
2639
+ }
2640
+ checkExpressionShiftOffsets(query) {
2641
+ const offsets = new Set();
2642
+ let overflowReported = false;
2643
+ const visit = (node) => {
2644
+ if (overflowReported || Date.now() >= this.deadline)
2645
+ return;
2646
+ if (node !== query && node.name === 'Query')
2647
+ return;
2648
+ if (node.name === 'ShiftExpr') {
2649
+ const duration = node.getChild('Duration');
2650
+ if (duration) {
2651
+ const rawOffset = this.nodeText(duration).toLowerCase();
2652
+ const milliseconds = this.fixedDurationMilliseconds(rawOffset);
2653
+ const offset = milliseconds !== null && Number.isSafeInteger(milliseconds) ? `${milliseconds}ms` : rawOffset;
2654
+ if (!offsets.has(offset)) {
2655
+ offsets.add(offset);
2656
+ if (offsets.size > 16) {
2657
+ this.pushSemanticDiagnostic(duration, 'a SELECT can use at most 16 distinct SHIFT offsets', `shift-offsets:${query.from}`);
2658
+ overflowReported = true;
2659
+ return;
2660
+ }
2661
+ }
2662
+ }
2663
+ }
2664
+ let child = node.firstChild;
2665
+ while (child) {
2666
+ visit(child);
2667
+ child = child.nextSibling;
2668
+ }
2669
+ };
2670
+ visit(query);
2671
+ }
2672
+ checkShiftDuration(shift) {
2673
+ const duration = shift.getChild('Duration');
2674
+ if (!duration || this.isPositiveFixedDuration(this.nodeText(duration)))
2675
+ return;
2676
+ this.pushSemanticDiagnostic(duration, 'SHIFT duration must be a positive fixed duration', `shift-duration:${shift.from}`);
2677
+ }
2678
+ checkShiftExpression(shift) {
2679
+ var _a, _b;
2680
+ const nested = this.findAncestor(shift.parent, 'ShiftExpr');
2681
+ if (nested) {
2682
+ this.pushSemanticDiagnostic(shift, 'SHIFT expressions cannot be nested', `shift-nested:${shift.from}`);
2683
+ return;
2684
+ }
2685
+ if (!this.findAncestor(shift.parent, 'TargetClause') && !this.findAncestor(shift.parent, 'ProjectionFragment')) {
2686
+ this.pushSemanticDiagnostic(shift, 'expression-level SHIFT is only allowed in the projection', `shift-context:${shift.from}`);
2687
+ return;
2688
+ }
2689
+ const operand = shift.getChild('ShiftOperand');
2690
+ if (!operand)
2691
+ return;
2692
+ const calls = this.shiftOperandFunctionCalls(operand);
2693
+ const unstable = calls.find((call) => {
2694
+ const name = this.functionCallName(call);
2695
+ return name !== null && unstableShiftAggregateFunctions.has(name);
2696
+ });
2697
+ if (unstable) {
2698
+ const name = (_a = this.functionCallName(unstable)) !== null && _a !== void 0 ? _a : 'function';
2699
+ const identifier = (_b = unstable.getChild('FunctionIdentifier')) !== null && _b !== void 0 ? _b : unstable;
2700
+ this.pushSemanticDiagnostic(identifier, `function "${name}" cannot be used as a SHIFT operand`, `shift-operand:${shift.from}`);
2701
+ return;
2702
+ }
2703
+ const hasStableAggregate = calls.some((call) => {
2704
+ const name = this.functionCallName(call);
2705
+ return name !== null && stableShiftAggregateFunctions.has(name);
2706
+ });
2707
+ if (!hasStableAggregate) {
2708
+ this.pushSemanticDiagnostic(operand, 'SHIFT operand must be a stable aggregate or subquery measure', `shift-operand:${shift.from}`);
2709
+ }
2710
+ }
2711
+ checkEvalFieldReferenceContext(reference) {
2712
+ if (this.findAncestor(reference.parent, 'EvalExpr'))
2713
+ return;
2714
+ this.pushSemanticDiagnostic(reference, 'qualified field references are only supported inside eval', `eval-field-context:${reference.from}`);
2715
+ }
2716
+ checkEvalReferences(evalExpr) {
2717
+ const body = evalExpr.getChild('EvalCallBody');
2718
+ if (!body)
2719
+ return;
2720
+ const aliases = new Set();
2721
+ for (const argument of this.directChildren(body, 'OuterArgItem')) {
2722
+ const identifier = argument.getChild('Identifier');
2723
+ if (!identifier || !argument.getChild('EqlSingle'))
2724
+ continue;
2725
+ if (!argument.getChild('Query') && !argument.getChild('OuterFunctionCall'))
2726
+ continue;
2727
+ const name = this.nodeText(identifier).toLowerCase();
2728
+ if (aliases.has(name)) {
2729
+ this.pushSemanticDiagnostic(identifier, `duplicate eval subquery "${name}"`, `eval-alias-duplicate:${identifier.from}`);
2730
+ continue;
2731
+ }
2732
+ aliases.add(name);
2733
+ }
2734
+ const expression = body.getChild('EvalArgItem');
2735
+ if (!expression)
2736
+ return;
2737
+ for (const reference of this.descendantsNamed(expression, 'EvalFieldReference')) {
2738
+ const alias = reference.getChild('Identifier');
2739
+ if (!alias)
2740
+ continue;
2741
+ const name = this.nodeText(alias).toLowerCase();
2742
+ if (!aliases.has(name)) {
2743
+ this.pushSemanticDiagnostic(alias, `unknown eval subquery "${name}" in field reference`, `eval-field:${reference.from}`);
2744
+ }
2745
+ }
2746
+ if (aliases.size > 1) {
2747
+ for (const atom of this.descendantsNamed(expression, 'EvalAtom')) {
2748
+ const identifier = atom.getChild('Identifier');
2749
+ if (!identifier)
2750
+ continue;
2751
+ const name = this.nodeText(identifier).toLowerCase();
2752
+ if (!aliases.has(name)) {
2753
+ this.pushSemanticDiagnostic(identifier, `unknown eval subquery "${name}" in expression`, `eval-alias:${identifier.from}`);
2754
+ }
2755
+ }
2756
+ }
2757
+ }
2368
2758
  checkDuplicateShowClauses(show) {
2369
2759
  const seen = new Set();
2370
2760
  let child = show.firstChild;
@@ -2480,12 +2870,44 @@ export class Parser {
2480
2870
  }
2481
2871
  }
2482
2872
  checkFunctionCall(call) {
2873
+ var _a;
2483
2874
  const identifier = call.getChild('FunctionIdentifier');
2484
- const body = call.getChild('FunctionCallBody');
2875
+ const body = (_a = call.getChild('FunctionCallBody')) !== null && _a !== void 0 ? _a : call.getChild('EvalFunctionCallBody');
2485
2876
  if (!identifier || !body)
2486
2877
  return;
2487
2878
  const name = this.nodeText(identifier).toLowerCase();
2488
- const args = this.directChildren(body, 'ArgItem');
2879
+ const argumentName = body.name === 'EvalFunctionCallBody' ? 'EvalFunctionArg' : 'ArgItem';
2880
+ const args = this.directChildren(body, argumentName);
2881
+ const arity = documentedFunctionArities.get(name);
2882
+ if (arity && (args.length < arity.min || args.length > arity.max)) {
2883
+ this.pushSemanticDiagnostic(body, this.functionArityMessage(name, arity), `function-arity:${call.from}`);
2884
+ return;
2885
+ }
2886
+ if ((name === 'count_filter' || name === 'distinct_by_collapse') && args.length === 2 && !args[1].getChild('InValue')) {
2887
+ this.pushSemanticDiagnostic(args[1], `function "${name}" expects a value list as its second argument`, `function:${call.from}`);
2888
+ return;
2889
+ }
2890
+ if (name === 'histogram_quantile' && args.length === 2) {
2891
+ const quantile = this.numericValue(args[1]);
2892
+ if (quantile === null || quantile < 0 || quantile > 1) {
2893
+ this.pushSemanticDiagnostic(args[1], 'function "histogram_quantile" expects a quantile in range [0, 1]', `function:${call.from}`);
2894
+ return;
2895
+ }
2896
+ }
2897
+ if (name === 'drain' && args.length >= 2) {
2898
+ const threshold = this.numericValue(args[1]);
2899
+ if (threshold === null || threshold <= 0 || threshold > 1) {
2900
+ this.pushSemanticDiagnostic(args[1], 'function "drain" expects a similarity threshold in range (0, 1]', `function:${call.from}`);
2901
+ return;
2902
+ }
2903
+ if (args.length === 3) {
2904
+ const maxClusters = this.numericValue(args[2]);
2905
+ if (maxClusters === null || !Number.isInteger(maxClusters) || maxClusters < 1 || maxClusters > 10000) {
2906
+ this.pushSemanticDiagnostic(args[2], 'function "drain" expects max_clusters to be an integer in range [1, 10000]', `function:${call.from}`);
2907
+ return;
2908
+ }
2909
+ }
2910
+ }
2489
2911
  if (name === 'ewma') {
2490
2912
  const alpha = args.length === 2 ? this.numericValue(args[1]) : null;
2491
2913
  if (alpha === null || alpha <= 0 || alpha > 1) {
@@ -2514,6 +2936,17 @@ export class Parser {
2514
2936
  this.pushSemanticDiagnostic(body, `function "${name}" expects exactly one input field`, `function:${call.from}`);
2515
2937
  }
2516
2938
  }
2939
+ functionArityMessage(name, arity) {
2940
+ if (arity.min === arity.max) {
2941
+ if (arity.min === 0)
2942
+ return `function "${name}" does not accept arguments`;
2943
+ return `function "${name}" expects exactly ${arity.min} ${arity.min === 1 ? 'argument' : 'arguments'}`;
2944
+ }
2945
+ if (!Number.isFinite(arity.max)) {
2946
+ return `function "${name}" expects at least ${arity.min} ${arity.min === 1 ? 'argument' : 'arguments'}`;
2947
+ }
2948
+ return `function "${name}" expects between ${arity.min} and ${arity.max} arguments`;
2949
+ }
2517
2950
  checkOuterFunctionCall(call) {
2518
2951
  const identifier = call.firstChild;
2519
2952
  if (!identifier || identifier.name === 'FunctionIdentifier')
@@ -2612,10 +3045,36 @@ export class Parser {
2612
3045
  this.pushSemanticDiagnostic(field, 'field names containing "-" must be enclosed in backticks', `field-name:${field.from}`);
2613
3046
  }
2614
3047
  }
2615
- checkCaseFunction(identifier) {
2616
- if (this.nodeText(identifier).toLowerCase() === 'substr') {
2617
- this.pushSemanticDiagnostic(identifier, 'function "substr" is not supported inside CASE', `case-function:${identifier.from}`);
2618
- }
3048
+ checkCaseFunction(call) {
3049
+ const caseExpr = this.findAncestor(call.parent, 'CaseExpr');
3050
+ if (!caseExpr)
3051
+ return;
3052
+ const shiftExpr = this.findAncestor(call.parent, 'ShiftExpr');
3053
+ if (shiftExpr && shiftExpr.from >= caseExpr.from && shiftExpr.to <= caseExpr.to)
3054
+ return;
3055
+ const identifier = call.getChild('FunctionIdentifier');
3056
+ const body = call.getChild('FunctionCallBody');
3057
+ if (!identifier || !body)
3058
+ return;
3059
+ const name = this.nodeText(identifier).toLowerCase();
3060
+ const args = this.directChildren(body, 'ArgItem');
3061
+ const arity = documentedFunctionArities.get(name);
3062
+ if (arity && (args.length < arity.min || args.length > arity.max))
3063
+ return;
3064
+ if (caseScalarFunctions.has(name))
3065
+ return;
3066
+ if (stableShiftAggregateFunctions.has(name) && this.caseContainsShift(caseExpr))
3067
+ return;
3068
+ this.pushSemanticDiagnostic(identifier, `function "${name}" is not supported inside CASE`, `case-function:${identifier.from}`);
3069
+ }
3070
+ caseContainsShift(caseExpr) {
3071
+ const key = this.nodeRangeKey(caseExpr);
3072
+ const cached = this.caseShiftPresence.get(key);
3073
+ if (cached !== undefined)
3074
+ return cached;
3075
+ const containsShift = this.containsNode(caseExpr, 'ShiftExpr');
3076
+ this.caseShiftPresence.set(key, containsShift);
3077
+ return containsShift;
2619
3078
  }
2620
3079
  checkCaseResults(caseExpr) {
2621
3080
  let clause = caseExpr.firstChild;
@@ -2642,6 +3101,10 @@ export class Parser {
2642
3101
  const hasFrom = /\bfrom\s*=/.test(bodyText.toLowerCase());
2643
3102
  const filter = show.getChild('FilterClause');
2644
3103
  const hasSourceFilter = Boolean(filter && /@__source__/.test(this.maskStringsAndLineComments(this.nodeText(filter))));
3104
+ if (/^show_[a-z0-9]+_index$/.test(name)) {
3105
+ this.pushSemanticDiagnostic(identifier, `"${name}" is not supported by the DQL parser`, `show:${show.from}:unsupported`);
3106
+ return;
3107
+ }
2645
3108
  if ((name === 'show_tag_value' || name === 'show_tag_value_cardinality' || name === 'show_series_count_by_tag_value') && !hasKeyin) {
2646
3109
  this.pushSemanticDiagnostic(identifier, `"${name}" requires the named argument "keyin"`, `show:${show.from}:keyin`);
2647
3110
  }
@@ -2667,13 +3130,105 @@ export class Parser {
2667
3130
  }
2668
3131
  return children;
2669
3132
  }
3133
+ descendantsNamed(node, name) {
3134
+ const descendants = [];
3135
+ const visit = (current) => {
3136
+ let child = current.firstChild;
3137
+ while (child) {
3138
+ if (child.name === name)
3139
+ descendants.push(child);
3140
+ visit(child);
3141
+ child = child.nextSibling;
3142
+ }
3143
+ };
3144
+ visit(node);
3145
+ return descendants;
3146
+ }
3147
+ shiftOperandFunctionCalls(operand) {
3148
+ const querySource = operand.getChild('QuerySource');
3149
+ if (!querySource)
3150
+ return this.descendantsNamed(operand, 'FunctionCall');
3151
+ const query = querySource.getChild('Query');
3152
+ if (!query)
3153
+ return [];
3154
+ const calls = [];
3155
+ let clause = query.firstChild;
3156
+ while (clause) {
3157
+ if (clause.name === 'QueryClause') {
3158
+ const target = clause.getChild('TargetClause');
3159
+ if (target)
3160
+ calls.push(...this.descendantsNamed(target, 'FunctionCall'));
3161
+ }
3162
+ clause = clause.nextSibling;
3163
+ }
3164
+ return calls;
3165
+ }
3166
+ errorDescendantsWithText(node, text) {
3167
+ const errors = [];
3168
+ const expected = text.toLowerCase();
3169
+ const visit = (current) => {
3170
+ let child = current.firstChild;
3171
+ while (child) {
3172
+ if (child.type.isError && this.nodeText(child).toLowerCase() === expected)
3173
+ errors.push(child);
3174
+ visit(child);
3175
+ child = child.nextSibling;
3176
+ }
3177
+ };
3178
+ visit(node);
3179
+ return errors;
3180
+ }
3181
+ functionCallName(call) {
3182
+ const identifier = call.getChild('FunctionIdentifier');
3183
+ return identifier ? this.nodeText(identifier).toLowerCase() : null;
3184
+ }
3185
+ isPositiveFixedDuration(text) {
3186
+ const milliseconds = this.fixedDurationMilliseconds(text);
3187
+ return milliseconds !== null && milliseconds > 0;
3188
+ }
3189
+ fixedDurationMilliseconds(text) {
3190
+ const multipliers = {
3191
+ y: 365 * 24 * 60 * 60 * 1000,
3192
+ w: 7 * 24 * 60 * 60 * 1000,
3193
+ d: 24 * 60 * 60 * 1000,
3194
+ h: 60 * 60 * 1000,
3195
+ m: 60 * 1000,
3196
+ s: 1000,
3197
+ ms: 1,
3198
+ };
3199
+ const normalized = text.toLowerCase();
3200
+ const pattern = /(\d+)(ms|[ywdhms])/g;
3201
+ let total = 0;
3202
+ let consumed = 0;
3203
+ let part = pattern.exec(normalized);
3204
+ while (part) {
3205
+ if (part.index !== consumed)
3206
+ return null;
3207
+ const amount = Number(part[1]);
3208
+ const component = amount * multipliers[part[2]];
3209
+ if (!Number.isSafeInteger(amount) || !Number.isSafeInteger(component) || !Number.isSafeInteger(total + component))
3210
+ return null;
3211
+ total += component;
3212
+ consumed = pattern.lastIndex;
3213
+ part = pattern.exec(normalized);
3214
+ }
3215
+ return consumed === normalized.length ? total : null;
3216
+ }
2670
3217
  numericValue(node) {
2671
3218
  if (!node)
2672
3219
  return null;
2673
- const number = node.name === 'Number' ? node : node.getChild('Number');
3220
+ let valueNode = node;
3221
+ if (node.name === 'EvalFunctionArg') {
3222
+ const expression = node.getChild('EvalExpression');
3223
+ const atom = expression === null || expression === void 0 ? void 0 : expression.firstChild;
3224
+ if (!expression || (atom === null || atom === void 0 ? void 0 : atom.name) !== 'EvalAtom' || atom.nextSibling)
3225
+ return null;
3226
+ valueNode = atom;
3227
+ }
3228
+ const number = valueNode.name === 'Number' ? valueNode : valueNode.getChild('Number');
2674
3229
  if (!number)
2675
3230
  return null;
2676
- const sign = node.name !== 'Number' && node.getChild('Sub') ? -1 : 1;
3231
+ const sign = valueNode.name !== 'Number' && valueNode.getChild('Sub') ? -1 : 1;
2677
3232
  const value = sign * Number(this.nodeText(number).replace(/_/g, ''));
2678
3233
  return Number.isFinite(value) ? value : null;
2679
3234
  }