@tellaro/tql 1.5.0 → 2.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 +67 -64
- package/dist/src/builder.d.ts +6 -0
- package/dist/src/completionProvider.js +276 -195
- package/dist/src/complexityAnalyzer.js +41 -32
- package/dist/src/index.d.ts +2 -2
- package/dist/src/parser.d.ts +193 -1
- package/dist/src/parser.js +614 -127
- package/dist/src/semanticPreview.d.ts +23 -0
- package/dist/src/semanticPreview.js +111 -41
- package/dist/src/tokenizer.d.ts +134 -5
- package/dist/src/tokenizer.js +47 -30
- package/dist/src/types.d.ts +86 -4
- package/dist/src/validator.d.ts +101 -1
- package/dist/src/validator.js +391 -152
- package/dist/tql.css +1 -1
- package/dist/vue/TqlInput.vue.js +1 -1
- package/dist/vue/TqlInput.vue2.js +273 -254
- package/package.json +1 -1
package/dist/src/types.d.ts
CHANGED
|
@@ -38,9 +38,25 @@ export interface TqlComparisonExpression {
|
|
|
38
38
|
start: number;
|
|
39
39
|
end: number;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* `field ANY value` and its relatives — a comparison applied across the ELEMENTS of an
|
|
43
|
+
* array-valued field rather than to the field as a whole.
|
|
44
|
+
*
|
|
45
|
+
* This type shipped for a long time with no parse path that could produce it: the tokenizer
|
|
46
|
+
* emitted `collectionOp` for `any` / `all` / `none`, the parser had no arm for that token
|
|
47
|
+
* type, and every collection query in the editor came back as
|
|
48
|
+
* "Expected operator after field". The type and `COLLECTION_OPERATORS` were exported the
|
|
49
|
+
* whole time, so the surface looked supported. `tests/collectionParity.spec.ts` now pins the
|
|
50
|
+
* accepted forms against both engine grammars.
|
|
51
|
+
*
|
|
52
|
+
* `negated` carries the `not any` / `!any` / `not_any` spellings, which both engines accept
|
|
53
|
+
* and which are otherwise indistinguishable from the positive form once `operator` has been
|
|
54
|
+
* normalised to its canonical upper-case name.
|
|
55
|
+
*/
|
|
41
56
|
export interface TqlCollectionExpression {
|
|
42
57
|
type: "collection";
|
|
43
58
|
operator: "ANY" | "ALL" | "NONE";
|
|
59
|
+
negated: boolean;
|
|
44
60
|
field: TqlFieldReference;
|
|
45
61
|
condition: TqlExpression;
|
|
46
62
|
start: number;
|
|
@@ -88,21 +104,52 @@ export interface TqlLiteral {
|
|
|
88
104
|
start: number;
|
|
89
105
|
end: number;
|
|
90
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* One argument inside a parameterised mutator call: `split(delimiter=",")`.
|
|
109
|
+
*
|
|
110
|
+
* Both engines accept named (`delimiter=","`), positional (`","`) and bare-identifier
|
|
111
|
+
* (`old=a`) arguments; neither accepts a list literal. See `mutator_arg` in
|
|
112
|
+
* tql/src/parser/grammar.pest and `_setup_mutators` in
|
|
113
|
+
* src/tql/parser_components/grammar.py.
|
|
114
|
+
*/
|
|
115
|
+
export interface TqlMutatorArgument {
|
|
116
|
+
/** Present for `name=value` form; absent for a positional argument. */
|
|
117
|
+
name?: string;
|
|
118
|
+
valueType: "string" | "number" | "boolean" | "null" | "identifier";
|
|
119
|
+
value: string | number | boolean | null;
|
|
120
|
+
start: number;
|
|
121
|
+
end: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* One link in a mutator chain, with the arguments it was called with.
|
|
125
|
+
*
|
|
126
|
+
* `TqlFieldReference.mutators` remains the flat list of names (unchanged for existing
|
|
127
|
+
* consumers); `mutatorCalls` carries the same chain with argument detail attached.
|
|
128
|
+
*/
|
|
129
|
+
export interface TqlMutatorCall {
|
|
130
|
+
name: string;
|
|
131
|
+
args: TqlMutatorArgument[];
|
|
132
|
+
/** True when the call carried an explicit `(...)` list, even an empty one. */
|
|
133
|
+
parenthesized: boolean;
|
|
134
|
+
start: number;
|
|
135
|
+
end: number;
|
|
136
|
+
}
|
|
91
137
|
export interface TqlFieldReference {
|
|
92
138
|
type: "field";
|
|
93
139
|
name: string;
|
|
94
140
|
path: string[];
|
|
95
141
|
mutators?: string[];
|
|
142
|
+
mutatorCalls?: TqlMutatorCall[];
|
|
96
143
|
typeHint?: TqlTypeHint;
|
|
97
144
|
start: number;
|
|
98
145
|
end: number;
|
|
99
146
|
}
|
|
100
|
-
export type TqlComparisonOperator = "eq" | "ne" | "=" | "!=" | "
|
|
147
|
+
export type TqlComparisonOperator = "eq" | "ne" | "=" | "!=" | "eq_ci" | "gt" | "gte" | "lt" | "lte" | ">" | ">=" | "<" | "<=" | "contains" | "not_contains" | "!contains" | "startswith" | "not_startswith" | "!startswith" | "endswith" | "not_endswith" | "!endswith" | "contains_cs" | "not_contains_cs" | "!contains_cs" | "startswith_cs" | "not_startswith_cs" | "!startswith_cs" | "endswith_cs" | "not_endswith_cs" | "!endswith_cs" | "regexp" | "regex" | "not_regexp" | "not_regex" | "!regexp" | "!regex" | "matches" | "not_matches" | "!matches" | "between" | "not_between" | "!between" | "in" | "not_in" | "!in" | "in_cs" | "not_in_cs" | "!in_cs" | "exists" | "not_exists" | "!exists" | "is" | "is_not" | "!is" | "cidr" | "not_cidr" | "!cidr" | "any" | "all" | "none" | "not_any" | "not_all" | "not_none" | "!any" | "!all" | "!none";
|
|
101
148
|
export type TqlLogicalOperator = "AND" | "OR" | "NOT";
|
|
102
149
|
export type TqlCollectionOperator = "ANY" | "ALL" | "NONE";
|
|
103
150
|
export type TqlMutator = "lowercase" | "uppercase" | "trim" | "split" | "length" | "replace" | "b64encode" | "b64decode" | "urldecode" | "hexencode" | "hexdecode" | "md5" | "sha256" | "refang" | "defang" | "is_private" | "is_global" | "is_multicast" | "is_loopback" | "is_link_local" | "nslookup" | "geoip_lookup" | "geoip" | "geo" | "any" | "all" | "avg" | "average" | "sum" | "min" | "max";
|
|
104
151
|
export type TqlAggregationFunction = "count" | "sum" | "avg" | "average" | "mean" | "median" | "med" | "min" | "max" | "percentile" | "percentiles" | "p" | "pct" | "percentile_rank" | "percentile_ranks" | "pct_rank" | "pct_ranks" | "std" | "standard_deviation" | "unique_count" | "cardinality" | "values" | "unique" | "distinct";
|
|
105
|
-
export type TqlTypeHint = "string" | "str" | "number" | "int" | "integer" | "float" | "double" | "decimal" | "date" | "bool" | "boolean" | "array" | "list" | "object" | "geo";
|
|
152
|
+
export type TqlTypeHint = "string" | "str" | "number" | "int" | "integer" | "float" | "double" | "decimal" | "date" | "bool" | "boolean" | "array" | "list" | "object" | "geo" | "ip";
|
|
106
153
|
export type TqlVisualizationType = "bar" | "barchart" | "horizontal_bar" | "line" | "area" | "stacked_area" | "pie" | "donut" | "scatter" | "heatmap" | "treemap" | "sunburst" | "table" | "number" | "gauge" | "map" | "grouped_bar" | "stacked_bar" | "nested_pie" | "nested_donut" | "chord" | "auto";
|
|
107
154
|
/**
|
|
108
155
|
* Visualization parameters (e.g., title="Events", stacked=true).
|
|
@@ -153,8 +200,43 @@ export interface TqlParseWarning {
|
|
|
153
200
|
code: TqlWarningCode;
|
|
154
201
|
suggestion?: string;
|
|
155
202
|
}
|
|
156
|
-
export type TqlErrorCode = "UNEXPECTED_TOKEN" | "UNEXPECTED_END" | "UNBALANCED_PARENS" | "UNBALANCED_BRACKETS" | "UNTERMINATED_STRING" | "INVALID_OPERATOR" | "INVALID_MUTATOR" | "INVALID_VALUE" | "MISSING_OPERAND" | "MISSING_OPERATOR" | "MISSING_VALUE" | "MISSING_FIELD" | "MISSING_MUTATOR" | "INVALID_SYNTAX" | "MAX_DEPTH_EXCEEDED" | "TYPE_MISMATCH"
|
|
157
|
-
|
|
203
|
+
export type TqlErrorCode = "UNEXPECTED_TOKEN" | "UNEXPECTED_END" | "UNBALANCED_PARENS" | "UNBALANCED_BRACKETS" | "UNTERMINATED_STRING" | "INVALID_OPERATOR" | "INVALID_MUTATOR" | "INVALID_VALUE" | "MISSING_OPERAND" | "MISSING_OPERATOR" | "MISSING_VALUE" | "MISSING_FIELD" | "MISSING_MUTATOR" | "INVALID_SYNTAX" | "MAX_DEPTH_EXCEEDED" | "TYPE_MISMATCH"
|
|
204
|
+
/**
|
|
205
|
+
* `f::int | trim::str` — a type hint on BOTH sides of the mutator chain.
|
|
206
|
+
*
|
|
207
|
+
* Both engines accept the hint in either position and refuse it in both, structurally: each
|
|
208
|
+
* grammar is an ordered choice whose first alternative consumes `f::int | trim` and strands
|
|
209
|
+
* `::str`. There is no defensible answer for which hint would win.
|
|
210
|
+
*/
|
|
211
|
+
| "DUPLICATE_TYPE_HINT"
|
|
212
|
+
/**
|
|
213
|
+
* `::` with a token after it that can never be a type name (`f::| trim`, `f:: eq 'a'`,
|
|
214
|
+
* `f | trim:: eq 'a'`, `f::lowercase`). All four are parse errors in both engines.
|
|
215
|
+
*
|
|
216
|
+
* `f::` at END of input is NOT this — that is a hint being typed, and stays a warning.
|
|
217
|
+
*/
|
|
218
|
+
| "MISSING_TYPE_HINT"
|
|
219
|
+
/**
|
|
220
|
+
* A malformed `top N` / `bottom N` modifier on a stats aggregation.
|
|
221
|
+
*
|
|
222
|
+
* Reserved for the shapes BOTH engines refuse: the modifier given twice on one aggregation
|
|
223
|
+
* (`sum(x, top 10) top 5`), a modifier with no count (`sum(x) top by y`), and the in-parens
|
|
224
|
+
* form on `count(*)` / `count()` (`count(*, top 2)`). A spelling only one engine refuses is
|
|
225
|
+
* an ENGINE_DIVERGENCE warning instead — never this.
|
|
226
|
+
*/
|
|
227
|
+
| "INVALID_STATS_MODIFIER";
|
|
228
|
+
export type TqlWarningCode = "UNKNOWN_FIELD" | "TYPE_MISMATCH" | "DEPRECATED_OPERATOR" | "REDUNDANT_PARENS"
|
|
229
|
+
/**
|
|
230
|
+
* The query parses, but only ONE of the two engines accepts it.
|
|
231
|
+
*
|
|
232
|
+
* Reserved for measured, currently-live divergences between `tql/src/parser/grammar.pest`
|
|
233
|
+
* and `src/tql/parser_components/grammar.py` — not for anything this package merely
|
|
234
|
+
* dislikes. The editor stays permissive (a red squiggle over syntax an engine accepts is
|
|
235
|
+
* the defect this parser keeps being fixed for) while still naming the engine that will
|
|
236
|
+
* refuse the query, because silently accepting it is the worse half of the same failure:
|
|
237
|
+
* the user finds out at execution instead of at the keystroke.
|
|
238
|
+
*/
|
|
239
|
+
| "ENGINE_DIVERGENCE";
|
|
158
240
|
export interface TqlValidationResult {
|
|
159
241
|
valid: boolean;
|
|
160
242
|
errors: TqlParseError[];
|
package/dist/src/validator.d.ts
CHANGED
|
@@ -1,4 +1,52 @@
|
|
|
1
|
-
import { TqlParseError, TqlParseWarning, TqlValidationOptions, TqlValidationResult } from './types';
|
|
1
|
+
import { TqlFieldType, TqlParseError, TqlParseWarning, TqlValidationOptions, TqlValidationResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Maximum regex pattern length TQL will compile, in characters (code points).
|
|
4
|
+
*
|
|
5
|
+
* A pattern longer than this is refused by every TQL runtime. It is flagged here
|
|
6
|
+
* -- in the editor, as the user types -- because the alternative is discovering
|
|
7
|
+
* it after the rule has been saved and deployed: before tql#185 the Python
|
|
8
|
+
* evaluator answered "no match" for an over-long pattern while the query parsed
|
|
9
|
+
* cleanly and reported health green, so a detection rule carrying one never
|
|
10
|
+
* fired and nothing anywhere said so.
|
|
11
|
+
*
|
|
12
|
+
* Mirrored by `MAX_REGEX_PATTERN_LENGTH` in `src/tql/regex_guard.py` and
|
|
13
|
+
* `tql/src/comparator.rs`. Changing it in one place only re-creates the
|
|
14
|
+
* divergence.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_REGEX_PATTERN_LENGTH = 1000;
|
|
17
|
+
/**
|
|
18
|
+
* Every field type in the `TqlFieldType` union.
|
|
19
|
+
*
|
|
20
|
+
* Declared once so an operator that genuinely applies to everything says so by NAME rather
|
|
21
|
+
* than by a transcribed twelve-element list — and so a new field type cannot be added to the
|
|
22
|
+
* union while silently falling out of the operators it belongs to.
|
|
23
|
+
* `tests/collectionOperatorTypes.spec.ts` reads the union out of `src/types.ts` and asserts
|
|
24
|
+
* this array is exactly it, so a divergence is a red rather than a quiet narrowing.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ALL_FIELD_TYPES: TqlFieldType[];
|
|
27
|
+
/**
|
|
28
|
+
* Every numeric field type in the union — all nine widths, not the five that get typed out.
|
|
29
|
+
*/
|
|
30
|
+
export declare const NUMERIC_FIELD_TYPES: TqlFieldType[];
|
|
31
|
+
/**
|
|
32
|
+
* The types an ORDERED comparison (`gt`/`gte`/`lt`/`lte`) is defined over.
|
|
33
|
+
*
|
|
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.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ORDERED_FIELD_TYPES: TqlFieldType[];
|
|
40
|
+
/**
|
|
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.
|
|
48
|
+
*/
|
|
49
|
+
export declare const BETWEEN_FIELD_TYPES: TqlFieldType[];
|
|
2
50
|
export declare class TqlValidator {
|
|
3
51
|
private options;
|
|
4
52
|
private errors;
|
|
@@ -12,6 +60,58 @@ export declare class TqlValidator {
|
|
|
12
60
|
* Validate an expression recursively
|
|
13
61
|
*/
|
|
14
62
|
private validateExpression;
|
|
63
|
+
/**
|
|
64
|
+
* `f any ['a','b']` is ill-typed. Say so, and name `in`.
|
|
65
|
+
*
|
|
66
|
+
* ## The rule, as both engines implement it
|
|
67
|
+
*
|
|
68
|
+
* `any` / `none` apply a comparison to each ELEMENT of an array-valued field. The operand
|
|
69
|
+
* is therefore a SCALAR: `f any 'admin'` asks whether some element equals `'admin'`. A
|
|
70
|
+
* LIST operand asks whether some single element equals the whole two-element list, which
|
|
71
|
+
* no scalar element can. `in` is already the membership operator, and is what the author
|
|
72
|
+
* meant.
|
|
73
|
+
*
|
|
74
|
+
* Both engines now refuse it -- `ab55587` in `tql/src/opensearch/query_builder.rs`
|
|
75
|
+
* (`build_collection_op`) and `_refuse_list_operand` in
|
|
76
|
+
* `src/tql/opensearch_components/query_converter.py` -- raising a type error whose
|
|
77
|
+
* `valid_operators` are `in` / `not in`. Before that it emitted `{"term": {"f": [..]}}`,
|
|
78
|
+
* which OpenSearch answers with HTTP 400 `[term] query does not support array of values`,
|
|
79
|
+
* failing the WHOLE search including every unrelated clause; the intermediate fix emitted
|
|
80
|
+
* `match_none`, which executed cleanly and excluded nothing, silently.
|
|
81
|
+
*
|
|
82
|
+
* ## Three arities, and only the middle one is new
|
|
83
|
+
*
|
|
84
|
+
* The engines do NOT test `is_array`. They unwrap a single-element list FIRST
|
|
85
|
+
* (`unwrap_single` in Rust, `len(value) == 1` in Python) and refuse only what is still a
|
|
86
|
+
* list. Measured against the live Python translator at `ab55587`:
|
|
87
|
+
*
|
|
88
|
+
* | Query | Python translator |
|
|
89
|
+
* |--------------------|------------------------------------------------------|
|
|
90
|
+
* | `f any 'admin'` | `{"term": {"f": "admin"}}` |
|
|
91
|
+
* | `f any ['a']` | `{"term": {"f": "a"}}` -- unwrapped, NOT refused |
|
|
92
|
+
* | `f any ['a','b']` | `TQLTypeError`, valid operators `in`, `not in` |
|
|
93
|
+
*
|
|
94
|
+
* A naive `Array.isArray(value)` check would flag `f any ['a']`, which both engines
|
|
95
|
+
* translate perfectly well -- a red squiggle over a working query, which is the defect
|
|
96
|
+
* this parser keeps being fixed for. Hence `length >= 2`, and hence the two arities are
|
|
97
|
+
* tested separately in `tests/collectionListOperand.spec.ts`.
|
|
98
|
+
*
|
|
99
|
+
* The empty list is not handled here: `f any []` is already an `INVALID_VALUE` error from
|
|
100
|
+
* the parser (`tests/emptyValueList.spec.ts`), and Python does not even parse it
|
|
101
|
+
* (`TQLSyntaxError`). Reporting it twice would be noise.
|
|
102
|
+
*
|
|
103
|
+
* ## Why `ALL` is excluded, and why that is not an oversight
|
|
104
|
+
*
|
|
105
|
+
* `all` / `not_all` are answered by a Painless script that compares each element to
|
|
106
|
+
* `params.value`. A list operand there emits valid DSL that matches nothing -- which is
|
|
107
|
+
* what both in-memory evaluators also answer -- so both engines dispatch `all` BEFORE the
|
|
108
|
+
* refusal and leave it untouched. `f all ['a','b']` translating cleanly is a pinned
|
|
109
|
+
* negative case here, not a gap.
|
|
110
|
+
*
|
|
111
|
+
* Negation is irrelevant: in both engines the array check precedes the quantifier wrapper,
|
|
112
|
+
* so `not any` / `not none` refuse on the same footing as the positive spellings.
|
|
113
|
+
*/
|
|
114
|
+
private validateCollectionOperand;
|
|
15
115
|
/**
|
|
16
116
|
* Validate a comparison expression
|
|
17
117
|
*/
|