@tellaro/tql 3.0.1 → 3.0.3
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/src/types.d.ts +10 -1
- package/dist/src/validator.d.ts +2 -35
- package/dist/src/validator.js +70 -61
- package/package.json +1 -1
package/dist/src/types.d.ts
CHANGED
|
@@ -276,7 +276,16 @@ export interface TqlValidationResult {
|
|
|
276
276
|
}
|
|
277
277
|
export interface TqlValidationOptions {
|
|
278
278
|
availableFields?: string[];
|
|
279
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Declared type per field name.
|
|
281
|
+
*
|
|
282
|
+
* An ARRAY means the field is mapped more than one way across the index pattern the
|
|
283
|
+
* schema describes -- `text` in one index and `keyword` in another is the ordinary way
|
|
284
|
+
* to get there, and the engines answer such a field with a disjunction over both forms
|
|
285
|
+
* (see `TypeDivergence` in the Rust engine). A single type is the common case and
|
|
286
|
+
* behaves exactly as before.
|
|
287
|
+
*/
|
|
288
|
+
fieldTypes?: Record<string, TqlFieldType | TqlFieldType[]>;
|
|
280
289
|
strictMode?: boolean;
|
|
281
290
|
allowAggregations?: boolean;
|
|
282
291
|
allowMutators?: boolean;
|
package/dist/src/validator.d.ts
CHANGED
|
@@ -82,40 +82,7 @@ export declare const CASE_SENSITIVE_WHOLE_VALUE_OPERATORS: TqlComparisonOperator
|
|
|
82
82
|
export declare const CASE_SENSITIVE_MEMBERSHIP_OPERATORS: TqlComparisonOperator[];
|
|
83
83
|
/** Every spelling of every case-SENSITIVE operator — the union of the two families above. */
|
|
84
84
|
export declare const CASE_SENSITIVE_OPERATORS: TqlComparisonOperator[];
|
|
85
|
-
|
|
86
|
-
* Whether a field offers a CASE-PRESERVING form, as far as the supplied schema shows.
|
|
87
|
-
*
|
|
88
|
-
* This is the field half of the engines' rule, on its own because the two `_cs` operator
|
|
89
|
-
* families consume it differently (see `caseSensitiveSupport`, which is the function you
|
|
90
|
-
* probably want).
|
|
91
|
-
*
|
|
92
|
-
* * `"present"` — the field itself is `keyword`, or it has a `keyword` subfield. One whole
|
|
93
|
-
* value, one indexed term, case intact.
|
|
94
|
-
* * `"absent"` — no such form. That covers analyzed `text` with no `keyword` sibling AND
|
|
95
|
-
* every non-string type: a `long`, `ip`, `date` or `boolean` field has no un-analyzed
|
|
96
|
-
* STRING form either. Whether that matters depends on the operator, which is why this
|
|
97
|
-
* function does not decide it.
|
|
98
|
-
* * `"unknown"` — no `fieldTypes` was supplied, or this field is not in it.
|
|
99
|
-
*
|
|
100
|
-
* Mirrors `FieldMappings::unanalyzed_form` (Rust) and `FieldMapping.unanalyzed_field`
|
|
101
|
-
* (Python) as closely as a flat name→type map allows. Two documented divergences, both of
|
|
102
|
-
* which fail quiet — see `caseSensitiveSupport`.
|
|
103
|
-
*
|
|
104
|
-
* ## The sibling test is on TYPE, not on the name `.keyword`
|
|
105
|
-
*
|
|
106
|
-
* Both engines scan a field's declared subfields for one whose TYPE is `keyword` (Rust:
|
|
107
|
-
* `subfield_of(field, |t| *t == FieldType::Keyword)`; Python: `keyword_field`, set for any
|
|
108
|
-
* subfield with `"type": "keyword"`). A multifield named `.raw` or `.kw` answers exactly as
|
|
109
|
-
* well as one named `.keyword`, and hard-coding the name would report `"absent"` on a working
|
|
110
|
-
* mapping. So this walks the map for `${field}.` prefixed entries and looks at their type.
|
|
111
|
-
*
|
|
112
|
-
* ## One level down, matching the engines
|
|
113
|
-
*
|
|
114
|
-
* A subfield is a DIRECT child: both engines read a declared `subfields` map, so
|
|
115
|
-
* `f.sub.deep` is not a subfield of `f` to either of them. Scanning descendants at any depth
|
|
116
|
-
* would report `"present"` for a mapping the engines refuse.
|
|
117
|
-
*/
|
|
118
|
-
export declare function casePreservingForm(field: string, fieldTypes?: Record<string, TqlFieldType>): "present" | "absent" | "unknown";
|
|
85
|
+
export declare function casePreservingForm(field: string, fieldTypes?: Record<string, TqlFieldType | TqlFieldType[]>): "present" | "absent" | "unknown";
|
|
119
86
|
/**
|
|
120
87
|
* Whether a field can answer a case-SENSITIVE operator, as far as the supplied schema shows.
|
|
121
88
|
*
|
|
@@ -180,7 +147,7 @@ export declare function casePreservingForm(field: string, fieldTypes?: Record<st
|
|
|
180
147
|
* distinguish the two `text` shapes it must ALSO contain the multifield entries
|
|
181
148
|
* (`message.keyword`), which is what an index-mapping flattener naturally produces.
|
|
182
149
|
*/
|
|
183
|
-
export declare function caseSensitiveSupport(field: string, operator: TqlComparisonOperator, fieldTypes?: Record<string, TqlFieldType>): CaseSensitiveSupport;
|
|
150
|
+
export declare function caseSensitiveSupport(field: string, operator: TqlComparisonOperator, fieldTypes?: Record<string, TqlFieldType | TqlFieldType[]>): CaseSensitiveSupport;
|
|
184
151
|
export declare class TqlValidator {
|
|
185
152
|
private options;
|
|
186
153
|
private errors;
|
package/dist/src/validator.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { parseTql as
|
|
2
|
-
const
|
|
1
|
+
import { parseTql as p } from "./parser.js";
|
|
2
|
+
const g = 1e3, r = [
|
|
3
3
|
"text",
|
|
4
4
|
"keyword",
|
|
5
5
|
"long",
|
|
@@ -17,7 +17,7 @@ const u = 1e3, r = [
|
|
|
17
17
|
"nested",
|
|
18
18
|
"object",
|
|
19
19
|
"geo_point"
|
|
20
|
-
],
|
|
20
|
+
], _ = [
|
|
21
21
|
"long",
|
|
22
22
|
"integer",
|
|
23
23
|
"short",
|
|
@@ -27,7 +27,7 @@ const u = 1e3, r = [
|
|
|
27
27
|
"half_float",
|
|
28
28
|
"scaled_float",
|
|
29
29
|
"unsigned_long"
|
|
30
|
-
], l = [...
|
|
30
|
+
], l = [..._, "date", "keyword"], u = [..._, "date"], b = {
|
|
31
31
|
// Equality (all types) — and now that is what it says.
|
|
32
32
|
//
|
|
33
33
|
// The transcribed twelve-element list this replaces claimed "all types" while omitting
|
|
@@ -157,9 +157,9 @@ const u = 1e3, r = [
|
|
|
157
157
|
// `keyword` and `text` stay out, and that is a decision rather than an omission: Python's
|
|
158
158
|
// `keyword_ops` excludes `between` while including `gte`, which is the divergence pinned
|
|
159
159
|
// in `tests/collectionAndNullSemantics.spec.ts`.
|
|
160
|
-
between:
|
|
161
|
-
not_between:
|
|
162
|
-
"!between":
|
|
160
|
+
between: u,
|
|
161
|
+
not_between: u,
|
|
162
|
+
"!between": u,
|
|
163
163
|
// Membership — the same set as `eq`, because `in` IS a disjunction of equalities and both
|
|
164
164
|
// evaluators implement it that way. The transcribed list this replaces omitted the five
|
|
165
165
|
// narrow numeric widths plus `nested`, `object` and `geo_point`.
|
|
@@ -226,11 +226,11 @@ const u = 1e3, r = [
|
|
|
226
226
|
"!any": r,
|
|
227
227
|
"!all": r,
|
|
228
228
|
"!none": r
|
|
229
|
-
},
|
|
229
|
+
}, h = {
|
|
230
230
|
cidr: ["ip"],
|
|
231
231
|
not_cidr: ["ip"],
|
|
232
232
|
"!cidr": ["ip"]
|
|
233
|
-
},
|
|
233
|
+
}, m = [
|
|
234
234
|
"contains_cs",
|
|
235
235
|
"not_contains_cs",
|
|
236
236
|
"!contains_cs",
|
|
@@ -240,35 +240,44 @@ const u = 1e3, r = [
|
|
|
240
240
|
"endswith_cs",
|
|
241
241
|
"not_endswith_cs",
|
|
242
242
|
"!endswith_cs"
|
|
243
|
-
],
|
|
243
|
+
], f = [
|
|
244
244
|
"in_cs",
|
|
245
245
|
"not_in_cs",
|
|
246
246
|
"!in_cs"
|
|
247
|
-
],
|
|
248
|
-
...
|
|
249
|
-
...
|
|
247
|
+
], v = [
|
|
248
|
+
...m,
|
|
249
|
+
...f
|
|
250
250
|
];
|
|
251
|
-
function
|
|
251
|
+
function c(o, t) {
|
|
252
|
+
const e = t?.[o];
|
|
253
|
+
return e === void 0 ? [] : Array.isArray(e) ? e : [e];
|
|
254
|
+
}
|
|
255
|
+
function E(o, t) {
|
|
252
256
|
if (!t)
|
|
253
257
|
return "unknown";
|
|
254
|
-
const e = t
|
|
255
|
-
if (
|
|
258
|
+
const e = c(o, t);
|
|
259
|
+
if (e.length === 0)
|
|
256
260
|
return "unknown";
|
|
257
|
-
if (e
|
|
261
|
+
if (e.includes("keyword") || c(`${o}.keyword`, t).includes("keyword"))
|
|
258
262
|
return "present";
|
|
259
263
|
const i = `${o}.`;
|
|
260
264
|
for (const [n, s] of Object.entries(t))
|
|
261
|
-
if (!(s
|
|
265
|
+
if (!(!(Array.isArray(s) ? s : [s]).includes("keyword") || !n.startsWith(i)) && !n.slice(i.length).includes("."))
|
|
262
266
|
return "present";
|
|
263
267
|
return "absent";
|
|
264
268
|
}
|
|
265
|
-
function
|
|
266
|
-
if (!
|
|
269
|
+
function k(o, t, e) {
|
|
270
|
+
if (!v.includes(t))
|
|
267
271
|
return "supported";
|
|
268
|
-
const i =
|
|
269
|
-
|
|
272
|
+
const i = E(o, e);
|
|
273
|
+
if (i !== "absent")
|
|
274
|
+
return i === "present" ? "supported" : "unknown";
|
|
275
|
+
if (!f.includes(t))
|
|
276
|
+
return "unsupported";
|
|
277
|
+
const n = c(o, e);
|
|
278
|
+
return n.length > 0 && n.every((s) => s === "text") ? "unsupported" : "supported";
|
|
270
279
|
}
|
|
271
|
-
const
|
|
280
|
+
const T = {
|
|
272
281
|
eq: ["string", "number", "boolean"],
|
|
273
282
|
ne: ["string", "number", "boolean"],
|
|
274
283
|
"=": ["string", "number", "boolean"],
|
|
@@ -337,7 +346,7 @@ const E = {
|
|
|
337
346
|
"!all": ["string", "number", "boolean"],
|
|
338
347
|
"!none": ["string", "number", "boolean"]
|
|
339
348
|
};
|
|
340
|
-
class
|
|
349
|
+
class A {
|
|
341
350
|
options;
|
|
342
351
|
errors;
|
|
343
352
|
warnings;
|
|
@@ -350,7 +359,7 @@ class k {
|
|
|
350
359
|
validate(t) {
|
|
351
360
|
if (this.errors = [], this.warnings = [], !t)
|
|
352
361
|
return { valid: !0, errors: [], warnings: [] };
|
|
353
|
-
const e =
|
|
362
|
+
const e = p(t);
|
|
354
363
|
return this.errors.push(...e.errors), this.warnings.push(...e.warnings), e.ast && this.validateExpression(e.ast), {
|
|
355
364
|
valid: this.errors.length === 0,
|
|
356
365
|
errors: this.errors,
|
|
@@ -477,22 +486,22 @@ class k {
|
|
|
477
486
|
"UNKNOWN_FIELD",
|
|
478
487
|
`Did you mean: ${this.suggestField(e)}`
|
|
479
488
|
), this.options.fieldTypes && t.field.type === "field") {
|
|
480
|
-
const n = this.options.fieldTypes
|
|
481
|
-
if (n) {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
`Operator '${t.operator}' requires field type ${
|
|
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}'`,
|
|
485
494
|
t.start,
|
|
486
495
|
t.end,
|
|
487
496
|
1,
|
|
488
497
|
1,
|
|
489
498
|
"TYPE_MISMATCH",
|
|
490
|
-
`Use '${t.operator}' only with ${
|
|
499
|
+
`Use '${t.operator}' only with ${a.join(" or ")} fields`
|
|
491
500
|
);
|
|
492
501
|
}
|
|
493
502
|
}
|
|
494
503
|
let i = !1;
|
|
495
|
-
if (t.field.type === "field" &&
|
|
504
|
+
if (t.field.type === "field" && k(e, t.operator, this.options.fieldTypes) === "unsupported") {
|
|
496
505
|
i = !0;
|
|
497
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`;
|
|
498
507
|
this.addWarning(
|
|
@@ -513,22 +522,22 @@ class k {
|
|
|
513
522
|
);
|
|
514
523
|
}
|
|
515
524
|
if (this.options.fieldTypes && t.field.type === "field") {
|
|
516
|
-
const n = this.options.fieldTypes
|
|
517
|
-
if (n) {
|
|
518
|
-
const
|
|
519
|
-
!i && !
|
|
520
|
-
`Operator '${t.operator}' may not work well with field type '${
|
|
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}'`,
|
|
521
530
|
t.start,
|
|
522
531
|
t.end,
|
|
523
532
|
1,
|
|
524
533
|
1,
|
|
525
534
|
"TYPE_MISMATCH",
|
|
526
|
-
`Consider using a different operator for ${
|
|
535
|
+
`Consider using a different operator for ${s} fields`
|
|
527
536
|
);
|
|
528
537
|
}
|
|
529
538
|
}
|
|
530
539
|
if (!Array.isArray(t.value) && t.value.type === "literal") {
|
|
531
|
-
const n =
|
|
540
|
+
const n = T[t.operator];
|
|
532
541
|
n && !n.includes(t.value.valueType) && this.addWarning(
|
|
533
542
|
`Operator '${t.operator}' expects ${n.join(" or ")} value`,
|
|
534
543
|
t.value.start,
|
|
@@ -540,9 +549,9 @@ class k {
|
|
|
540
549
|
}
|
|
541
550
|
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") {
|
|
542
551
|
const n = Array.from(t.value.value).length;
|
|
543
|
-
if (n >
|
|
552
|
+
if (n > g)
|
|
544
553
|
this.addError(
|
|
545
|
-
`Regex pattern is ${n} characters, which exceeds the maximum supported length of ${
|
|
554
|
+
`Regex pattern is ${n} characters, which exceeds the maximum supported length of ${g}. The query cannot run as written.`,
|
|
546
555
|
t.value.start,
|
|
547
556
|
t.value.end,
|
|
548
557
|
1,
|
|
@@ -622,17 +631,17 @@ class k {
|
|
|
622
631
|
});
|
|
623
632
|
}
|
|
624
633
|
}
|
|
625
|
-
function
|
|
626
|
-
return new
|
|
634
|
+
function w(o, t) {
|
|
635
|
+
return new A(t).validate(o);
|
|
627
636
|
}
|
|
628
|
-
function
|
|
629
|
-
return
|
|
637
|
+
function I(o) {
|
|
638
|
+
return w(o).valid;
|
|
630
639
|
}
|
|
631
|
-
function
|
|
632
|
-
const e =
|
|
640
|
+
function S(o, t) {
|
|
641
|
+
const e = w(o, t);
|
|
633
642
|
return [...e.errors, ...e.warnings];
|
|
634
643
|
}
|
|
635
|
-
function
|
|
644
|
+
function R(o) {
|
|
636
645
|
const t = [];
|
|
637
646
|
o.valid ? t.push("Query is valid") : t.push("Query has errors:");
|
|
638
647
|
for (const e of o.errors)
|
|
@@ -644,18 +653,18 @@ function I(o) {
|
|
|
644
653
|
}
|
|
645
654
|
export {
|
|
646
655
|
r as ALL_FIELD_TYPES,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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,
|
|
653
662
|
l as ORDERED_FIELD_TYPES,
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
|
661
670
|
};
|