tellaro-query-language 0.2.7__tar.gz → 0.2.10__tar.gz
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.
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/PKG-INFO +10 -9
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/pyproject.toml +9 -8
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core_components/opensearch_operations.py +2 -2
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/evaluator_components/value_comparison.py +10 -8
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/dns.py +42 -9
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser.py +78 -20
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/grammar.py +19 -1
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/LICENSE +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/README.md +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/__init__.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/analyzer.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/cache/__init__.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/cache/base.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/cache/memory.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/cache/redis.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/cli.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core_components/README.md +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core_components/__init__.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core_components/file_operations.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core_components/stats_operations.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core_components/validation_operations.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/evaluator.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/evaluator_components/README.md +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/evaluator_components/__init__.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/evaluator_components/field_access.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/evaluator_components/special_expressions.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/exceptions.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/field_type_inference.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/geoip_normalizer.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutator_analyzer.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/__init__.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/base.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/encoding.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/geo.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/list.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/network.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/security.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/mutators/string.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch_components/README.md +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch_components/__init__.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch_components/field_mapping.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch_components/lucene_converter.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch_components/query_converter.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch_mappings.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch_stats.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/README.md +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/__init__.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/ast_builder.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/error_analyzer.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/field_extractor.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/post_processor.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/scripts.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/stats_evaluator.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/stats_transformer.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/streaming_file_processor.py +0 -0
- {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/validators.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tellaro-query-language
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
4
4
|
Summary: A flexible, human-friendly query language for searching and filtering structured data
|
|
5
|
-
Home-page: https://github.com/tellaro/tellaro-query-language
|
|
6
5
|
License: Proprietary
|
|
6
|
+
License-File: LICENSE
|
|
7
7
|
Keywords: query,language,opensearch,elasticsearch,search,filter,tql
|
|
8
8
|
Author: Justin Henderson
|
|
9
9
|
Author-email: justin@tellaro.io
|
|
@@ -18,14 +18,15 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Classifier: Topic :: Text Processing :: Linguistic
|
|
20
20
|
Provides-Extra: opensearch
|
|
21
|
-
Requires-Dist: dnspython (>=2.
|
|
22
|
-
Requires-Dist: maxminddb (>=
|
|
21
|
+
Requires-Dist: dnspython (>=2.8.0,<3.0.0)
|
|
22
|
+
Requires-Dist: maxminddb (>=3.0.0,<4.0.0)
|
|
23
23
|
Requires-Dist: opensearch-dsl (>=2.1.0,<3.0.0) ; extra == "opensearch"
|
|
24
|
-
Requires-Dist: opensearch-py (>=2.
|
|
25
|
-
Requires-Dist: pyparsing (>=3.
|
|
26
|
-
Requires-Dist: setuptools (>=80.
|
|
27
|
-
Requires-Dist: urllib3 (>=2.
|
|
24
|
+
Requires-Dist: opensearch-py (>=2.8.0,<3.0.0) ; extra == "opensearch"
|
|
25
|
+
Requires-Dist: pyparsing (>=3.3.0,<4.0.0)
|
|
26
|
+
Requires-Dist: setuptools (>=80.9.0,<81.0.0)
|
|
27
|
+
Requires-Dist: urllib3 (>=2.6.0,<3.0.0)
|
|
28
28
|
Project-URL: Documentation, https://github.com/tellaro/tellaro-query-language/tree/main/docs
|
|
29
|
+
Project-URL: Homepage, https://github.com/tellaro/tellaro-query-language
|
|
29
30
|
Project-URL: Repository, https://github.com/tellaro/tellaro-query-language
|
|
30
31
|
Description-Content-Type: text/markdown
|
|
31
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "tellaro-query-language"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.10"
|
|
4
4
|
description = "A flexible, human-friendly query language for searching and filtering structured data"
|
|
5
5
|
authors = ["Justin Henderson <justin@tellaro.io>"]
|
|
6
6
|
license = "Proprietary"
|
|
@@ -34,13 +34,13 @@ tests = "tql.scripts:run_tests"
|
|
|
34
34
|
|
|
35
35
|
[tool.poetry.dependencies]
|
|
36
36
|
python = ">=3.11,<3.14"
|
|
37
|
-
pyparsing = "^3.
|
|
38
|
-
setuptools = "^80.
|
|
39
|
-
dnspython = "^2.
|
|
40
|
-
opensearch-py = {version = "^2.
|
|
37
|
+
pyparsing = "^3.3.0"
|
|
38
|
+
setuptools = "^80.9.0"
|
|
39
|
+
dnspython = "^2.8.0"
|
|
40
|
+
opensearch-py = {version = "^2.8.0", optional = true}
|
|
41
41
|
opensearch-dsl = {version = "^2.1.0", optional = true}
|
|
42
|
-
maxminddb = "^
|
|
43
|
-
urllib3 = "^2.
|
|
42
|
+
maxminddb = "^3.0.0"
|
|
43
|
+
urllib3 = "^2.6.0"
|
|
44
44
|
|
|
45
45
|
[tool.poetry.extras]
|
|
46
46
|
opensearch = ["opensearch-py", "opensearch-dsl"]
|
|
@@ -76,7 +76,8 @@ vulture = "^2.14"
|
|
|
76
76
|
types-setuptools = "^75.8.2.20250305"
|
|
77
77
|
opensearch-dsl = "^2.1.0"
|
|
78
78
|
python-dotenv = "^1.1.0"
|
|
79
|
-
jupyter
|
|
79
|
+
# jupyter removed due to CVE-2025-53000 (nbconvert vulnerability, no patch available)
|
|
80
|
+
# Use ipykernel for IDE notebook support; reinstall jupyter manually if needed
|
|
80
81
|
redis = "^7.1.0"
|
|
81
82
|
|
|
82
83
|
[build-system]
|
|
@@ -813,10 +813,10 @@ class OpenSearchOperations:
|
|
|
813
813
|
# scan_all mode with post-processing - process all results
|
|
814
814
|
processor = QueryPostProcessor()
|
|
815
815
|
|
|
816
|
-
# Extract all documents from
|
|
816
|
+
# Extract all documents from initial_hits (which contains all scrolled results)
|
|
817
817
|
documents = []
|
|
818
818
|
hit_metadata = []
|
|
819
|
-
for hit in
|
|
819
|
+
for hit in initial_hits:
|
|
820
820
|
documents.append(hit["_source"])
|
|
821
821
|
hit_metadata.append(
|
|
822
822
|
{
|
|
@@ -170,15 +170,17 @@ class ValueComparator:
|
|
|
170
170
|
return str(field_value).lower().endswith(str(expected_value).lower())
|
|
171
171
|
elif operator == "in":
|
|
172
172
|
if isinstance(expected_value, list):
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
# Convert list elements to appropriate types for comparison
|
|
174
|
+
converted_list = [self._convert_numeric(val) for val in expected_value]
|
|
175
|
+
|
|
176
|
+
if isinstance(field_value, (list, tuple)):
|
|
177
|
+
# Field is an array - check if ANY element of field equals ANY value in list
|
|
178
|
+
# This handles both:
|
|
179
|
+
# - "value" in field (single expected value, array field)
|
|
180
|
+
# - field in ["val1", "val2"] (multiple expected values, array field)
|
|
181
|
+
return any(elem in converted_list for elem in field_value)
|
|
178
182
|
else:
|
|
179
|
-
#
|
|
180
|
-
# Convert list elements to appropriate types for comparison
|
|
181
|
-
converted_list = [self._convert_numeric(val) for val in expected_value]
|
|
183
|
+
# Scalar field - check if field_value is in the list
|
|
182
184
|
return field_value in converted_list
|
|
183
185
|
else:
|
|
184
186
|
return field_value == expected_value
|
|
@@ -246,20 +246,53 @@ class NSLookupMutator(BaseMutator):
|
|
|
246
246
|
|
|
247
247
|
# Save enrichment if requested
|
|
248
248
|
if save_enrichment:
|
|
249
|
-
#
|
|
249
|
+
# Determine the DNS data field (for full ECS structure)
|
|
250
|
+
# If append_field is like "destination.domain", dns_field is "destination.dns"
|
|
251
|
+
if append_field.endswith(".domain"):
|
|
252
|
+
dns_field = append_field.rsplit(".domain", 1)[0] + ".dns"
|
|
253
|
+
elif append_field == "domain":
|
|
254
|
+
dns_field = "dns"
|
|
255
|
+
else:
|
|
256
|
+
dns_field = append_field + "_dns"
|
|
257
|
+
|
|
250
258
|
if len(queries) == 1 and queries[0] in resolved_results:
|
|
251
|
-
# Single query:
|
|
252
|
-
|
|
259
|
+
# Single query: extract domain names for the domain field
|
|
260
|
+
dns_data = resolved_results[queries[0]]
|
|
261
|
+
answers = dns_data.get("answers", [])
|
|
262
|
+
|
|
263
|
+
# Store domain name(s) in the domain field (string or list of strings)
|
|
264
|
+
if len(answers) == 1:
|
|
265
|
+
append_to_result(record, append_field, answers[0])
|
|
266
|
+
elif len(answers) > 1:
|
|
267
|
+
append_to_result(record, append_field, answers)
|
|
268
|
+
# If no answers, don't set the domain field (leave it unset)
|
|
269
|
+
|
|
270
|
+
# Store full ECS data in the dns field
|
|
271
|
+
append_to_result(record, dns_field, dns_data)
|
|
272
|
+
|
|
253
273
|
elif len(queries) > 1:
|
|
254
|
-
# Multiple queries:
|
|
274
|
+
# Multiple queries: collect all domain names and ECS results
|
|
275
|
+
all_domains = []
|
|
255
276
|
results_array = []
|
|
256
277
|
for query in queries:
|
|
257
278
|
if query in resolved_results:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
279
|
+
dns_data = resolved_results[query]
|
|
280
|
+
results_array.append(dns_data)
|
|
281
|
+
answers = dns_data.get("answers", [])
|
|
282
|
+
all_domains.extend(answers)
|
|
283
|
+
|
|
284
|
+
# Store unique domain names in the domain field
|
|
285
|
+
if all_domains:
|
|
286
|
+
unique_domains = list(dict.fromkeys(all_domains)) # Preserve order, remove dupes
|
|
287
|
+
if len(unique_domains) == 1:
|
|
288
|
+
append_to_result(record, append_field, unique_domains[0])
|
|
289
|
+
else:
|
|
290
|
+
append_to_result(record, append_field, unique_domains)
|
|
291
|
+
|
|
292
|
+
# Store full ECS data array in the dns field
|
|
293
|
+
if results_array:
|
|
294
|
+
append_to_result(record, dns_field, results_array)
|
|
295
|
+
# If no results, don't set any fields
|
|
263
296
|
|
|
264
297
|
# For enrichment mutators, return data for comparison
|
|
265
298
|
# The full enrichment data is stored via append_to_result above
|
|
@@ -332,6 +332,29 @@ class TQLParser:
|
|
|
332
332
|
return result
|
|
333
333
|
|
|
334
334
|
if len(parsed) == 4:
|
|
335
|
+
# Check for field_in_values marker: field in [values] __field_in_values__
|
|
336
|
+
if isinstance(parsed[3], str) and parsed[3] == "__field_in_values__":
|
|
337
|
+
# This is field in [values] syntax
|
|
338
|
+
field_part, op, values_list, marker = parsed
|
|
339
|
+
field_name, type_hint, field_mutators = self.ast_builder.extract_field_info(field_part)
|
|
340
|
+
# Extract values from the list
|
|
341
|
+
values = []
|
|
342
|
+
for item in values_list:
|
|
343
|
+
if isinstance(item, list) and len(item) >= 1:
|
|
344
|
+
values.append(item[0] if len(item) == 1 else item)
|
|
345
|
+
else:
|
|
346
|
+
values.append(item)
|
|
347
|
+
result = {
|
|
348
|
+
"type": "comparison",
|
|
349
|
+
"field": field_name,
|
|
350
|
+
"type_hint": type_hint,
|
|
351
|
+
"operator": "in",
|
|
352
|
+
"value": values,
|
|
353
|
+
}
|
|
354
|
+
if field_mutators:
|
|
355
|
+
result["field_mutators"] = field_mutators
|
|
356
|
+
return result
|
|
357
|
+
|
|
335
358
|
# Check for ANY/ALL operators: ANY field op value
|
|
336
359
|
first, field, operator, value = parsed
|
|
337
360
|
|
|
@@ -368,12 +391,25 @@ class TQLParser:
|
|
|
368
391
|
normalized_operator = "any"
|
|
369
392
|
else:
|
|
370
393
|
normalized_operator = f"not_{third.lower()}"
|
|
394
|
+
|
|
395
|
+
# Extract value properly - unwrap if it's a double-wrapped list
|
|
396
|
+
# This happens for "field not in [values]" where the list is wrapped twice
|
|
397
|
+
value = fourth
|
|
398
|
+
if (
|
|
399
|
+
third.lower() == "in"
|
|
400
|
+
and isinstance(fourth, list)
|
|
401
|
+
and len(fourth) == 1
|
|
402
|
+
and isinstance(fourth[0], list)
|
|
403
|
+
):
|
|
404
|
+
# Unwrap the double-wrapped list for "not in" with values
|
|
405
|
+
value = fourth[0]
|
|
406
|
+
|
|
371
407
|
result = {
|
|
372
408
|
"type": "comparison",
|
|
373
409
|
"field": field_name,
|
|
374
410
|
"type_hint": type_hint,
|
|
375
411
|
"operator": normalized_operator,
|
|
376
|
-
"value":
|
|
412
|
+
"value": value,
|
|
377
413
|
}
|
|
378
414
|
if field_mutators:
|
|
379
415
|
result["field_mutators"] = field_mutators
|
|
@@ -577,6 +613,29 @@ class TQLParser:
|
|
|
577
613
|
result["value_mutators"] = value_mutators
|
|
578
614
|
return result
|
|
579
615
|
elif len(parsed) == 5:
|
|
616
|
+
# Check for field_not_in_values marker: field not in [values] __field_not_in_values__
|
|
617
|
+
if isinstance(parsed[4], str) and parsed[4] == "__field_not_in_values__":
|
|
618
|
+
# This is field not in [values] syntax
|
|
619
|
+
field_part, not_kw, in_kw, values_list, marker = parsed
|
|
620
|
+
field_name, type_hint, field_mutators = self.ast_builder.extract_field_info(field_part)
|
|
621
|
+
# Extract values from the list
|
|
622
|
+
values = []
|
|
623
|
+
for item in values_list:
|
|
624
|
+
if isinstance(item, list) and len(item) >= 1:
|
|
625
|
+
values.append(item[0] if len(item) == 1 else item)
|
|
626
|
+
else:
|
|
627
|
+
values.append(item)
|
|
628
|
+
result = {
|
|
629
|
+
"type": "comparison",
|
|
630
|
+
"field": field_name,
|
|
631
|
+
"type_hint": type_hint,
|
|
632
|
+
"operator": "not_in",
|
|
633
|
+
"value": values,
|
|
634
|
+
}
|
|
635
|
+
if field_mutators:
|
|
636
|
+
result["field_mutators"] = field_mutators
|
|
637
|
+
return result
|
|
638
|
+
|
|
580
639
|
# Check for natural between syntax: field between value1 and value2
|
|
581
640
|
# Only process as between if the second element is "between"
|
|
582
641
|
if (
|
|
@@ -996,7 +1055,7 @@ class TQLParser:
|
|
|
996
1055
|
return {"type": "unknown", "value": parsed}
|
|
997
1056
|
else:
|
|
998
1057
|
# Comparison operation
|
|
999
|
-
# Handle 'in' operator -
|
|
1058
|
+
# Handle 'in' operator - can be "value in field(s)" or "field in [values]"
|
|
1000
1059
|
if isinstance(operator, str) and operator.lower() == "in":
|
|
1001
1060
|
# Check for old syntax: [field1, field2] in value
|
|
1002
1061
|
# The parser wraps list literals, so check for wrapped lists too
|
|
@@ -1031,26 +1090,21 @@ class TQLParser:
|
|
|
1031
1090
|
position=0,
|
|
1032
1091
|
)
|
|
1033
1092
|
|
|
1034
|
-
#
|
|
1035
|
-
#
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
# Check if right is a list of fields
|
|
1093
|
+
# Check if right is a list of fields (value in [fields] syntax)
|
|
1094
|
+
# Note: field in [values] is now handled by field_in_values grammar rule
|
|
1095
|
+
# with __field_in_values__ marker, so this is only for value in [fields]
|
|
1039
1096
|
if isinstance(right, list) and len(right) > 0:
|
|
1040
|
-
# Check if all elements are fields
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
break
|
|
1048
|
-
elif not isinstance(item, str):
|
|
1049
|
-
all_fields = False
|
|
1050
|
-
break
|
|
1097
|
+
# Check if all elements are fields (typed_fields wrapped in lists)
|
|
1098
|
+
# value in [field1, field2] produces right = [['field1'], ['field2']]
|
|
1099
|
+
# field in ["val1", "val2"] produces right = ['val1', 'val2']
|
|
1100
|
+
# (but field in [values] is now handled by __field_in_values__ marker)
|
|
1101
|
+
is_field_list = all(
|
|
1102
|
+
isinstance(item, list) and len(item) >= 1 and isinstance(item[0], str) for item in right
|
|
1103
|
+
)
|
|
1051
1104
|
|
|
1052
|
-
if
|
|
1105
|
+
if is_field_list:
|
|
1053
1106
|
# This is "value in [field1, field2, ...]" format
|
|
1107
|
+
value_extracted, value_mutators = self.ast_builder.extract_value_info(left)
|
|
1054
1108
|
# Create an OR expression for all fields
|
|
1055
1109
|
field_comparisons = []
|
|
1056
1110
|
for field in right:
|
|
@@ -1083,7 +1137,11 @@ class TQLParser:
|
|
|
1083
1137
|
}
|
|
1084
1138
|
return result
|
|
1085
1139
|
|
|
1086
|
-
#
|
|
1140
|
+
# For 'in' operator with single field, left is the value, right is field
|
|
1141
|
+
# Extract the value from left
|
|
1142
|
+
value_extracted, value_mutators = self.ast_builder.extract_value_info(left)
|
|
1143
|
+
|
|
1144
|
+
# Treat as standard "value in field" (single field)
|
|
1087
1145
|
field_name, type_hint, field_mutators = self.ast_builder.extract_field_info(right)
|
|
1088
1146
|
result = {
|
|
1089
1147
|
"type": "comparison",
|
{tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/grammar.py
RENAMED
|
@@ -225,10 +225,26 @@ class TQLGrammar:
|
|
|
225
225
|
self.field_list_item = self.typed_field
|
|
226
226
|
self.field_list = Group(Suppress("[") + delimitedList(self.field_list_item) + Suppress("]"))
|
|
227
227
|
|
|
228
|
-
# Special case for 'in' operator -
|
|
228
|
+
# Special case for 'in' operator - value in field(s)
|
|
229
229
|
self.value_in_field = Group(self.value + CaselessKeyword("in") + self.typed_field)
|
|
230
230
|
self.value_in_field_list = Group(self.value + CaselessKeyword("in") + self.field_list)
|
|
231
231
|
|
|
232
|
+
# Field-first 'in' operator: field in [value1, value2, ...] (checks if field equals any value)
|
|
233
|
+
# Add a marker "__field_in_values__" to distinguish from value_in_field
|
|
234
|
+
self.field_in_values = Group(
|
|
235
|
+
self.typed_field
|
|
236
|
+
+ CaselessKeyword("in")
|
|
237
|
+
+ self.list_literal
|
|
238
|
+
+ Literal("").setParseAction(lambda: "__field_in_values__")
|
|
239
|
+
)
|
|
240
|
+
self.field_not_in_values = Group(
|
|
241
|
+
self.typed_field
|
|
242
|
+
+ (CaselessKeyword("not") | Literal("!"))
|
|
243
|
+
+ CaselessKeyword("in")
|
|
244
|
+
+ self.list_literal
|
|
245
|
+
+ Literal("").setParseAction(lambda: "__field_not_in_values__")
|
|
246
|
+
)
|
|
247
|
+
|
|
232
248
|
def _setup_special_expressions(self):
|
|
233
249
|
"""Set up special expressions like geo() and nslookup()."""
|
|
234
250
|
# Forward declare for recursive use
|
|
@@ -455,6 +471,8 @@ class TQLGrammar:
|
|
|
455
471
|
| self.is_not_comparison
|
|
456
472
|
| self.not_between_comparison_natural
|
|
457
473
|
| self.not_between_comparison_list
|
|
474
|
+
| self.field_not_in_values # field not in [values] - must come before std_comparison
|
|
475
|
+
| self.field_in_values # field in [values] - must come before std_comparison
|
|
458
476
|
| self.std_comparison
|
|
459
477
|
| self.between_comparison_natural
|
|
460
478
|
| self.between_comparison_list
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core_components/README.md
RENAMED
|
File without changes
|
{tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/core_components/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/field_type_inference.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/opensearch_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
{tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/README.md
RENAMED
|
File without changes
|
{tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/parser_components/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tellaro_query_language-0.2.7 → tellaro_query_language-0.2.10}/src/tql/streaming_file_processor.py
RENAMED
|
File without changes
|
|
File without changes
|