@tellaro/tql 4.0.2 → 5.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.
package/dist/index.js CHANGED
@@ -1,90 +1,98 @@
1
- import { AGGREGATION_FUNCTIONS as t, BOOLEAN_PREDICATE_MUTATORS as o, BOOLEAN_PREDICATE_MUTATOR_SET as i, COLLECTION_OPERATORS as T, COMPARISON_OPERATORS as a, DATE_HISTOGRAM_CALENDAR_INTERVALS as E, DATE_HISTOGRAM_FIXED_UNITS as S, GEO_GRID_PRECISION as _, GROUP_BY_BUCKET_FUNCTIONS as l, KEYWORDS as A, LOGICAL_OPERATORS as O, MUTATORS as n, NEGATION_SYMBOL_OPERATORS as R, SYMBOL_OPERATORS as s, TYPE_HINTS as g, TqlTokenizer as m, VISUALIZATION_TYPES as P, findPreviousToken as I, findTokenAtPosition as N, findTokenBefore as C, findTokensInRange as c, tokenizeTql as p, tokenizeTqlNoWhitespace as L } from "./src/tokenizer.js";
2
- import { TqlParser as f, dateHistogramIntervalProblem as h, formatParseError as x, geoPrecisionProblem as U, getTqlErrors as M, isValidTql as u, parseTql as y, rangeBoundariesProblem as q } from "./src/parser.js";
3
- import { CASE_SENSITIVE_MEMBERSHIP_OPERATORS as V, CASE_SENSITIVE_OPERATORS as z, CASE_SENSITIVE_WHOLE_VALUE_OPERATORS as D, MAX_REGEX_PATTERN_LENGTH as F, TqlValidator as Y, casePreservingForm as B, caseSensitiveSupport as H, formatValidationResult as v, getTqlIssues as k, isValidTqlSyntax as Q, validateTql as W } from "./src/validator.js";
4
- import { getCompletionTriggerCharacters as b, getCompletions as K, getWordAtPosition as w, shouldTriggerCompletion as Z } from "./src/completionProvider.js";
5
- import { findClosestMatch as J, fuzzyFilter as $, fuzzyMatch as ee, highlightMatch as re, levenshteinDistance as te } from "./src/fuzzyMatcher.js";
6
- import { SchemaCache as ie, createSchemaCache as Te, getCachedField as ae, getCachedFieldNames as Ee, getCachedFieldType as Se, getSchemaCache as _e } from "./src/schemaCache.js";
7
- import { ComplexityAnalyzer as Ae, analyzeComplexity as Oe, getComplexityMetrics as ne, getComplexityWarnings as Re, isHighComplexity as se } from "./src/complexityAnalyzer.js";
8
- import { SemanticPreview as me, flattenLogicTree as Pe, formatLogicTreeAscii as Ie, formatLogicTreeNode as Ne, generatePreview as Ce, getFieldUsage as ce, getLogicTree as pe, getLogicTreeDepth as Le, getOperatorSummary as de, getUniqueFields as fe } from "./src/semanticPreview.js";
9
- import { buildStatsQuery as xe, parseVizHintString as Ue } from "./src/builder.js";
10
- import { QUERY_PLAN_EXPLAIN_KEYS as ue, QUERY_PLAN_EXPLAIN_VERSION as ye, QUERY_PLAN_RULES as qe, summarizeQueryPlan as Ge } from "./src/queryPlan.js";
1
+ import { AGGREGATION_FUNCTIONS as t, BOOLEAN_PREDICATE_MUTATORS as o, BOOLEAN_PREDICATE_MUTATOR_SET as i, COLLECTION_OPERATORS as T, COMPARISON_OPERATORS as a, DATE_HISTOGRAM_CALENDAR_INTERVALS as E, DATE_HISTOGRAM_FIXED_UNITS as O, GEO_GRID_PRECISION as S, GROUP_BY_BUCKET_FUNCTIONS as l, KEYWORDS as R, LOGICAL_OPERATORS as _, MUTATORS as n, NEGATION_SYMBOL_OPERATORS as A, SYMBOL_OPERATORS as s, TYPE_HINTS as g, TqlTokenizer as m, VISUALIZATION_TYPES as I, findPreviousToken as P, findTokenAtPosition as N, findTokenBefore as C, findTokensInRange as c, tokenizeTql as d, tokenizeTqlNoWhitespace as p } from "./src/tokenizer.js";
2
+ import { TqlParser as f, dateHistogramIntervalProblem as h, formatParseError as x, geoPrecisionProblem as M, getTqlErrors as U, isValidTql as u, parseTql as y, rangeBoundariesProblem as D } from "./src/parser.js";
3
+ import { CASE_SENSITIVE_MEMBERSHIP_OPERATORS as V, CASE_SENSITIVE_OPERATORS as q, CASE_SENSITIVE_WHOLE_VALUE_OPERATORS as z, MAX_REGEX_PATTERN_LENGTH as F, TqlValidator as Y, casePreservingForm as B, caseSensitiveSupport as H, formatValidationResult as k, getTqlIssues as v, isValidTqlSyntax as b, validateTql as Q } from "./src/validator.js";
4
+ import { ORDERABLE_FIELD_TYPES as X, ORDERING_OPERATORS as K, checkOrderingComparison as w, isOrderingOperator as Z, literalIsOrderable as j, orderingRefusalMessage as J, orderingVerdict as $ } from "./src/ordering.js";
5
+ import { getCompletionTriggerCharacters as re, getCompletions as te, getWordAtPosition as oe, shouldTriggerCompletion as ie } from "./src/completionProvider.js";
6
+ import { findClosestMatch as ae, fuzzyFilter as Ee, fuzzyMatch as Oe, highlightMatch as Se, levenshteinDistance as le } from "./src/fuzzyMatcher.js";
7
+ import { SchemaCache as _e, createSchemaCache as ne, getCachedField as Ae, getCachedFieldNames as se, getCachedFieldType as ge, getSchemaCache as me } from "./src/schemaCache.js";
8
+ import { ComplexityAnalyzer as Pe, analyzeComplexity as Ne, getComplexityMetrics as Ce, getComplexityWarnings as ce, isHighComplexity as de } from "./src/complexityAnalyzer.js";
9
+ import { SemanticPreview as Le, flattenLogicTree as fe, formatLogicTreeAscii as he, formatLogicTreeNode as xe, generatePreview as Me, getFieldUsage as Ue, getLogicTree as ue, getLogicTreeDepth as ye, getOperatorSummary as De, getUniqueFields as Ge } from "./src/semanticPreview.js";
10
+ import { buildStatsQuery as qe, parseVizHintString as ze } from "./src/builder.js";
11
+ import { QUERY_PLAN_EXPLAIN_KEYS as Ye, QUERY_PLAN_EXPLAIN_VERSION as Be, QUERY_PLAN_RULES as He, summarizeQueryPlan as ke } from "./src/queryPlan.js";
11
12
  export {
12
13
  t as AGGREGATION_FUNCTIONS,
13
14
  o as BOOLEAN_PREDICATE_MUTATORS,
14
15
  i as BOOLEAN_PREDICATE_MUTATOR_SET,
15
16
  V as CASE_SENSITIVE_MEMBERSHIP_OPERATORS,
16
- z as CASE_SENSITIVE_OPERATORS,
17
- D as CASE_SENSITIVE_WHOLE_VALUE_OPERATORS,
17
+ q as CASE_SENSITIVE_OPERATORS,
18
+ z as CASE_SENSITIVE_WHOLE_VALUE_OPERATORS,
18
19
  T as COLLECTION_OPERATORS,
19
20
  a as COMPARISON_OPERATORS,
20
- Ae as ComplexityAnalyzer,
21
+ Pe as ComplexityAnalyzer,
21
22
  E as DATE_HISTOGRAM_CALENDAR_INTERVALS,
22
- S as DATE_HISTOGRAM_FIXED_UNITS,
23
- _ as GEO_GRID_PRECISION,
23
+ O as DATE_HISTOGRAM_FIXED_UNITS,
24
+ S as GEO_GRID_PRECISION,
24
25
  l as GROUP_BY_BUCKET_FUNCTIONS,
25
- A as KEYWORDS,
26
- O as LOGICAL_OPERATORS,
26
+ R as KEYWORDS,
27
+ _ as LOGICAL_OPERATORS,
27
28
  F as MAX_REGEX_PATTERN_LENGTH,
28
29
  n as MUTATORS,
29
- R as NEGATION_SYMBOL_OPERATORS,
30
- ue as QUERY_PLAN_EXPLAIN_KEYS,
31
- ye as QUERY_PLAN_EXPLAIN_VERSION,
32
- qe as QUERY_PLAN_RULES,
30
+ A as NEGATION_SYMBOL_OPERATORS,
31
+ X as ORDERABLE_FIELD_TYPES,
32
+ K as ORDERING_OPERATORS,
33
+ Ye as QUERY_PLAN_EXPLAIN_KEYS,
34
+ Be as QUERY_PLAN_EXPLAIN_VERSION,
35
+ He as QUERY_PLAN_RULES,
33
36
  s as SYMBOL_OPERATORS,
34
- ie as SchemaCache,
35
- me as SemanticPreview,
37
+ _e as SchemaCache,
38
+ Le as SemanticPreview,
36
39
  g as TYPE_HINTS,
37
40
  f as TqlParser,
38
41
  m as TqlTokenizer,
39
42
  Y as TqlValidator,
40
- P as VISUALIZATION_TYPES,
41
- Oe as analyzeComplexity,
42
- xe as buildStatsQuery,
43
+ I as VISUALIZATION_TYPES,
44
+ Ne as analyzeComplexity,
45
+ qe as buildStatsQuery,
43
46
  B as casePreservingForm,
44
47
  H as caseSensitiveSupport,
45
- Te as createSchemaCache,
48
+ w as checkOrderingComparison,
49
+ ne as createSchemaCache,
46
50
  h as dateHistogramIntervalProblem,
47
- J as findClosestMatch,
48
- I as findPreviousToken,
51
+ ae as findClosestMatch,
52
+ P as findPreviousToken,
49
53
  N as findTokenAtPosition,
50
54
  C as findTokenBefore,
51
55
  c as findTokensInRange,
52
- Pe as flattenLogicTree,
53
- Ie as formatLogicTreeAscii,
54
- Ne as formatLogicTreeNode,
56
+ fe as flattenLogicTree,
57
+ he as formatLogicTreeAscii,
58
+ xe as formatLogicTreeNode,
55
59
  x as formatParseError,
56
- v as formatValidationResult,
57
- $ as fuzzyFilter,
58
- ee as fuzzyMatch,
59
- Ce as generatePreview,
60
- U as geoPrecisionProblem,
61
- ae as getCachedField,
62
- Ee as getCachedFieldNames,
63
- Se as getCachedFieldType,
64
- b as getCompletionTriggerCharacters,
65
- K as getCompletions,
66
- ne as getComplexityMetrics,
67
- Re as getComplexityWarnings,
68
- ce as getFieldUsage,
69
- pe as getLogicTree,
70
- Le as getLogicTreeDepth,
71
- de as getOperatorSummary,
72
- _e as getSchemaCache,
73
- M as getTqlErrors,
74
- k as getTqlIssues,
75
- fe as getUniqueFields,
76
- w as getWordAtPosition,
77
- re as highlightMatch,
78
- se as isHighComplexity,
60
+ k as formatValidationResult,
61
+ Ee as fuzzyFilter,
62
+ Oe as fuzzyMatch,
63
+ Me as generatePreview,
64
+ M as geoPrecisionProblem,
65
+ Ae as getCachedField,
66
+ se as getCachedFieldNames,
67
+ ge as getCachedFieldType,
68
+ re as getCompletionTriggerCharacters,
69
+ te as getCompletions,
70
+ Ce as getComplexityMetrics,
71
+ ce as getComplexityWarnings,
72
+ Ue as getFieldUsage,
73
+ ue as getLogicTree,
74
+ ye as getLogicTreeDepth,
75
+ De as getOperatorSummary,
76
+ me as getSchemaCache,
77
+ U as getTqlErrors,
78
+ v as getTqlIssues,
79
+ Ge as getUniqueFields,
80
+ oe as getWordAtPosition,
81
+ Se as highlightMatch,
82
+ de as isHighComplexity,
83
+ Z as isOrderingOperator,
79
84
  u as isValidTql,
80
- Q as isValidTqlSyntax,
81
- te as levenshteinDistance,
85
+ b as isValidTqlSyntax,
86
+ le as levenshteinDistance,
87
+ j as literalIsOrderable,
88
+ J as orderingRefusalMessage,
89
+ $ as orderingVerdict,
82
90
  y as parseTql,
83
- Ue as parseVizHintString,
84
- q as rangeBoundariesProblem,
85
- Z as shouldTriggerCompletion,
86
- Ge as summarizeQueryPlan,
87
- p as tokenizeTql,
88
- L as tokenizeTqlNoWhitespace,
89
- W as validateTql
91
+ ze as parseVizHintString,
92
+ D as rangeBoundariesProblem,
93
+ ie as shouldTriggerCompletion,
94
+ ke as summarizeQueryPlan,
95
+ d as tokenizeTql,
96
+ p as tokenizeTqlNoWhitespace,
97
+ Q as validateTql
90
98
  };
