@tellaro/tql 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { tokenizeTql as T, TYPE_HINTS as f, BOOLEAN_PREDICATE_MUTATOR_SET as N, COMPARISON_OPERATORS as k, NEGATION_SYMBOL_OPERATORS as g, MUTATORS as w, SYMBOL_LOGICAL_OPERATORS as E } from "./tokenizer.js";
2
- const L = 50, O = /* @__PURE__ */ new Set(["logicalOp", "paren"]), v = /* @__PURE__ */ new Map([
1
+ import { tokenizeTql as b, TYPE_HINTS as g, BOOLEAN_PREDICATE_MUTATOR_SET as L, COMPARISON_OPERATORS as E, NEGATION_SYMBOL_OPERATORS as m, MUTATORS as S, SYMBOL_LOGICAL_OPERATORS as y, DATE_HISTOGRAM_CALENDAR_INTERVALS as A, GEO_GRID_PRECISION as O } from "./tokenizer.js";
2
+ const C = 50, w = /* @__PURE__ */ new Set(["logicalOp", "paren"]), v = /* @__PURE__ */ new Map([
3
3
  ["any", { op: "ANY", negated: !1 }],
4
4
  ["all", { op: "ALL", negated: !1 }],
5
5
  ["none", { op: "NONE", negated: !1 }],
@@ -10,13 +10,13 @@ const L = 50, O = /* @__PURE__ */ new Set(["logicalOp", "paren"]), v = /* @__PUR
10
10
  ["!all", { op: "ALL", negated: !0 }],
11
11
  ["!none", { op: "NONE", negated: !0 }]
12
12
  ]);
13
- function S(o, e) {
13
+ function G(a, e) {
14
14
  for (const [t, s] of v)
15
- if (s.op === o && s.negated === e && /^[a-z_]+$/.test(t))
15
+ if (s.op === a && s.negated === e && /^[a-z_]+$/.test(t))
16
16
  return t;
17
- throw new Error(`no word spelling for collection operator ${o} (negated=${e})`);
17
+ throw new Error(`no word spelling for collection operator ${a} (negated=${e})`);
18
18
  }
19
- const y = "Collection syntax accepted by the Rust engine only; the Python query API rejects it", _ = "Aggregation modifier on '*' accepted by the Rust engine only; the Python query API rejects it", A = /* @__PURE__ */ new Set([
19
+ const T = "Collection syntax accepted by the Rust engine only; the Python query API rejects it", $ = "Aggregation modifier on '*' accepted by the Rust engine only; the Python query API rejects it", N = /* @__PURE__ */ new Set([
20
20
  "field",
21
21
  "mutator",
22
22
  "typeHint",
@@ -24,8 +24,60 @@ const y = "Collection syntax accepted by the Rust engine only; the Python query
24
24
  "keyword",
25
25
  "visualization",
26
26
  "collectionOp"
27
- ]);
28
- class I {
27
+ ]), x = 1e9, _ = 1e4;
28
+ function M(a) {
29
+ if (a === null || /^auto$/i.test(a)) return null;
30
+ const e = /^auto\s*\(/i.test(a) && a.endsWith(")") ? a.slice(a.indexOf("(") + 1, -1) : null;
31
+ if (e !== null) {
32
+ const s = e.trim(), r = Number(s);
33
+ return /^[0-9]+$/.test(s) && String(r) === s && r >= 1 && r <= _ ? null : {
34
+ message: `Invalid date_histogram auto bucket target '${s}': it must be a whole number from 1 to ${_}`,
35
+ suggestion: "Write auto(50), or omit the interval for the default of 50 buckets"
36
+ };
37
+ }
38
+ if (A.includes(a.toLowerCase())) return null;
39
+ const t = /^([0-9]+)(ms|s|m|h|d)$/.exec(a);
40
+ if (t) {
41
+ const s = Number(t[1]);
42
+ if (s >= 1 && s <= x) return null;
43
+ }
44
+ return /^-?[A-Za-z0-9_.]+$/.test(a) ? {
45
+ message: `Invalid date_histogram interval '${a}'`,
46
+ suggestion: `A fixed interval is a positive whole number and a unit: ms, s, m, h or d (e.g. 30s, 5m, 1h, 1d); a calendar interval is one of: ${A.join(", ")}; or auto / auto(N)`
47
+ } : null;
48
+ }
49
+ function R(a, e) {
50
+ if (e === null) return null;
51
+ const [t, s, r] = O[a];
52
+ return /^[0-9]+$/.test(e) && Number(e) >= t && Number(e) <= s || !/^-?[0-9.]+$/.test(e) ? null : {
53
+ message: `Invalid ${a} precision '${e}': it must be a whole number from ${t} to ${s}`,
54
+ suggestion: `Omit the precision for OpenSearch's default of ${r}`
55
+ };
56
+ }
57
+ const P = /^[-+]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][-+]?[0-9]+)?$/;
58
+ function D(a) {
59
+ let e = null;
60
+ for (let t = 0; t < a.length; t++) {
61
+ const s = a[t];
62
+ if (!/^[-+]?[0-9.][0-9.eE+-]*$/.test(s)) return null;
63
+ if (!P.test(s) || !Number.isFinite(Number(s)))
64
+ return {
65
+ message: `Invalid range() boundary '${s}': it must be a number`,
66
+ suggestion: "Write range(field, 0, 1024, 65536): the field, then increasing numeric boundaries",
67
+ index: t
68
+ };
69
+ const r = s.replace(/^\+/, ""), i = Number(s);
70
+ if (e && !(i > e.value))
71
+ return {
72
+ message: `range() boundaries must be strictly increasing: '${r}' follows '${e.text}'`,
73
+ suggestion: "List each boundary once, smallest first",
74
+ index: t
75
+ };
76
+ e = { value: i, text: r };
77
+ }
78
+ return null;
79
+ }
80
+ class V {
29
81
  tokens;
30
82
  allTokens;
31
83
  // Including whitespace for highlighting
@@ -33,8 +85,9 @@ class I {
33
85
  errors;
34
86
  warnings;
35
87
  depth;
88
+ input;
36
89
  constructor(e) {
37
- this.allTokens = T(e), this.tokens = this.allTokens.filter((t) => t.type !== "whitespace"), this.current = 0, this.errors = [], this.warnings = [], this.depth = 0;
90
+ this.input = e, this.allTokens = b(e), this.tokens = this.allTokens.filter((t) => t.type !== "whitespace"), this.current = 0, this.errors = [], this.warnings = [], this.depth = 0;
38
91
  }
39
92
  /**
40
93
  * Parse the input and return the result
@@ -49,7 +102,7 @@ class I {
49
102
  success: !0
50
103
  };
51
104
  if (this.isStatsQuery())
52
- return this.checkStatsModifiers(), {
105
+ return this.checkStatsModifiers(), this.checkStatsBucketFunctions(), {
53
106
  ast: null,
54
107
  // Stats AST is produced by the Python/Rust parser
55
108
  tokens: this.allTokens,
@@ -92,7 +145,7 @@ class I {
92
145
  * Parse a full expression (lowest precedence)
93
146
  */
94
147
  parseExpression() {
95
- if (this.depth++, this.depth > L)
148
+ if (this.depth++, this.depth > C)
96
149
  throw new Error("Maximum query depth exceeded");
97
150
  try {
98
151
  return this.parseOr();
@@ -227,35 +280,35 @@ class I {
227
280
  let t = this.createFieldReference(e);
228
281
  const s = this.readTypeHintSlot(t);
229
282
  t = s.field;
230
- const n = [], i = [];
283
+ const r = [], i = [];
231
284
  for (; this.check("pipe"); )
232
285
  if (this.advance(), this.check("mutator") || this.isCatalogMutatorToken()) {
233
- const r = this.advance();
234
- n.push(r.value.toLowerCase()), i.push(this.parseMutatorCall(r));
286
+ const n = this.advance();
287
+ r.push(n.value.toLowerCase()), i.push(this.parseMutatorCall(n));
235
288
  } else if (this.check("field")) {
236
- const r = this.advance();
237
- n.push(r.value.toLowerCase()), this.addWarning(`Unknown mutator '${r.value}'`, r, "Check the mutator name spelling"), i.push(this.parseMutatorCall(r));
289
+ const n = this.advance();
290
+ r.push(n.value.toLowerCase()), this.addWarning(`Unknown mutator '${n.value}'`, n, "Check the mutator name spelling"), i.push(this.parseMutatorCall(n));
238
291
  } else {
239
- const r = this.isAtEnd() ? this.previous() : this.peek();
292
+ const n = this.isAtEnd() ? this.previous() : this.peek();
240
293
  this.addError(
241
294
  "Expected mutator name after '|'",
242
- r,
295
+ n,
243
296
  "MISSING_MUTATOR",
244
297
  "Add a mutator like 'lowercase', 'uppercase', 'trim', etc."
245
298
  );
246
299
  break;
247
300
  }
248
- if (n.length > 0 && (t = {
301
+ if (r.length > 0 && (t = {
249
302
  ...t,
250
- mutators: n,
303
+ mutators: r,
251
304
  mutatorCalls: i,
252
305
  end: this.previous().end
253
306
  }), this.check("doubleColon"))
254
307
  if (s.filled) {
255
- const r = this.advance();
308
+ const n = this.advance();
256
309
  this.addError(
257
310
  "A field may carry only one type hint",
258
- r,
311
+ n,
259
312
  "DUPLICATE_TYPE_HINT",
260
313
  `Write the hint once — either '${e.value}::${t.typeHint ?? "type"} | mutator' or '${e.value} | mutator::type'`
261
314
  ), (this.check("field") || this.check("typeHint") || this.check("mutator")) && this.advance();
@@ -281,7 +334,7 @@ class I {
281
334
  if (!this.check("doubleColon")) return { field: e, filled: !1 };
282
335
  this.advance();
283
336
  const t = this.isAtEnd() ? void 0 : this.peek();
284
- if (t !== void 0 && f.includes(t.value.toLowerCase())) {
337
+ if (t !== void 0 && g.includes(t.value.toLowerCase())) {
285
338
  const s = this.advance();
286
339
  return {
287
340
  field: { ...e, typeHint: s.value.toLowerCase(), end: s.end },
@@ -300,13 +353,13 @@ class I {
300
353
  this.previous(),
301
354
  // Derived from the catalog rather than a hand-written excerpt, which had drifted
302
355
  // to six of the fifteen names and omitted the very one being rejected.
303
- `Valid types: ${f.join(", ")}`
356
+ `Valid types: ${g.join(", ")}`
304
357
  ), { field: e, filled: !1 }) : (this.addError(
305
358
  "Expected type hint after '::'",
306
359
  this.peek(),
307
360
  "MISSING_TYPE_HINT",
308
361
  // Catalog-derived for the same reason as the warning arm, so the two cannot drift.
309
- `Name a type (${f.join(", ")}), or remove the '::'`
362
+ `Name a type (${g.join(", ")}), or remove the '::'`
310
363
  ), { field: e, filled: !1 });
311
364
  }
312
365
  /**
@@ -324,15 +377,15 @@ class I {
324
377
  if (t === void 0)
325
378
  return this.createPlaceholderExpression(e);
326
379
  if (this.addWarning(
327
- y,
380
+ T,
328
381
  e,
329
382
  `Write it field-first instead: field ${e.value.toLowerCase()} value`,
330
383
  "ENGINE_DIVERGENCE"
331
384
  ), !(this.check("field") || this.check("aggregation") || this.check("mutator") || this.check("typeHint"))) {
332
- const n = this.isAtEnd() ? this.previous() : this.peek();
385
+ const r = this.isAtEnd() ? this.previous() : this.peek();
333
386
  return this.addError(
334
387
  `Expected a field name after '${e.value}'`,
335
- n,
388
+ r,
336
389
  "MISSING_FIELD",
337
390
  `Use: ${e.value} field eq "value"`
338
391
  ), this.createPlaceholderExpression(e);
@@ -350,14 +403,14 @@ class I {
350
403
  * warning rather than being silently accepted or silently refused.
351
404
  */
352
405
  tryParseCollectionTail(e) {
353
- const t = this.check("logicalOp") && this.logicalWord(this.peek()) === "NOT", s = t ? 1 : 0, n = this.peekAt(s);
354
- if (n === void 0 || n.type !== "collectionOp" && n.type !== "operator") return null;
355
- const i = v.get(n.value.toLowerCase());
406
+ const t = this.check("logicalOp") && this.logicalWord(this.peek()) === "NOT", s = t ? 1 : 0, r = this.peekAt(s);
407
+ if (r === void 0 || r.type !== "collectionOp" && r.type !== "operator") return null;
408
+ const i = v.get(r.value.toLowerCase());
356
409
  if (i === void 0 || t && i.negated) return null;
357
410
  t && this.advance();
358
- const r = this.advance();
411
+ const n = this.advance();
359
412
  return this.finishCollection(
360
- r,
413
+ n,
361
414
  i.op,
362
415
  i.negated || t,
363
416
  e,
@@ -368,33 +421,33 @@ class I {
368
421
  * The shared tail of both collection forms: an OPTIONAL inner comparison operator
369
422
  * (defaulting to `eq`, as both grammars do) followed by a value or a value list.
370
423
  */
371
- finishCollection(e, t, s, n, i) {
372
- let r = "eq";
424
+ finishCollection(e, t, s, r, i) {
425
+ let n = "eq";
373
426
  if (this.check("operator")) {
374
- const h = this.advance();
375
- r = this.normalizeOperator(h.value), this.addWarning(
376
- y,
377
- h,
378
- `Drop the '${h.value}': the Python parser accepts only field ${e.value.toLowerCase()} value`,
427
+ const l = this.advance();
428
+ n = this.normalizeOperator(l.value), this.addWarning(
429
+ T,
430
+ l,
431
+ `Drop the '${l.value}': the Python parser accepts only field ${e.value.toLowerCase()} value`,
379
432
  "ENGINE_DIVERGENCE"
380
433
  );
381
434
  }
382
- const u = this.check("bracket") && this.peek().value === "[" ? this.parseValueList() : this.parseValue(), a = this.previous().end;
435
+ const h = this.check("bracket") && this.peek().value === "[" ? this.parseValueList() : this.parseValue(), o = this.previous().end;
383
436
  return {
384
437
  type: "collection",
385
438
  operator: t,
386
439
  negated: s,
387
- field: n,
440
+ field: r,
388
441
  condition: {
389
442
  type: "comparison",
390
- field: n,
391
- operator: r,
392
- value: u,
393
- start: n.start,
394
- end: a
443
+ field: r,
444
+ operator: n,
445
+ value: h,
446
+ start: r.start,
447
+ end: o
395
448
  },
396
449
  start: i,
397
- end: a
450
+ end: o
398
451
  };
399
452
  }
400
453
  /**
@@ -404,10 +457,10 @@ class I {
404
457
  * user typed rather than the normalised reference.
405
458
  */
406
459
  parseComparisonTail(e, t) {
407
- const s = t.mutators ?? [], n = this.consumeNegationPrefix();
460
+ const s = t.mutators ?? [], r = this.consumeNegationPrefix();
408
461
  if (!this.check("operator")) {
409
- const a = s.length > 0 ? s[s.length - 1] : null;
410
- if (n === null && a !== null && N.has(a))
462
+ const o = s.length > 0 ? s[s.length - 1] : null;
463
+ if (r === null && o !== null && L.has(o))
411
464
  return {
412
465
  type: "comparison",
413
466
  field: t,
@@ -422,10 +475,10 @@ class I {
422
475
  start: t.start,
423
476
  end: t.end
424
477
  };
425
- const h = this.isAtEnd() ? this.previous() : this.peek(), p = s.length > 0 ? `'${e.value}' with mutator` : `'${e.value}'`;
478
+ const l = this.isAtEnd() ? this.previous() : this.peek(), u = s.length > 0 ? `'${e.value}' with mutator` : `'${e.value}'`;
426
479
  return this.addError(
427
- `Expected operator after field ${p}`,
428
- h,
480
+ `Expected operator after field ${u}`,
481
+ l,
429
482
  "MISSING_OPERATOR",
430
483
  "Add an operator like 'eq', 'contains', '=', etc."
431
484
  ), {
@@ -450,21 +503,21 @@ class I {
450
503
  "INVALID_OPERATOR",
451
504
  "Use '=' or 'eq' for equality"
452
505
  );
453
- let r = this.normalizeOperator(i.value);
454
- if (n !== null) {
455
- const a = this.negateOperator(r);
456
- a === null ? this.addError(
457
- `'${n.value} ${i.value}' is not a TQL operator`,
458
- n,
506
+ let n = this.normalizeOperator(i.value);
507
+ if (r !== null) {
508
+ const o = this.negateOperator(n);
509
+ o === null ? this.addError(
510
+ `'${r.value} ${i.value}' is not a TQL operator`,
511
+ r,
459
512
  "INVALID_OPERATOR",
460
- `'${r}' has no negated form; use 'ne' or negate the whole expression: not (field ${r} value)`
461
- ) : r = a;
513
+ `'${n}' has no negated form; use 'ne' or negate the whole expression: not (field ${n} value)`
514
+ ) : n = o;
462
515
  }
463
- if (r === "is" && this.check("logicalOp") && this.logicalWord(this.peek()) === "NOT" && (this.advance(), r = "is_not"), r === "exists" || r === "not_exists")
516
+ if (n === "is" && this.check("logicalOp") && this.logicalWord(this.peek()) === "NOT" && (this.advance(), n = "is_not"), n === "exists" || n === "not_exists")
464
517
  return {
465
518
  type: "comparison",
466
519
  field: t,
467
- operator: r,
520
+ operator: n,
468
521
  value: {
469
522
  type: "literal",
470
523
  valueType: "boolean",
@@ -475,78 +528,78 @@ class I {
475
528
  start: t.start,
476
529
  end: i.end
477
530
  };
478
- if ((r === "is" || r === "is_not") && this.check("null")) {
479
- const a = this.advance();
531
+ if ((n === "is" || n === "is_not") && this.check("null")) {
532
+ const o = this.advance();
480
533
  return {
481
534
  type: "comparison",
482
535
  field: t,
483
- operator: r,
536
+ operator: n,
484
537
  value: {
485
538
  type: "literal",
486
539
  valueType: "null",
487
540
  value: null,
488
- start: a.start,
489
- end: a.end
541
+ start: o.start,
542
+ end: o.end
490
543
  },
491
544
  start: t.start,
492
- end: a.end
545
+ end: o.end
493
546
  };
494
547
  }
495
- if (/^(not_)?in(_cs)?$/.test(r)) {
496
- const a = this.parseValueList();
548
+ if (/^(not_)?in(_cs)?$/.test(n)) {
549
+ const o = this.parseValueList();
497
550
  return {
498
551
  type: "comparison",
499
552
  field: t,
500
- operator: r,
501
- value: a,
553
+ operator: n,
554
+ value: o,
502
555
  start: t.start,
503
556
  end: this.previous().end
504
557
  };
505
558
  }
506
- if (r === "between" || r === "not_between") {
559
+ if (n === "between" || n === "not_between") {
507
560
  if (this.check("bracket") && this.peek().value === "[") {
508
- const p = this.peek(), c = this.parseValueList();
561
+ const u = this.peek(), c = this.parseValueList();
509
562
  return c.length !== 2 && this.addError(
510
- `'${r}' needs exactly 2 values, got ${c.length}`,
511
- p,
563
+ `'${n}' needs exactly 2 values, got ${c.length}`,
564
+ u,
512
565
  "INVALID_VALUE",
513
- `Use: field ${r} [low, high] or field ${r} low AND high`
566
+ `Use: field ${n} [low, high] or field ${n} low AND high`
514
567
  ), {
515
568
  type: "comparison",
516
569
  field: t,
517
- operator: r,
570
+ operator: n,
518
571
  value: c,
519
572
  start: t.start,
520
573
  end: this.previous().end
521
574
  };
522
575
  }
523
- const a = this.parseValue();
576
+ const o = this.parseValue();
524
577
  this.matchLogicalWord("AND") || this.addError(
525
578
  "Expected 'AND' between values in 'between' operator",
526
579
  this.isAtEnd() ? this.previous() : this.peek(),
527
580
  "INVALID_SYNTAX",
528
581
  "Use format: field between value1 AND value2"
529
582
  );
530
- const h = this.parseValue();
583
+ const l = this.parseValue();
531
584
  return {
532
585
  type: "comparison",
533
586
  field: t,
534
- operator: r,
535
- value: [a, h],
587
+ operator: n,
588
+ value: [o, l],
536
589
  start: t.start,
537
- end: h.end
590
+ end: l.end
538
591
  };
539
592
  }
540
593
  if (this.isAtEnd())
541
594
  return this.addError(
542
- `Expected a value after operator '${r}'`,
595
+ `Expected a value after operator '${n}'`,
543
596
  i,
544
597
  "MISSING_VALUE",
545
598
  'Add a value like "text", 123, or true'
546
599
  ), {
547
600
  type: "comparison",
548
601
  field: t,
549
- operator: r,
602
+ operator: n,
550
603
  value: {
551
604
  type: "literal",
552
605
  valueType: "string",
@@ -557,14 +610,14 @@ class I {
557
610
  start: t.start,
558
611
  end: i.end
559
612
  };
560
- const u = this.parseValue();
613
+ const h = this.parseValue();
561
614
  return {
562
615
  type: "comparison",
563
616
  field: t,
564
- operator: r,
565
- value: u,
617
+ operator: n,
618
+ value: h,
566
619
  start: t.start,
567
- end: u.end
620
+ end: h.end
568
621
  };
569
622
  }
570
623
  /**
@@ -680,9 +733,9 @@ class I {
680
733
  return { name: t, args: s, parenthesized: !1, start: e.start, end: e.end };
681
734
  if (this.advance(), !(this.check("paren") && this.peek().value === ")"))
682
735
  for (; ; ) {
683
- const n = this.parseMutatorArgument(t);
684
- if (n === null) break;
685
- if (s.push(n), this.check("comma")) {
736
+ const r = this.parseMutatorArgument(t);
737
+ if (r === null) break;
738
+ if (s.push(r), this.check("comma")) {
686
739
  this.advance();
687
740
  continue;
688
741
  }
@@ -703,11 +756,11 @@ class I {
703
756
  */
704
757
  parseMutatorArgument(e) {
705
758
  const t = this.isAtEnd() ? this.previous() : this.peek();
706
- if (A.has(t.type) && this.peekAt(1)?.type === "operator" && this.peekAt(1)?.value === "=") {
707
- const n = this.advance();
759
+ if (N.has(t.type) && this.peekAt(1)?.type === "operator" && this.peekAt(1)?.value === "=") {
760
+ const r = this.advance();
708
761
  this.advance();
709
762
  const i = this.parseMutatorArgumentValue(e);
710
- return i === null ? null : { name: n.value, ...i, start: n.start, end: i.end };
763
+ return i === null ? null : { name: r.value, ...i, start: r.start, end: i.end };
711
764
  }
712
765
  const s = this.parseMutatorArgumentValue(e);
713
766
  return s === null ? null : s;
@@ -736,7 +789,7 @@ class I {
736
789
  const s = this.advance();
737
790
  return { valueType: "null", value: null, start: s.start, end: s.end };
738
791
  }
739
- if (A.has(t.type) && !this.isAtEnd()) {
792
+ if (N.has(t.type) && !this.isAtEnd()) {
740
793
  const s = this.advance();
741
794
  return { valueType: "identifier", value: s.value, start: s.start, end: s.end };
742
795
  }
@@ -806,7 +859,7 @@ class I {
806
859
  */
807
860
  negateOperator(e) {
808
861
  const t = `not_${e}`;
809
- return k.includes(t) ? t : null;
862
+ return E.includes(t) ? t : null;
810
863
  }
811
864
  normalizeOperator(e) {
812
865
  const t = e.toLowerCase(), s = {
@@ -819,14 +872,14 @@ class I {
819
872
  };
820
873
  if (e in s)
821
874
  return s[e];
822
- if (t in g)
823
- return g[t];
824
- const n = {
875
+ if (t in m)
876
+ return m[t];
877
+ const r = {
825
878
  regex: "regexp",
826
879
  starts_with: "startswith",
827
880
  ends_with: "endswith"
828
881
  };
829
- return t in n ? n[t] : (k.includes(t), t);
882
+ return t in r ? r[t] : (E.includes(t), t);
830
883
  }
831
884
  isAtEnd() {
832
885
  return this.current >= this.tokens.length;
@@ -858,7 +911,7 @@ class I {
858
911
  isCatalogMutatorToken() {
859
912
  if (this.isAtEnd()) return !1;
860
913
  const e = this.peek();
861
- return e.type !== "collectionOp" && e.type !== "aggregation" ? !1 : w.includes(e.value.toLowerCase());
914
+ return e.type !== "collectionOp" && e.type !== "aggregation" ? !1 : S.includes(e.value.toLowerCase());
862
915
  }
863
916
  match(...e) {
864
917
  for (const t of e)
@@ -878,7 +931,7 @@ class I {
878
931
  * source text, which the highlighting and completion surfaces read.
879
932
  */
880
933
  logicalWord(e) {
881
- return E[e.value] ?? e.value.toUpperCase();
934
+ return y[e.value] ?? e.value.toUpperCase();
882
935
  }
883
936
  /**
884
937
  * Match the WORD spelling of a logical operator only, never the symbol.
@@ -889,9 +942,9 @@ class I {
889
942
  * would make this package the only one of the three that does.
890
943
  */
891
944
  matchLogicalWord(e) {
892
- return this.check("logicalOp") && this.peek().value.toUpperCase() === e && !(this.peek().value in E) ? (this.advance(), !0) : !1;
945
+ return this.check("logicalOp") && this.peek().value.toUpperCase() === e && !(this.peek().value in y) ? (this.advance(), !0) : !1;
893
946
  }
894
- addError(e, t, s, n) {
947
+ addError(e, t, s, r) {
895
948
  this.errors.push({
896
949
  message: e,
897
950
  start: t.start,
@@ -900,10 +953,10 @@ class I {
900
953
  column: t.column,
901
954
  severity: "error",
902
955
  code: s,
903
- suggestion: n
956
+ suggestion: r
904
957
  });
905
958
  }
906
- addWarning(e, t, s, n = "TYPE_MISMATCH") {
959
+ addWarning(e, t, s, r = "TYPE_MISMATCH") {
907
960
  this.warnings.push({
908
961
  message: e,
909
962
  start: t.start,
@@ -911,7 +964,7 @@ class I {
911
964
  line: t.line,
912
965
  column: t.column,
913
966
  severity: "warning",
914
- code: n,
967
+ code: r,
915
968
  suggestion: s
916
969
  });
917
970
  }
@@ -921,7 +974,7 @@ class I {
921
974
  */
922
975
  synchronize() {
923
976
  for (this.advance(); !this.isAtEnd(); ) {
924
- if (O.has(this.peek().type) || this.peek().type === "paren" && this.peek().value === ")")
977
+ if (w.has(this.peek().type) || this.peek().type === "paren" && this.peek().value === ")")
925
978
  return;
926
979
  this.advance();
927
980
  }
@@ -982,58 +1035,114 @@ class I {
982
1035
  const e = (s) => s !== void 0 && s.type === "keyword" && (s.value.toLowerCase() === "top" || s.value.toLowerCase() === "bottom");
983
1036
  let t = this.tokens.length;
984
1037
  for (let s = 0; s < this.tokens.length; s++) {
985
- const n = this.tokens[s];
986
- if (n.type === "keyword" && n.value.toLowerCase() === "by" || n.value === "=>") {
1038
+ const r = this.tokens[s];
1039
+ if (r.type === "keyword" && r.value.toLowerCase() === "by" || r.value === "=>") {
987
1040
  t = s;
988
1041
  break;
989
1042
  }
990
1043
  }
991
1044
  for (let s = 0; s < t; s++) {
992
1045
  if (this.tokens[s].type !== "aggregation") continue;
993
- const n = s + 1;
994
- if (n >= t || this.tokens[n].value !== "(") continue;
995
- let i = 0, r = -1, u = -1, a;
996
- for (let l = n; l < t; l++) {
997
- const d = this.tokens[l];
998
- if (d.value === "(") i++;
999
- else if (d.value === ")") {
1046
+ const r = s + 1;
1047
+ if (r >= t || this.tokens[r].value !== "(") continue;
1048
+ let i = 0, n = -1, h = -1, o;
1049
+ for (let d = r; d < t; d++) {
1050
+ const p = this.tokens[d];
1051
+ if (p.value === "(") i++;
1052
+ else if (p.value === ")") {
1000
1053
  if (i--, i === 0) {
1001
- r = l;
1054
+ n = d;
1002
1055
  break;
1003
1056
  }
1004
- } else i === 1 && d.type === "comma" && (u === -1 && (u = l), e(this.tokens[l + 1]) && (a = this.tokens[l + 1]));
1057
+ } else i === 1 && p.type === "comma" && (h === -1 && (h = d), e(this.tokens[d + 1]) && (o = this.tokens[d + 1]));
1005
1058
  }
1006
- if (r === -1) continue;
1007
- const h = this.tokens.slice(n + 1, u === -1 ? r : u), p = h.length === 0 || h.length === 1 && h[0].value === "*", c = e(this.tokens[r + 1]) ? this.tokens[r + 1] : void 0;
1008
- a !== void 0 && c !== void 0 && this.addError(
1059
+ if (n === -1) continue;
1060
+ const l = this.tokens.slice(r + 1, h === -1 ? n : h), u = l.length === 0 || l.length === 1 && l[0].value === "*", c = e(this.tokens[n + 1]) ? this.tokens[n + 1] : void 0;
1061
+ o !== void 0 && c !== void 0 && this.addError(
1009
1062
  "The 'top'/'bottom' modifier is given twice on one aggregation",
1010
1063
  c,
1011
1064
  "INVALID_STATS_MODIFIER",
1012
1065
  "Write it once — either 'sum(x, top 10)' or 'sum(x) top 10'"
1013
1066
  );
1014
- for (const l of [a, c]) {
1015
- if (l === void 0) continue;
1016
- const d = this.tokens.indexOf(l);
1017
- this.tokens[d + 1]?.type !== "number" && this.addError(
1018
- `Expected a count after '${l.value}'`,
1019
- l,
1067
+ for (const d of [o, c]) {
1068
+ if (d === void 0) continue;
1069
+ const p = this.tokens.indexOf(d);
1070
+ this.tokens[p + 1]?.type !== "number" && this.addError(
1071
+ `Expected a count after '${d.value}'`,
1072
+ d,
1020
1073
  "INVALID_STATS_MODIFIER",
1021
- `Give the modifier a number, like '${l.value.toLowerCase()} 10'`
1074
+ `Give the modifier a number, like '${d.value.toLowerCase()} 10'`
1022
1075
  );
1023
1076
  }
1024
- a !== void 0 && p && this.addError(
1077
+ o !== void 0 && u && this.addError(
1025
1078
  "'*' cannot take an in-parens 'top'/'bottom' modifier",
1026
- a,
1079
+ o,
1027
1080
  "INVALID_STATS_MODIFIER",
1028
1081
  "Name a field, or move the modifier outside the parentheses"
1029
- ), c !== void 0 && p && a === void 0 && this.addWarning(
1030
- _,
1082
+ ), c !== void 0 && u && o === void 0 && this.addWarning(
1083
+ $,
1031
1084
  c,
1032
1085
  "Name a field instead of '*', which both engines accept",
1033
1086
  "ENGINE_DIVERGENCE"
1034
1087
  );
1035
1088
  }
1036
1089
  }
1090
+ /**
1091
+ * Flag group-by bucket functions whose ARGUMENTS both engines refuse at parse time.
1092
+ *
1093
+ * Like `checkStatsModifiers`, a targeted scan rather than a stats parser: it reads each
1094
+ * `bucketFunction` call's comma-separated arguments and applies the same argument rules as
1095
+ * `stats_buckets.parse_date_histogram_interval` (Python) / `parse_date_histogram_interval`
1096
+ * (Rust), with the same message text, so the editor reports what the engine will. Shapes the
1097
+ * grammars themselves refuse (a missing field, `top N` on a bucket level) are left to the
1098
+ * engines' ordinary syntax errors. `tests/statsBucketKinds.spec.ts` replays
1099
+ * `validation/stats_bucket_errors.json` against this.
1100
+ */
1101
+ checkStatsBucketFunctions() {
1102
+ for (let e = 0; e < this.tokens.length; e++) {
1103
+ const t = this.tokens[e];
1104
+ if (t.type !== "bucketFunction" || this.tokens[e + 1]?.value !== "(") continue;
1105
+ const s = [[]];
1106
+ let r = 0, i = !1;
1107
+ for (let l = e + 1; l < this.tokens.length; l++) {
1108
+ const u = this.tokens[l];
1109
+ if (u.value === "(") {
1110
+ if (r++, r === 1) continue;
1111
+ } else if (u.value === ")") {
1112
+ if (r--, r === 0) {
1113
+ i = !0;
1114
+ break;
1115
+ }
1116
+ } else if (r === 1 && u.type === "comma") {
1117
+ s.push([]);
1118
+ continue;
1119
+ }
1120
+ s[s.length - 1].push(u);
1121
+ }
1122
+ if (!i) continue;
1123
+ const n = (l) => l && l.length > 0 ? this.input.slice(l[0].start, l[l.length - 1].end) : null, h = t.value.toLowerCase();
1124
+ let o = null;
1125
+ if (h === "date_histogram" ? o = M(n(s[1])) : (h === "geotile" || h === "geohash") && (o = R(h, n(s[1]))), o) {
1126
+ this.addError(o.message, s[1][0], "INVALID_STATS_BUCKET", o.suggestion);
1127
+ continue;
1128
+ }
1129
+ if (h === "range") {
1130
+ const l = s.slice(1).map((c) => ({ raw: n(c), at: c[0] })), u = D(l.map((c) => c.raw ?? ""));
1131
+ u && this.addError(u.message, l[u.index]?.at ?? t, "INVALID_STATS_BUCKET", u.suggestion);
1132
+ } else if (h === "filters") {
1133
+ const l = /* @__PURE__ */ new Set();
1134
+ s.forEach((u, c) => {
1135
+ const d = u.findIndex((k) => k.type === "keyword" && k.value.toLowerCase() === "as"), p = d >= 0 ? u[d + 1] : void 0, f = p ? String(p.value) : `filter_${c + 1}`;
1136
+ l.has(f) && this.addError(
1137
+ `Duplicate filters() label '${f}'`,
1138
+ p ?? u[0] ?? t,
1139
+ "INVALID_STATS_BUCKET",
1140
+ "Give every filter a distinct label with `as '<label>'`"
1141
+ ), l.add(f);
1142
+ });
1143
+ }
1144
+ }
1145
+ }
1037
1146
  /**
1038
1147
  * Check if the token stream represents a stats query.
1039
1148
  * Handles: "| stats ...", "stats ...", or starts with a filter before "| stats".
@@ -1058,24 +1167,27 @@ class I {
1058
1167
  return t >= this.tokens.length ? !1 : this.tokens[t].type === "keyword" && this.tokens[t].value.toLowerCase() === "stats";
1059
1168
  }
1060
1169
  }
1061
- function m(o) {
1062
- return o ? new I(o).parse() : { ast: null, errors: [], warnings: [], tokens: [], success: !0 };
1170
+ function I(a) {
1171
+ return a ? new V(a).parse() : { ast: null, errors: [], warnings: [], tokens: [], success: !0 };
1063
1172
  }
1064
- function b(o) {
1065
- return m(o).success;
1173
+ function W(a) {
1174
+ return I(a).success;
1066
1175
  }
1067
- function x(o) {
1068
- return m(o).errors;
1176
+ function F(a) {
1177
+ return I(a).errors;
1069
1178
  }
1070
- function M(o) {
1071
- let e = `Error at line ${o.line}, column ${o.column}: ${o.message}`;
1072
- return o.suggestion && (e += ` (${o.suggestion})`), e;
1179
+ function q(a) {
1180
+ let e = `Error at line ${a.line}, column ${a.column}: ${a.message}`;
1181
+ return a.suggestion && (e += ` (${a.suggestion})`), e;
1073
1182
  }
1074
1183
  export {
1075
- I as TqlParser,
1076
- S as collectionOperatorSpelling,
1077
- M as formatParseError,
1078
- x as getTqlErrors,
1079
- b as isValidTql,
1080
- m as parseTql
1184
+ V as TqlParser,
1185
+ G as collectionOperatorSpelling,
1186
+ M as dateHistogramIntervalProblem,
1187
+ q as formatParseError,
1188
+ R as geoPrecisionProblem,
1189
+ F as getTqlErrors,
1190
+ W as isValidTql,
1191
+ I as parseTql,
1192
+ D as rangeBoundariesProblem
1081
1193
  };