@@ -1,7 +1,7 @@
1
- import { BOOLEAN_PREDICATE_MUTATORS as E, tokenizeTql as T, COMPARISON_OPERATORS as R, LOGICAL_OPERATORS as $, GROUP_BY_BUCKET_FUNCTIONS as P, AGGREGATION_FUNCTIONS as D, VISUALIZATION_TYPES as M, TYPE_HINTS as z, MUTATORS as U } from "./tokenizer.js";
1
+ import { BOOLEAN_PREDICATE_MUTATORS as E, tokenizeTql as T, COMPARISON_OPERATORS as $, LOGICAL_OPERATORS as R, GROUP_BY_BUCKET_FUNCTIONS as P, AGGREGATION_FUNCTIONS as D, VISUALIZATION_TYPES as M, TYPE_HINTS as z, MUTATORS as U } from "./tokenizer.js";
2
2
  import { fuzzyMatch as v } from "./fuzzyMatcher.js";
3
3
  import { getSchemaCache as I } from "./schemaCache.js";
4
- import { ALL_FIELD_TYPES as u, BETWEEN_FIELD_TYPES as w, ORDERED_FIELD_TYPES as f } from "./validator.js";
4
+ import { ALL_FIELD_TYPES as c, BETWEEN_FIELD_TYPES as w, ORDERED_FIELD_TYPES as f } from "./validator.js";
5
5
  const F = [
6
6
  {
7
7
  label: "Simple equality",
@@ -105,24 +105,24 @@ const F = [
105
105
  description: "Top 10 results by count",
106
106
  category: "Stats"
107
107
  }
108
- ], G = {
108
+ ], q = {
109
109
  eq: { detail: "Equals", documentation: "Exact match comparison" },
110
110
  ne: { detail: "Not equals", documentation: "Match any value except this one" },
111
111
  gt: {
112
112
  detail: "Greater than",
113
- documentation: "Ordered comparison. Numbers and dates compare numerically; two STRINGS compare lexicographically. An array-valued field matches if ANY element does."
113
+ documentation: "Ordered comparison, on numeric, date and ip fields only: a keyword or text field is refused (tql#302). With no schema the value must be a number, a date or an ip. An array-valued field matches if ANY element does."
114
114
  },
115
115
  gte: {
116
116
  detail: "Greater than or equal",
117
- documentation: "Ordered comparison. Numbers and dates compare numerically; two STRINGS compare lexicographically. An array-valued field matches if ANY element does."
117
+ documentation: "Ordered comparison, on numeric, date and ip fields only: a keyword or text field is refused (tql#302). With no schema the value must be a number, a date or an ip. An array-valued field matches if ANY element does."
118
118
  },
119
119
  lt: {
120
120
  detail: "Less than",
121
- documentation: "Ordered comparison. Numbers and dates compare numerically; two STRINGS compare lexicographically. An array-valued field matches if ANY element does."
121
+ documentation: "Ordered comparison, on numeric, date and ip fields only: a keyword or text field is refused (tql#302). With no schema the value must be a number, a date or an ip. An array-valued field matches if ANY element does."
122
122
  },
123
123
  lte: {
124
124
  detail: "Less than or equal",
125
- documentation: "Ordered comparison. Numbers and dates compare numerically; two STRINGS compare lexicographically. An array-valued field matches if ANY element does."
125
+ documentation: "Ordered comparison, on numeric, date and ip fields only: a keyword or text field is refused (tql#302). With no schema the value must be a number, a date or an ip. An array-valued field matches if ANY element does."
126
126
  },
127
127
  contains: { detail: "Contains substring", documentation: "Case-insensitive substring match" },
128
128
  not_contains: { detail: "Does not contain", documentation: "Negative substring match" },
@@ -138,7 +138,7 @@ const F = [
138
138
  not_matches: { detail: "Not matching pattern", documentation: "Negative pattern match" },
139
139
  between: {
140
140
  detail: "Between range",
141
- documentation: "Inclusive range. Usage: field between A AND B, or field between [A, B]. Numeric bounds compare numerically; STRING bounds compare lexicographically, matching the range query this becomes on a keyword field."
141
+ documentation: "Inclusive range. Usage: field between A AND B, or field between [A, B]. Numeric, date and ip fields only: a keyword or text field is refused (tql#302), and with no schema both bounds must be numbers, dates or ips."
142
142
  },
143
143
  not_between: { detail: "Not between range", documentation: "Exclude values in range" },
144
144
  in: { detail: "In list", documentation: 'Usage: field in ["a", "b", "c"]' },
@@ -257,30 +257,27 @@ const F = [
257
257
  geo: "object",
258
258
  // Array output (returns array of strings)
259
259
  split: "keyword"
260
- }, q = {
260
+ }, G = {
261
261
  // Equality and membership — every field type, by NAME rather than by a transcribed list.
262
- eq: u,
263
- ne: u,
264
- in: u,
265
- not_in: u,
262
+ eq: c,
263
+ ne: c,
264
+ in: c,
265
+ not_in: c,
266
266
  // Existence. `is null` is the SAME PREDICATE as `not exists`, so these four lists must be
267
267
  // equal. They were not: `nested` and `object` were offered for `exists` and withheld from
268
268
  // `is`, so the completion menu published one of two spellings of one operation depending
269
269
  // on the field type. Pinned by `tests/collectionAndNullSemantics.spec.ts`.
270
- exists: u,
271
- not_exists: u,
272
- is: u,
273
- is_not: u,
274
- // Ordered comparison — numeric, date and keyword. See ORDERED_FIELD_TYPES in
275
- // `validator.ts`: both evaluators fall back to a lexicographic string comparison, and
276
- // Python's own validator lists all four in `keyword_ops`. Withholding them from a keyword
277
- // field's menu hid an operator every layer of the product supports.
270
+ exists: c,
271
+ not_exists: c,
272
+ is: c,
273
+ is_not: c,
274
+ // Ordered comparison — numeric, date and ip only (tql#302). See ORDERED_FIELD_TYPES in
275
+ // `validator.ts`: every engine refuses them on any other type, so offering one on a
276
+ // keyword field's menu would offer a query that cannot run.
278
277
  gt: f,
279
278
  gte: f,
280
279
  lt: f,
281
280
  lte: f,
282
- // Numeric and date. `keyword` and `text` stay out because Python's `keyword_ops` excludes
283
- // `between` while including `gte` — see BETWEEN_FIELD_TYPES in `validator.ts`.
284
281
  between: w,
285
282
  not_between: w,
286
283
  // String operators (text/keyword only)
@@ -312,12 +309,12 @@ const F = [
312
309
  // filter reads as "no restriction" — so the negated halves were offered on every type
313
310
  // while their positive twins were withheld. Permissive by omission is not the same as
314
311
  // permissive by decision, and only one of them survives someone reading the table.
315
- any: u,
316
- all: u,
317
- none: u,
318
- not_any: u,
319
- not_all: u,
320
- not_none: u
312
+ any: c,
313
+ all: c,
314
+ none: c,
315
+ not_any: c,
316
+ not_all: c,
317
+ not_none: c
321
318
  };
322
319
  class Y {
323
320
  cache = /* @__PURE__ */ new Map();
@@ -354,7 +351,7 @@ function H(e, t, i) {
354
351
  const n = T(e.substring(0, t)).filter((l) => l.type !== "whitespace");
355
352
  if (n.length === 0)
356
353
  return { type: "start" };
357
- const a = n[n.length - 1], s = n.length > 1 ? n[n.length - 2] : null, c = a.end === t && !e[t - 1]?.match(/\s/), d = c ? a.value : void 0;
354
+ const a = n[n.length - 1], s = n.length > 1 ? n[n.length - 2] : null, u = a.end === t && !e[t - 1]?.match(/\s/), d = u ? a.value : void 0;
358
355
  if (a.type === "logicalOp")
359
356
  return { type: "start" };
360
357
  if (a.type === "paren" && a.value === "(") {
@@ -362,7 +359,7 @@ function H(e, t, i) {
362
359
  if (r && r.type === "visualization")
363
360
  return { type: "in_viz_params" };
364
361
  }
365
- if (a.type === "comma" || a.type === "field" && c) {
362
+ if (a.type === "comma" || a.type === "field" && u) {
366
363
  const l = n.indexOf(a);
367
364
  for (let r = l - 1; r >= 0; r--) {
368
365
  if (n[r].type === "paren" && n[r].value === "(") {
@@ -377,13 +374,13 @@ function H(e, t, i) {
377
374
  return { type: "after_paren" };
378
375
  if (a.type === "arrow")
379
376
  return { type: "after_arrow", partialText: d };
380
- if (s?.type === "doubleColon" && c)
377
+ if (s?.type === "doubleColon" && u)
381
378
  return { type: "after_double_colon", partialText: d };
382
379
  if (a.type === "doubleColon")
383
380
  return { type: "after_double_colon" };
384
- if (a.type === "visualization" && c)
381
+ if (a.type === "visualization" && u)
385
382
  return { type: "after_arrow", partialText: d };
386
- if (a.type === "typeHint" && c)
383
+ if (a.type === "typeHint" && u)
387
384
  return { type: "after_double_colon", partialText: d };
388
385
  if (a.type === "keyword" && a.value.toLowerCase() === "stats")
389
386
  return { type: "after_stats", partialText: d };
@@ -397,7 +394,7 @@ function H(e, t, i) {
397
394
  if (n[r].type === "keyword" && n[r].value.toLowerCase() === "by" || n[r].type === "pipe") break;
398
395
  }
399
396
  }
400
- if (a.type === "field" && !c) {
397
+ if (a.type === "field" && !u) {
401
398
  const l = n.indexOf(a);
402
399
  for (let r = l - 1; r >= 0; r--) {
403
400
  if (n[r].type === "keyword" && n[r].value.toLowerCase() === "by") {
@@ -414,7 +411,7 @@ function H(e, t, i) {
414
411
  if (a.type === "pipe")
415
412
  return { type: "after_pipe", partialText: d };
416
413
  if (a.type === "mutator") {
417
- if (c)
414
+ if (u)
418
415
  return { type: "after_pipe", partialText: d };
419
416
  const l = a.value.toLowerCase(), r = g[l];
420
417
  return {
@@ -424,9 +421,9 @@ function H(e, t, i) {
424
421
  };
425
422
  }
426
423
  if (a.type === "aggregation")
427
- return c ? { type: "after_stats", partialText: d } : { type: "after_value" };
424
+ return u ? { type: "after_stats", partialText: d } : { type: "after_value" };
428
425
  if (a.type === "field") {
429
- if (c)
426
+ if (u)
430
427
  return s?.type === "pipe" ? { type: "after_pipe", partialText: d } : { type: "start", partialText: d };
431
428
  if (s?.type === "pipe")
432
429
  return { type: "after_pipe", partialText: a.value };
@@ -434,7 +431,7 @@ function H(e, t, i) {
434
431
  return { type: "after_field", fieldName: a.value, fieldType: l };
435
432
  }
436
433
  if (a.type === "operator") {
437
- if (c) {
434
+ if (u) {
438
435
  if (s?.type === "field") {
439
436
  const l = h(s.value, i);
440
437
  return { type: "after_field", fieldName: s.value, fieldType: l, partialText: d };
@@ -447,14 +444,14 @@ function H(e, t, i) {
447
444
  return { type: "after_operator", operatorName: a.value };
448
445
  }
449
446
  if (a.type === "string" || a.type === "number" || a.type === "boolean" || a.type === "null")
450
- return c ? { type: "at_value_end" } : { type: "after_value" };
447
+ return u ? { type: "at_value_end" } : { type: "after_value" };
451
448
  if (a.type === "paren" && a.value === ")")
452
449
  return { type: "after_value" };
453
- if (s?.type === "field" && c) {
450
+ if (s?.type === "field" && u) {
454
451
  const l = h(s.value, i);
455
452
  return { type: "after_field", fieldName: s.value, fieldType: l, partialText: d };
456
453
  }
457
- if (s?.type === "mutator" && c) {
454
+ if (s?.type === "mutator" && u) {
458
455
  const l = s.value.toLowerCase(), r = g[l];
459
456
  return { type: "after_mutator", mutatorName: l, mutatorOutputType: r, partialText: d };
460
457
  }
@@ -477,10 +474,10 @@ function le(e, t = {}) {
477
474
  a.push(...b(n.partialText, n.mutatorOutputType));
478
475
  break;
479
476
  case "after_operator":
480
- a.push(...W(n.fieldName, t));
477
+ a.push(...K(n.fieldName, t));
481
478
  break;
482
479
  case "after_value":
483
- a.push(...C());
480
+ a.push(...A());
484
481
  break;
485
482
  case "at_value_end":
486
483
  break;
@@ -540,20 +537,20 @@ function le(e, t = {}) {
540
537
  a.push(...ae(n.partialText));
541
538
  break;
542
539
  case "after_stats":
543
- a.push(...A(n.partialText));
540
+ a.push(...N(n.partialText));
544
541
  break;
545
542
  case "after_by":
546
543
  a.push(...x(t.fields, n.partialText)), a.push(...J(n.partialText));
547
544
  break;
548
545
  default:
549
- a.push(...x(t.fields, n.partialText)), a.push(...b(n.partialText)), a.push(...C());
546
+ a.push(...x(t.fields, n.partialText)), a.push(...b(n.partialText)), a.push(...A());
550
547
  }
551
- t.includeAggregations && n.type === "start" && a.push(...A(n.partialText));
548
+ t.includeAggregations && n.type === "start" && a.push(...N(n.partialText));
552
549
  let s = a;
553
- const c = t.fuzzyMatching !== !1, d = t.minFuzzyScore ?? 0;
550
+ const u = t.fuzzyMatching !== !1, d = t.minFuzzyScore ?? 0;
554
551
  if (n.partialText) {
555
552
  const l = n.partialText.toLowerCase();
556
- if (c) {
553
+ if (u) {
557
554
  const r = [];
558
555
  for (const m of a) {
559
556
  const p = v(l, m.label), _ = m.filterText ? v(l, m.filterText) : { matched: !1, score: 0 }, y = p.score >= _.score ? p : _;
@@ -576,10 +573,10 @@ function x(e, t) {
576
573
  return j(t);
577
574
  const i = [];
578
575
  for (const o of e) {
579
- const n = N(o);
576
+ const n = C(o);
580
577
  if (i.push(n), o.nested)
581
578
  for (const a of o.nested) {
582
- const s = N(a, o.name);
579
+ const s = C(a, o.name);
583
580
  i.push(s);
584
581
  }
585
582
  }
@@ -597,9 +594,6 @@ function j(e) {
597
594
  { name: "@timestamp", description: "Event timestamp" },
598
595
  { name: "message", description: "Message content" },
599
596
  { name: "user.name", description: "User name" },
600
- { name: "os.family", description: "Operating system family" },
601
- { name: "os.type", description: "Operating system type" },
602
- { name: "os.version", description: "Operating system version" },
603
597
  { name: "customer", description: "Customer identifier" },
604
598
  { name: "stack", description: "Stack identifier" }
605
599
  ].filter((i) => !e || i.name.toLowerCase().includes(e.toLowerCase())).map((i) => ({
@@ -611,7 +605,7 @@ function j(e) {
611
605
  sortOrder: 10
612
606
  }));
613
607
  }
614
- function N(e, t) {
608
+ function C(e, t) {
615
609
  const i = t ? `${t}.${e.name}` : e.name;
616
610
  return {
617
611
  label: i,
@@ -624,12 +618,12 @@ function N(e, t) {
624
618
  };
625
619
  }
626
620
  function b(e, t) {
627
- let i = [...R];
621
+ let i = [...$];
628
622
  return t && (i = i.filter((o) => {
629
- const n = q[o];
623
+ const n = G[o];
630
624
  return !n || n.includes(t);
631
625
  })), e && (i = i.filter((o) => o.toLowerCase().includes(e.toLowerCase()))), i.map((o, n) => {
632
- const a = G[o] || { detail: o, documentation: "" }, s = K(o, t);
626
+ const a = q[o] || { detail: o, documentation: "" }, s = W(o, t);
633
627
  return {
634
628
  label: o,
635
629
  insertText: s,
@@ -640,7 +634,7 @@ function b(e, t) {
640
634
  };
641
635
  });
642
636
  }
643
- function K(e, t) {
637
+ function W(e, t) {
644
638
  const i = [
645
639
  "contains",
646
640
  "not_contains",
@@ -683,6 +677,7 @@ function K(e, t) {
683
677
  return `${e} $1`;
684
678
  // Numbers, no quotes
685
679
  case "date":
680
+ case "date_nanos":
686
681
  return `${e} "$1"`;
687
682
  // Dates typically quoted
688
683
  case "ip":
@@ -695,8 +690,8 @@ function K(e, t) {
695
690
  function O(e) {
696
691
  return e === "long" || e === "integer" || e === "double" || e === "float" || e === "short" || e === "byte" || e === "half_float" || e === "scaled_float" || e === "unsigned_long";
697
692
  }
698
- function C() {
699
- return $.filter((e) => e !== "NOT").map(
693
+ function A() {
694
+ return R.filter((e) => e !== "NOT").map(
700
695
  (e, t) => L(e, t)
701
696
  );
702
697
  }
@@ -709,7 +704,7 @@ function L(e, t = 0) {
709
704
  sortOrder: 30 + t
710
705
  };
711
706
  }
712
- function W(e, t) {
707
+ function K(e, t) {
713
708
  const i = [];
714
709
  return e && t?.fieldValues?.[e] && t.fieldValues[e].forEach((n, a) => {
715
710
  i.push({
@@ -775,7 +770,7 @@ function X(e) {
775
770
  }
776
771
  );
777
772
  }
778
- function A(e) {
773
+ function N(e) {
779
774
  return D.filter(
780
775
  (t) => !e || t.toLowerCase().includes(e.toLowerCase())
781
776
  ).map((t, i) => ({
@@ -901,7 +896,7 @@ function se(e, t) {
901
896
  function de() {
902
897
  return [" ", "(", ".", '"', "'", "|", ":", "="];
903
898
  }
904
- function ce(e, t, i) {
899
+ function ue(e, t, i) {
905
900
  if (!e) return !1;
906
901
  if (i === " " || i === "(" || i === "." || i === "|" || i === ":" || i === "=") return !0;
907
902
  const o = T(e.substring(0, t)), n = o[o.length - 1];
@@ -912,5 +907,5 @@ export {
912
907
  de as getCompletionTriggerCharacters,
913
908
  le as getCompletions,
914
909
  se as getWordAtPosition,
915
- ce as shouldTriggerCompletion
910
+ ue as shouldTriggerCompletion
916
911
  };
@@ -6,10 +6,11 @@
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
- export type { TqlToken, TqlTokenType, TqlExpression, TqlBinaryExpression, TqlUnaryExpression, TqlComparisonExpression, TqlCollectionExpression, TqlMutatorExpression, TqlAggregationExpression, TqlStatsExpression, TqlGroupByBucket, TqlLatLon, TqlGroupByLevel, TqlGroupByMetadata, TqlRowBucketMeta, TqlStatsRow, TqlGroupExpression, TqlLiteral, TqlFieldReference, TqlComparisonOperator, TqlLogicalOperator, TqlCollectionOperator, TqlMutator, TqlAggregationFunction, TqlTypeHint, TqlVisualizationType, TqlVizParams, TqlVizHint, TqlParseResult, TqlParseError, TqlParseWarning, TqlErrorCode, TqlWarningCode, TqlValidationResult, TqlValidationOptions, TqlFieldType, CaseSensitiveSupport, TqlCompletionItem, TqlCompletionKind, TqlCompletionContext, TqlCompletionOptions, TqlFieldSchema, TqlSnippet, TqlInputProps, TqlInputEmits, TqlInputExpose } from './types';
9
+ export type { TqlToken, TqlTokenType, TqlExpression, TqlBinaryExpression, TqlUnaryExpression, TqlComparisonExpression, TqlCollectionExpression, TqlMutatorExpression, TqlAggregationExpression, TqlStatsExpression, TqlGroupByBucket, TqlLatLon, TqlGroupByLevel, TqlGroupByMetadata, TqlRowBucketMeta, TqlStatsRow, TqlGroupExpression, TqlLiteral, TqlFieldReference, TqlComparisonOperator, TqlLogicalOperator, TqlCollectionOperator, TqlMutator, TqlAggregationFunction, TqlTypeHint, TqlVisualizationType, TqlVizParams, TqlVizHint, TqlParseResult, TqlParseError, TqlOrderingRefusal, TqlParseWarning, TqlErrorCode, TqlWarningCode, TqlValidationResult, TqlValidationOptions, TqlFieldType, CaseSensitiveSupport, TqlCompletionItem, TqlCompletionKind, TqlCompletionContext, TqlCompletionOptions, TqlFieldSchema, TqlSnippet, TqlInputProps, TqlInputEmits, TqlInputExpose } from './types';
10
10
  export { TqlTokenizer, tokenizeTql, tokenizeTqlNoWhitespace, findTokenAtPosition, findTokensInRange, findTokenBefore, findPreviousToken, COMPARISON_OPERATORS, SYMBOL_OPERATORS, NEGATION_SYMBOL_OPERATORS, LOGICAL_OPERATORS, COLLECTION_OPERATORS, MUTATORS, BOOLEAN_PREDICATE_MUTATORS, BOOLEAN_PREDICATE_MUTATOR_SET, AGGREGATION_FUNCTIONS, GROUP_BY_BUCKET_FUNCTIONS, DATE_HISTOGRAM_CALENDAR_INTERVALS, DATE_HISTOGRAM_FIXED_UNITS, GEO_GRID_PRECISION, TYPE_HINTS, VISUALIZATION_TYPES, KEYWORDS } from './tokenizer';
11
11
  export { TqlParser, parseTql, isValidTql, getTqlErrors, formatParseError, dateHistogramIntervalProblem, geoPrecisionProblem, rangeBoundariesProblem } from './parser';
12
12
  export { TqlValidator, validateTql, isValidTqlSyntax, getTqlIssues, formatValidationResult, caseSensitiveSupport, casePreservingForm, CASE_SENSITIVE_OPERATORS, CASE_SENSITIVE_WHOLE_VALUE_OPERATORS, CASE_SENSITIVE_MEMBERSHIP_OPERATORS, MAX_REGEX_PATTERN_LENGTH } from './validator';
13
+ export { ORDERING_OPERATORS, ORDERABLE_FIELD_TYPES, isOrderingOperator, literalIsOrderable, orderingVerdict, orderingRefusalMessage, checkOrderingComparison } from './ordering';
13
14
  export { getCompletions, getWordAtPosition, getCompletionTriggerCharacters, shouldTriggerCompletion } from './completionProvider';
14
15
  export { fuzzyMatch, levenshteinDistance, findClosestMatch, fuzzyFilter, highlightMatch, type FuzzyMatchResult, type FuzzyMatchOptions } from './fuzzyMatcher';
15
16
  export { SchemaCache, getSchemaCache, createSchemaCache, getCachedFieldNames, getCachedFieldType, getCachedField, type CachedSchema } from './schemaCache';
@@ -0,0 +1,32 @@
1
+ import { TqlFieldType, TqlLiteral, TqlOrderingRefusal } from './types';
2
+ /** Every spelling of an ordering comparator the parser produces. */
3
+ export declare const ORDERING_OPERATORS: readonly string[];
4
+ /** The field types an ordering comparator is valid on, in the order the error lists them. */
5
+ export declare const ORDERABLE_FIELD_TYPES: readonly string[];
6
+ /** Type names a refusal reports as themselves; see `orderingVerdict`. */
7
+ export declare const REPORTED_TYPE_NAMES: ReadonlySet<string>;
8
+ export declare function isOrderingOperator(operator: string): boolean;
9
+ export type { TqlOrderingRefusal };
10
+ /** The refusal text. Byte-identical to Python's `TQLOrderingTypeError` and Rust's `OrderingTypeError`. */
11
+ export declare function orderingRefusalMessage(refusal: Omit<TqlOrderingRefusal, "allowedTypes">): string;
12
+ /** Is this literal a number, a date or an ip address? Judged on its text: quoting is not the rule. */
13
+ export declare function literalIsOrderable(literal: TqlLiteral): boolean;
14
+ /** `null` when nothing is known about the field's type; else its type and whether it is orderable. */
15
+ export type TqlFieldTypeVerdict = {
16
+ reported: string;
17
+ orderable: boolean;
18
+ } | null;
19
+ /**
20
+ * The verdict for `field` from a flat schema. A field is orderable when it, or a DIRECT subfield
21
+ * the schema lists (`f.num`), has an orderable type — the engines aim the clause at that subfield.
22
+ */
23
+ export declare function orderingVerdict(field: string, fieldTypes?: Record<string, TqlFieldType | TqlFieldType[]>): TqlFieldTypeVerdict;
24
+ /** Refuse one ordering comparison with no meaningful order, or return `null`. See the module doc. */
25
+ export declare function checkOrderingComparison(args: {
26
+ field: string;
27
+ operator: string;
28
+ value: TqlLiteral | TqlLiteral[];
29
+ verdict: TqlFieldTypeVerdict;
30
+ typeHint?: string;
31
+ hasFieldMutators: boolean;
32
+ }): TqlOrderingRefusal | null;
@@ -0,0 +1,141 @@
1
+ const h = [
2
+ "gt",
3
+ "gte",
4
+ "lt",
5
+ "lte",
6
+ ">",
7
+ ">=",
8
+ "<",
9
+ "<=",
10
+ "between",
11
+ "not_between",
12
+ "!between"
13
+ ], c = [
14
+ "long",
15
+ "integer",
16
+ "short",
17
+ "byte",
18
+ "double",
19
+ "float",
20
+ "half_float",
21
+ "scaled_float",
22
+ "unsigned_long",
23
+ "date",
24
+ "date_nanos",
25
+ "ip"
26
+ ], w = /* @__PURE__ */ new Set([
27
+ "keyword",
28
+ "text",
29
+ "long",
30
+ "double",
31
+ "boolean",
32
+ "date",
33
+ "ip",
34
+ "wildcard",
35
+ "object",
36
+ "nested",
37
+ "geo_point",
38
+ "unsigned_long",
39
+ "match_only_text",
40
+ "annotated_text",
41
+ "unknown"
42
+ ]), y = /* @__PURE__ */ new Set(["integer", "int", "number", "decimal", "float", "double", "date", "ip"]), _ = /* @__PURE__ */ new Set(["string", "str", "boolean", "bool", "geo", "object"]);
43
+ function E(e) {
44
+ return h.includes(e);
45
+ }
46
+ function $(e) {
47
+ const t = e.operator === "!between" ? "not_between" : e.operator, s = e.literal === void 0 ? `field '${e.field}' of type '${e.fieldType}'` : `field '${e.field}' of unknown type with the literal '${e.literal}'`;
48
+ let r = `Cannot apply ordering operator '${t}' to ${s}. Ordering operators (gt, gte, lt, lte, between) are valid only on numeric, date and ip fields (allowed types: ${c.join(", ")}).`;
49
+ return e.literal !== void 0 && (r += " With no field type known, the literal must be a number, a date or an ip address."), r;
50
+ }
51
+ const O = /^[+-]?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?$/, f = /^[0-9]{4}-[0-9]{2}-[0-9]{2}([T ][0-9]{2}:[0-9]{2}(:[0-9]{2}([.,][0-9]+)?)?(Z|[+-][0-9]{2}(:?[0-9]{2})?)?)?$/, p = /^([+-][0-9]+[yMwdhHms]|\/[yMwdhHms])*$/, g = /^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}$/, v = /^[0-9A-Fa-f]{1,4}$/;
52
+ function A(e) {
53
+ if (!e.includes(":") || !/^[0-9A-Fa-f:.]+$/.test(e))
54
+ return !1;
55
+ const t = e.split("::");
56
+ if (t.length > 2)
57
+ return !1;
58
+ const s = (n) => n === "" ? [] : n.split(":"), r = s(t[0]), i = t.length === 2 ? s(t[1]) : [];
59
+ if (r === null || i === null)
60
+ return !1;
61
+ const a = [...r, ...i];
62
+ let u = 0;
63
+ for (let n = 0; n < a.length; n++) {
64
+ const l = a[n];
65
+ if (n === a.length - 1 && !e.endsWith("::") && l.includes(".")) {
66
+ if (!g.test(l)) return !1;
67
+ u += 2;
68
+ } else if (v.test(l))
69
+ u += 1;
70
+ else
71
+ return !1;
72
+ }
73
+ return t.length === 2 ? u <= 7 : u === 8;
74
+ }
75
+ function m(e) {
76
+ if (e.startsWith("now"))
77
+ return p.test(e.slice(3));
78
+ const t = e.indexOf("||");
79
+ return t === -1 ? f.test(e) : f.test(e.slice(0, t)) && p.test(e.slice(t + 2));
80
+ }
81
+ function R(e) {
82
+ if (e.valueType === "number") return !0;
83
+ if (e.valueType !== "string" || typeof e.value != "string") return !1;
84
+ const t = e.value;
85
+ return O.test(t) || m(t) || g.test(t) || A(t);
86
+ }
87
+ function T(e) {
88
+ return e.value === null ? "null" : String(e.value);
89
+ }
90
+ function S(e, t) {
91
+ const s = t?.[e];
92
+ if (s === void 0) return null;
93
+ const r = Array.isArray(s) ? s : [s];
94
+ if (r.length === 0) return null;
95
+ let i = r.some((n) => c.includes(n));
96
+ const a = r.every((n) => n === "object" || n === "nested");
97
+ if (!i && !a && t) {
98
+ const n = `${e}.`;
99
+ for (const [l, o] of Object.entries(t)) {
100
+ if (!l.startsWith(n) || l.slice(n.length).includes(".")) continue;
101
+ if ((Array.isArray(o) ? o : [o]).some((b) => c.includes(b))) {
102
+ i = !0;
103
+ break;
104
+ }
105
+ }
106
+ }
107
+ return { reported: r.map((n) => i || w.has(n) ? n : "unknown").join(" or "), orderable: i };
108
+ }
109
+ function D(e) {
110
+ const { field: t, operator: s, value: r, verdict: i, typeHint: a, hasFieldMutators: u } = e;
111
+ if (!E(s)) return null;
112
+ const n = (o, d) => ({
113
+ field: t,
114
+ fieldType: o,
115
+ operator: s === "!between" ? "not_between" : s,
116
+ ...d === void 0 ? {} : { literal: d },
117
+ allowedTypes: [...c]
118
+ });
119
+ if (!u) {
120
+ if (i !== null)
121
+ return i.orderable ? null : n(i.reported);
122
+ const o = (a ?? "").toLowerCase();
123
+ if (y.has(o)) return null;
124
+ if (_.has(o)) return n(o);
125
+ }
126
+ const l = Array.isArray(r) ? r : [r];
127
+ for (const o of l)
128
+ if (o.type === "literal" && !R(o))
129
+ return n("unknown", T(o));
130
+ return null;
131
+ }
132
+ export {
133
+ c as ORDERABLE_FIELD_TYPES,
134
+ h as ORDERING_OPERATORS,
135
+ w as REPORTED_TYPE_NAMES,
136
+ D as checkOrderingComparison,
137
+ E as isOrderingOperator,
138
+ R as literalIsOrderable,
139
+ $ as orderingRefusalMessage,
140
+ S as orderingVerdict
141
+ };
@@ -293,6 +293,18 @@ export interface TqlParseError {
293
293
  severity: "error";
294
294
  code: TqlErrorCode;
295
295
  suggestion?: string;
296
+ /** Set on `UNORDERABLE_FIELD`: the field, its type, the operator and the allowed types. */
297
+ ordering?: TqlOrderingRefusal;
298
+ }
299
+ /** An ordering comparator refused on a field with no meaningful order (tql#302). */
300
+ export interface TqlOrderingRefusal {
301
+ field: string;
302
+ /** Mapped type, the type hint, or `"unknown"` when the literal was refused. */
303
+ fieldType: string;
304
+ operator: string;
305
+ /** Set when no field type was known and the LITERAL was refused. */
306
+ literal?: string;
307
+ allowedTypes: string[];
296
308
  }
297
309
  export interface TqlParseWarning {
298
310
  message: string;
@@ -335,7 +347,15 @@ export type TqlErrorCode = "UNEXPECTED_TOKEN" | "UNEXPECTED_END" | "UNBALANCED_P
335
347
  * malformed `range` boundaries, a duplicate `filters` label. Mirrors
336
348
  * `cross_language_tests/fixtures/test_cases/validation/stats_bucket_errors.json`.
337
349
  */
338
- | "INVALID_STATS_BUCKET";
350
+ | "INVALID_STATS_BUCKET"
351
+ /**
352
+ * An ordering comparator (`gt`/`gte`/`lt`/`lte`, `between`) on a field with no meaningful
353
+ * order (tql#302): a mapped type that is not numeric, date or ip, or — with no type known —
354
+ * a literal that is not a number, a date or an ip address. Both engines refuse the same
355
+ * query with the same message (`TQLOrderingTypeError` / `OrderingTypeError`). The error's
356
+ * `ordering` field carries the details.
357
+ */
358
+ | "UNORDERABLE_FIELD";
339
359
  export type TqlWarningCode = "UNKNOWN_FIELD" | "TYPE_MISMATCH" | "DEPRECATED_OPERATOR" | "REDUNDANT_PARENS"
340
360
  /**
341
361
  * The query parses, but only ONE of the two engines accepts it.
@@ -412,7 +432,7 @@ export interface TqlValidationOptions {
412
432
  * documents the predicate and its two blind spots.
413
433
  */
414
434
  export type CaseSensitiveSupport = "supported" | "unsupported" | "unknown";
415
- export type TqlFieldType = "text" | "keyword" | "long" | "integer" | "short" | "byte" | "double" | "float" | "half_float" | "scaled_float" | "unsigned_long" | "date" | "boolean" | "ip" | "nested" | "object" | "geo_point";
435
+ export type TqlFieldType = "text" | "keyword" | "long" | "integer" | "short" | "byte" | "double" | "float" | "half_float" | "scaled_float" | "unsigned_long" | "date" | "date_nanos" | "boolean" | "ip" | "nested" | "object" | "geo_point";
416
436
  export interface TqlCompletionItem {
417
437
  label: string;
418
438
  insertText: string;
@@ -29,22 +29,18 @@ export declare const ALL_FIELD_TYPES: TqlFieldType[];
29
29
  */
30
30
  export declare const NUMERIC_FIELD_TYPES: TqlFieldType[];
31
31
  /**
32
- * The types an ORDERED comparison (`gt`/`gte`/`lt`/`lte`) is defined over.
32
+ * The types an ORDERED comparison (`gt`/`gte`/`lt`/`lte`) is defined over: numeric, date and
33
+ * ip — exactly `ORDERABLE_FIELD_TYPES` in `./ordering` (`date_nanos` joined this union in
34
+ * tql#302, so a `date_nanos` field's completion menu offers them).
33
35
  *
34
- * Numeric and date, plus `keyword`: both evaluators fall back to a lexicographic string
35
- * comparison when neither side reads as a number, and Python's own validator lists all four
36
- * operators in its `keyword_ops`. `text` is excluded on that same source's reasoning — a
37
- * range over an analyzed field compares tokens, not the value the user typed.
36
+ * `keyword` used to be here, because both evaluators fell back to a lexicographic string
37
+ * comparison. That fallback is what made `os.version gte "10"` match Windows 7, and every
38
+ * engine now refuses it (tql#302); `validate()` reports it as `UNORDERABLE_FIELD`.
38
39
  */
39
40
  export declare const ORDERED_FIELD_TYPES: TqlFieldType[];
40
41
  /**
41
- * The types `between` / `not_between` are defined over.
42
- *
43
- * `ORDERED_FIELD_TYPES` minus `keyword`. The four ordered operators and `between` are NOT
44
- * the same set, and the difference is Python's, not this package's: `keyword_ops` in
45
- * `_validate_simple_type_compatibility` lists `gt`/`gte`/`lt`/`lte` and excludes `between`.
46
- * `tests/collectionAndNullSemantics.spec.ts` pins that exclusion against the Python source,
47
- * so this constant self-retires if Python ever adds it.
42
+ * The types `between` / `not_between` are defined over: the same set as the four ordered
43
+ * operators since tql#302 (they differed only by `keyword`, which neither allows now).
48
44
  */
49
45
  export declare const BETWEEN_FIELD_TYPES: TqlFieldType[];
50
46
  /**
@@ -220,6 +216,12 @@ export declare class TqlValidator {
220
216
  /**
221
217
  * Suggest a similar field name using Levenshtein distance
222
218
  */
219
+ /**
220
+ * Report an ordering comparator with no meaningful order as `UNORDERABLE_FIELD`. Returns
221
+ * whether it did. A field with mutators (`f | length gt 5`) is judged by its literal, as the
222
+ * engines judge it.
223
+ */
224
+ private validateOrdering;
223
225
  private suggestField;
224
226
  /**
225
227
  * Calculate Levenshtein distance between two strings
@@ -1,5 +1,6 @@
1
- import { parseTql as p } from "./parser.js";
2
- const g = 1e3, r = [
1
+ import { isOrderingOperator as h, checkOrderingComparison as b, orderingVerdict as m, orderingRefusalMessage as v } from "./ordering.js";
2
+ import { parseTql as E } from "./parser.js";
3
+ const _ = 1e3, r = [
3
4
  "text",
4
5
  "keyword",
5
6
  "long",
@@ -12,12 +13,13 @@ const g = 1e3, r = [
12
13
  "scaled_float",
13
14
  "unsigned_long",
14
15
  "date",
16
+ "date_nanos",
15
17
  "boolean",
16
18
  "ip",
17
19
  "nested",
18
20
  "object",
19
21
  "geo_point"
20
- ], _ = [
22
+ ], k = [
21
23
  "long",
22
24
  "integer",
23
25
  "short",
@@ -27,7 +29,7 @@ const g = 1e3, r = [
27
29
  "half_float",
28
30
  "scaled_float",
29
31
  "unsigned_long"
30
- ], l = [..._, "date", "keyword"], u = [..._, "date"], b = {
32
+ ], d = [...k, "date", "date_nanos", "ip"], g = d, T = {
31
33
  // Equality (all types) — and now that is what it says.
32
34
  //
33
35
  // The transcribed twelve-element list this replaces claimed "all types" while omitting
@@ -39,29 +41,21 @@ const g = 1e3, r = [
39
41
  "=": r,
40
42
  "!=": r,
41
43
  eq_ci: ["text", "keyword"],
42
- // Ordered comparison — numeric, date, AND keyword.
44
+ // Ordered comparison — numeric, date and ip (tql#302). These rows no longer produce the
45
+ // soft warning: `validateOrdering` reports every ordering comparator on another type as the
46
+ // `UNORDERABLE_FIELD` ERROR the engines' refusal deserves. They stay here because the
47
+ // completion menu is checked against this table (completionOperatorTableParity.spec.ts).
43
48
  //
44
- // `keyword` is not a courtesy. `compare_ordered` in `tql/src/comparator.rs` tries numbers
45
- // first and falls back to a LEXICOGRAPHIC string comparison when both sides are strings,
46
- // Python does the same, and Python's own validator lists all four in `keyword_ops`
47
- // (`_validate_simple_type_compatibility`). Warning here was a false positive against every
48
- // layer at once — `host.name gte "m"` is a query the editor flagged, the validator
49
- // accepts, the evaluator answers and the cluster serves.
50
- //
51
- // `text` stays out, and that is the same source's judgement rather than an omission:
52
- // `text_ops` excludes the four, because a range over an ANALYZED field compares tokens
53
- // rather than the value the user typed. `between` also stays out — see the note on it.
54
- //
55
- // Every numeric width is listed via ALL_NUMERIC_FIELD_TYPES so the five narrow ones cannot
49
+ // Every numeric width is listed via NUMERIC_FIELD_TYPES so the five narrow ones cannot
56
50
  // fall out again the way they did from `eq`.
57
- gt: l,
58
- gte: l,
59
- lt: l,
60
- lte: l,
61
- ">": l,
62
- ">=": l,
63
- "<": l,
64
- "<=": l,
51
+ gt: d,
52
+ gte: d,
53
+ lt: d,
54
+ lte: d,
55
+ ">": d,
56
+ ">=": d,
57
+ "<": d,
58
+ "<=": d,
65
59
  // String operations
66
60
  //
67
61
  // ## The `_cs` operators list `text`, and that is deliberate — the rule lives in the caller
@@ -147,19 +141,12 @@ const g = 1e3, r = [
147
141
  matches: ["text", "keyword"],
148
142
  not_matches: ["text", "keyword"],
149
143
  "!matches": ["text", "keyword"],
150
- // Range. Numeric and date, via the named constant — the transcribed list this replaces
151
- // dropped the same five narrow widths `eq` did (`short`, `byte`, `half_float`,
152
- // `scaled_float`, `unsigned_long`), so `port between [1, 1024]` on a `short` field warned.
153
- // Python's `numeric_ops` carries `between` and covers `short` and `byte`; the other three
154
- // widths have no arm at all in `_validate_simple_type_compatibility` and fall through its
155
- // `else: return`, which allows every operator.
156
- //
157
- // `keyword` and `text` stay out, and that is a decision rather than an omission: Python's
158
- // `keyword_ops` excludes `between` while including `gte`, which is the divergence pinned
159
- // in `tests/collectionAndNullSemantics.spec.ts`.
160
- between: u,
161
- not_between: u,
162
- "!between": u,
144
+ // Range. Numeric, date and ip, via the named constant — the same set as the four ordered
145
+ // operators since tql#302; on any other type `validateOrdering` reports the engines'
146
+ // refusal as an ERROR, and these rows never warn.
147
+ between: g,
148
+ not_between: g,
149
+ "!between": g,
163
150
  // Membership — the same set as `eq`, because `in` IS a disjunction of equalities and both
164
151
  // evaluators implement it that way. The transcribed list this replaces omitted the five
165
152
  // narrow numeric widths plus `nested`, `object` and `geo_point`.
@@ -226,11 +213,11 @@ const g = 1e3, r = [
226
213
  "!any": r,
227
214
  "!all": r,
228
215
  "!none": r
229
- }, h = {
216
+ }, f = {
230
217
  cidr: ["ip"],
231
218
  not_cidr: ["ip"],
232
219
  "!cidr": ["ip"]
233
- }, m = [
220
+ }, A = [
234
221
  "contains_cs",
235
222
  "not_contains_cs",
236
223
  "!contains_cs",
@@ -240,25 +227,25 @@ const g = 1e3, r = [
240
227
  "endswith_cs",
241
228
  "not_endswith_cs",
242
229
  "!endswith_cs"
243
- ], f = [
230
+ ], w = [
244
231
  "in_cs",
245
232
  "not_in_cs",
246
233
  "!in_cs"
247
- ], v = [
248
- ...m,
249
- ...f
234
+ ], $ = [
235
+ ...A,
236
+ ...w
250
237
  ];
251
- function c(o, t) {
238
+ function u(o, t) {
252
239
  const e = t?.[o];
253
240
  return e === void 0 ? [] : Array.isArray(e) ? e : [e];
254
241
  }
255
- function E(o, t) {
242
+ function R(o, t) {
256
243
  if (!t)
257
244
  return "unknown";
258
- const e = c(o, t);
245
+ const e = u(o, t);
259
246
  if (e.length === 0)
260
247
  return "unknown";
261
- if (e.includes("keyword") || c(`${o}.keyword`, t).includes("keyword"))
248
+ if (e.includes("keyword") || u(`${o}.keyword`, t).includes("keyword"))
262
249
  return "present";
263
250
  const i = `${o}.`;
264
251
  for (const [n, s] of Object.entries(t))
@@ -266,18 +253,18 @@ function E(o, t) {
266
253
  return "present";
267
254
  return "absent";
268
255
  }
269
- function k(o, t, e) {
270
- if (!v.includes(t))
256
+ function I(o, t, e) {
257
+ if (!$.includes(t))
271
258
  return "supported";
272
- const i = E(o, e);
259
+ const i = R(o, e);
273
260
  if (i !== "absent")
274
261
  return i === "present" ? "supported" : "unknown";
275
- if (!f.includes(t))
262
+ if (!w.includes(t))
276
263
  return "unsupported";
277
- const n = c(o, e);
264
+ const n = u(o, e);
278
265
  return n.length > 0 && n.every((s) => s === "text") ? "unsupported" : "supported";
279
266
  }
280
- const T = {
267
+ const S = {
281
268
  eq: ["string", "number", "boolean"],
282
269
  ne: ["string", "number", "boolean"],
283
270
  "=": ["string", "number", "boolean"],
@@ -346,7 +333,7 @@ const T = {
346
333
  "!all": ["string", "number", "boolean"],
347
334
  "!none": ["string", "number", "boolean"]
348
335
  };
349
- class A {
336
+ class O {
350
337
  options;
351
338
  errors;
352
339
  warnings;
@@ -359,7 +346,7 @@ class A {
359
346
  validate(t) {
360
347
  if (this.errors = [], this.warnings = [], !t)
361
348
  return { valid: !0, errors: [], warnings: [] };
362
- const e = p(t);
349
+ const e = E(t);
363
350
  return this.errors.push(...e.errors), this.warnings.push(...e.warnings), e.ast && this.validateExpression(e.ast), {
364
351
  valid: this.errors.length === 0,
365
352
  errors: this.errors,
@@ -477,7 +464,7 @@ class A {
477
464
  */
478
465
  validateComparison(t) {
479
466
  const e = t.field.type === "field" ? t.field.name : "computed";
480
- if (this.options.availableFields && this.options.strictMode && !this.options.availableFields.includes(e) && t.field.type === "field" && this.addWarning(
467
+ this.options.availableFields && this.options.strictMode && !this.options.availableFields.includes(e) && t.field.type === "field" && this.addWarning(
481
468
  `Unknown field '${e}'`,
482
469
  t.field.start,
483
470
  t.field.end,
@@ -485,25 +472,27 @@ class A {
485
472
  1,
486
473
  "UNKNOWN_FIELD",
487
474
  `Did you mean: ${this.suggestField(e)}`
488
- ), this.options.fieldTypes && t.field.type === "field") {
489
- const n = c(e, this.options.fieldTypes), s = n.join(" or ");
490
- if (n.length > 0) {
491
- const a = h[t.operator];
492
- a && !n.some((d) => a.includes(d)) && this.addError(
493
- `Operator '${t.operator}' requires field type ${a.join(" or ")}, but '${e}' is type '${s}'`,
475
+ );
476
+ const i = this.validateOrdering(t);
477
+ if (this.options.fieldTypes && t.field.type === "field") {
478
+ const s = u(e, this.options.fieldTypes), a = s.join(" or ");
479
+ if (s.length > 0) {
480
+ const l = f[t.operator];
481
+ l && !s.some((c) => l.includes(c)) && this.addError(
482
+ `Operator '${t.operator}' requires field type ${l.join(" or ")}, but '${e}' is type '${a}'`,
494
483
  t.start,
495
484
  t.end,
496
485
  1,
497
486
  1,
498
487
  "TYPE_MISMATCH",
499
- `Use '${t.operator}' only with ${a.join(" or ")} fields`
488
+ `Use '${t.operator}' only with ${l.join(" or ")} fields`
500
489
  );
501
490
  }
502
491
  }
503
- let i = !1;
504
- if (t.field.type === "field" && k(e, t.operator, this.options.fieldTypes) === "unsupported") {
505
- i = !0;
506
- const n = t.operator.replace(/_cs$/, ""), s = this.options.fieldTypes?.[e], a = s === "text" ? `'${e}' is an analyzed text field with no case-preserving subfield in the supplied schema, so the analyzer lowercased every token it indexed and the case '${t.operator}' asks about is not in the index` : `'${e}' is type '${s}' and has no un-analyzed string form, which is what '${t.operator}' matches against`;
492
+ let n = !1;
493
+ if (t.field.type === "field" && I(e, t.operator, this.options.fieldTypes) === "unsupported") {
494
+ n = !0;
495
+ const s = t.operator.replace(/_cs$/, ""), a = this.options.fieldTypes?.[e], l = a === "text" ? `'${e}' is an analyzed text field with no case-preserving subfield in the supplied schema, so the analyzer lowercased every token it indexed and the case '${t.operator}' asks about is not in the index` : `'${e}' is type '${a}' and has no un-analyzed string form, which is what '${t.operator}' matches against`;
507
496
  this.addWarning(
508
497
  // "The query engine", singular and unqualified, and kept that way deliberately.
509
498
  // It was written this way because "both engines" was FALSE in twelve cells —
@@ -512,34 +501,34 @@ class A {
512
501
  // user does not choose the engine, the sentence is about what will go wrong
513
502
  // rather than about who refuses, and a message that counts engines is a message
514
503
  // that goes stale the next time one of them changes.
515
- `${a}. The query engine refuses this at execution.`,
504
+ `${l}. The query engine refuses this at execution.`,
516
505
  t.start,
517
506
  t.end,
518
507
  1,
519
508
  1,
520
509
  "UNANSWERABLE_MAPPING",
521
- s === "text" ? `Use the case-insensitive '${n}', or query a keyword subfield (\`${e}.keyword\`) if the index has one this schema does not list.` : `Use the case-insensitive '${n}', or compare '${e}' with an operator suited to '${s}'.`
510
+ a === "text" ? `Use the case-insensitive '${s}', or query a keyword subfield (\`${e}.keyword\`) if the index has one this schema does not list.` : `Use the case-insensitive '${s}', or compare '${e}' with an operator suited to '${a}'.`
522
511
  );
523
512
  }
524
513
  if (this.options.fieldTypes && t.field.type === "field") {
525
- const n = c(e, this.options.fieldTypes), s = n.join(" or ");
526
- if (n.length > 0) {
527
- const a = b[t.operator], d = h[t.operator];
528
- !i && !d && a && !n.some((y) => a.includes(y)) && this.addWarning(
529
- `Operator '${t.operator}' may not work well with field type '${s}'`,
514
+ const s = u(e, this.options.fieldTypes), a = s.join(" or ");
515
+ if (s.length > 0) {
516
+ const l = T[t.operator], c = f[t.operator];
517
+ !n && !i && !h(t.operator) && !c && l && !s.some((p) => l.includes(p)) && this.addWarning(
518
+ `Operator '${t.operator}' may not work well with field type '${a}'`,
530
519
  t.start,
531
520
  t.end,
532
521
  1,
533
522
  1,
534
523
  "TYPE_MISMATCH",
535
- `Consider using a different operator for ${s} fields`
524
+ `Consider using a different operator for ${a} fields`
536
525
  );
537
526
  }
538
527
  }
539
528
  if (!Array.isArray(t.value) && t.value.type === "literal") {
540
- const n = T[t.operator];
541
- n && !n.includes(t.value.valueType) && this.addWarning(
542
- `Operator '${t.operator}' expects ${n.join(" or ")} value`,
529
+ const s = S[t.operator];
530
+ s && !s.includes(t.value.valueType) && this.addWarning(
531
+ `Operator '${t.operator}' expects ${s.join(" or ")} value`,
543
532
  t.value.start,
544
533
  t.value.end,
545
534
  1,
@@ -548,10 +537,10 @@ class A {
548
537
  );
549
538
  }
550
539
  if ((t.operator === "regexp" || t.operator === "regex" || t.operator === "not_regexp" || t.operator === "not_regex" || t.operator === "matches" || t.operator === "not_matches") && !Array.isArray(t.value) && t.value.valueType === "string" && typeof t.value.value == "string") {
551
- const n = Array.from(t.value.value).length;
552
- if (n > g)
540
+ const s = Array.from(t.value.value).length;
541
+ if (s > _)
553
542
  this.addError(
554
- `Regex pattern is ${n} characters, which exceeds the maximum supported length of ${g}. The query cannot run as written.`,
543
+ `Regex pattern is ${s} characters, which exceeds the maximum supported length of ${_}. The query cannot run as written.`,
555
544
  t.value.start,
556
545
  t.value.end,
557
546
  1,
@@ -578,6 +567,36 @@ class A {
578
567
  /**
579
568
  * Suggest a similar field name using Levenshtein distance
580
569
  */
570
+ /**
571
+ * Report an ordering comparator with no meaningful order as `UNORDERABLE_FIELD`. Returns
572
+ * whether it did. A field with mutators (`f | length gt 5`) is judged by its literal, as the
573
+ * engines judge it.
574
+ */
575
+ validateOrdering(t) {
576
+ if (!h(t.operator)) return !1;
577
+ let e = t.field, i = !1;
578
+ for (; e.type === "mutator"; )
579
+ i = !0, e = e.field;
580
+ ((e.mutators?.length ?? 0) > 0 || (e.mutatorCalls?.length ?? 0) > 0) && (i = !0);
581
+ const n = b({
582
+ field: e.name,
583
+ operator: t.operator,
584
+ value: t.value,
585
+ verdict: m(e.name, this.options.fieldTypes),
586
+ typeHint: e.typeHint,
587
+ hasFieldMutators: i
588
+ });
589
+ return n === null ? !1 : (this.addError(
590
+ v(n),
591
+ t.start,
592
+ t.end,
593
+ 1,
594
+ 1,
595
+ "UNORDERABLE_FIELD",
596
+ n.literal === void 0 ? `Compare '${n.field}' with eq/ne/in, or order on a numeric, date or ip field (a version has numeric parts such as os.version_build).` : "Use a number, a date or an ip address, or compare with eq/ne/in.",
597
+ n
598
+ ), !0);
599
+ }
581
600
  suggestField(t) {
582
601
  if (!this.options.availableFields || this.options.availableFields.length === 0)
583
602
  return "check available fields";
@@ -606,7 +625,7 @@ class A {
606
625
  );
607
626
  return i[e.length][t.length];
608
627
  }
609
- addError(t, e, i, n, s, a, d) {
628
+ addError(t, e, i, n, s, a, l, c) {
610
629
  this.errors.push({
611
630
  message: t,
612
631
  start: e,
@@ -615,10 +634,11 @@ class A {
615
634
  column: s,
616
635
  severity: "error",
617
636
  code: a,
618
- suggestion: d
637
+ suggestion: l,
638
+ ...c ? { ordering: c } : {}
619
639
  });
620
640
  }
621
- addWarning(t, e, i, n, s, a, d) {
641
+ addWarning(t, e, i, n, s, a, l) {
622
642
  this.warnings.push({
623
643
  message: t,
624
644
  start: e,
@@ -627,21 +647,21 @@ class A {
627
647
  column: s,
628
648
  severity: "warning",
629
649
  code: a,
630
- suggestion: d
650
+ suggestion: l
631
651
  });
632
652
  }
633
653
  }
634
- function w(o, t) {
635
- return new A(t).validate(o);
654
+ function y(o, t) {
655
+ return new O(t).validate(o);
636
656
  }
637
- function I(o) {
638
- return w(o).valid;
657
+ function x(o) {
658
+ return y(o).valid;
639
659
  }
640
- function S(o, t) {
641
- const e = w(o, t);
660
+ function C(o, t) {
661
+ const e = y(o, t);
642
662
  return [...e.errors, ...e.warnings];
643
663
  }
644
- function R(o) {
664
+ function M(o) {
645
665
  const t = [];
646
666
  o.valid ? t.push("Query is valid") : t.push("Query has errors:");
647
667
  for (const e of o.errors)
@@ -653,18 +673,18 @@ function R(o) {
653
673
  }
654
674
  export {
655
675
  r as ALL_FIELD_TYPES,
656
- u as BETWEEN_FIELD_TYPES,
657
- f as CASE_SENSITIVE_MEMBERSHIP_OPERATORS,
658
- v as CASE_SENSITIVE_OPERATORS,
659
- m as CASE_SENSITIVE_WHOLE_VALUE_OPERATORS,
660
- g as MAX_REGEX_PATTERN_LENGTH,
661
- _ as NUMERIC_FIELD_TYPES,
662
- l as ORDERED_FIELD_TYPES,
663
- A as TqlValidator,
664
- E as casePreservingForm,
665
- k as caseSensitiveSupport,
666
- R as formatValidationResult,
667
- S as getTqlIssues,
668
- I as isValidTqlSyntax,
669
- w as validateTql
676
+ g as BETWEEN_FIELD_TYPES,
677
+ w as CASE_SENSITIVE_MEMBERSHIP_OPERATORS,
678
+ $ as CASE_SENSITIVE_OPERATORS,
679
+ A as CASE_SENSITIVE_WHOLE_VALUE_OPERATORS,
680
+ _ as MAX_REGEX_PATTERN_LENGTH,
681
+ k as NUMERIC_FIELD_TYPES,
682
+ d as ORDERED_FIELD_TYPES,
683
+ O as TqlValidator,
684
+ R as casePreservingForm,
685
+ I as caseSensitiveSupport,
686
+ M as formatValidationResult,
687
+ C as getTqlIssues,
688
+ x as isValidTqlSyntax,
689
+ y as validateTql
670
690
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellaro/tql",
3
- "version": "4.0.2",
3
+ "version": "5.0.0",
4
4
  "description": "Tellaro Query Language (TQL) parser, validator, and autocomplete for JavaScript/TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",