tellaro-query-language 0.1.0__py3-none-any.whl
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.1.0.dist-info/LICENSE +21 -0
- tellaro_query_language-0.1.0.dist-info/METADATA +401 -0
- tellaro_query_language-0.1.0.dist-info/RECORD +56 -0
- tellaro_query_language-0.1.0.dist-info/WHEEL +4 -0
- tellaro_query_language-0.1.0.dist-info/entry_points.txt +7 -0
- tql/__init__.py +47 -0
- tql/analyzer.py +385 -0
- tql/cache/__init__.py +7 -0
- tql/cache/base.py +25 -0
- tql/cache/memory.py +63 -0
- tql/cache/redis.py +68 -0
- tql/core.py +929 -0
- tql/core_components/README.md +92 -0
- tql/core_components/__init__.py +20 -0
- tql/core_components/file_operations.py +113 -0
- tql/core_components/opensearch_operations.py +869 -0
- tql/core_components/stats_operations.py +200 -0
- tql/core_components/validation_operations.py +599 -0
- tql/evaluator.py +379 -0
- tql/evaluator_components/README.md +131 -0
- tql/evaluator_components/__init__.py +17 -0
- tql/evaluator_components/field_access.py +176 -0
- tql/evaluator_components/special_expressions.py +296 -0
- tql/evaluator_components/value_comparison.py +315 -0
- tql/exceptions.py +160 -0
- tql/geoip_normalizer.py +233 -0
- tql/mutator_analyzer.py +830 -0
- tql/mutators/__init__.py +222 -0
- tql/mutators/base.py +78 -0
- tql/mutators/dns.py +316 -0
- tql/mutators/encoding.py +218 -0
- tql/mutators/geo.py +363 -0
- tql/mutators/list.py +212 -0
- tql/mutators/network.py +163 -0
- tql/mutators/security.py +225 -0
- tql/mutators/string.py +165 -0
- tql/opensearch.py +78 -0
- tql/opensearch_components/README.md +130 -0
- tql/opensearch_components/__init__.py +17 -0
- tql/opensearch_components/field_mapping.py +399 -0
- tql/opensearch_components/lucene_converter.py +305 -0
- tql/opensearch_components/query_converter.py +775 -0
- tql/opensearch_mappings.py +309 -0
- tql/opensearch_stats.py +451 -0
- tql/parser.py +1363 -0
- tql/parser_components/README.md +72 -0
- tql/parser_components/__init__.py +20 -0
- tql/parser_components/ast_builder.py +162 -0
- tql/parser_components/error_analyzer.py +101 -0
- tql/parser_components/field_extractor.py +112 -0
- tql/parser_components/grammar.py +473 -0
- tql/post_processor.py +737 -0
- tql/scripts.py +124 -0
- tql/stats_evaluator.py +444 -0
- tql/stats_transformer.py +184 -0
- tql/validators.py +110 -0
tql/mutators/string.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""String manipulation mutators."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from .base import BaseMutator, PerformanceClass, append_to_result
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LowercaseMutator(BaseMutator):
|
|
9
|
+
"""Mutator that converts a string value to lowercase.
|
|
10
|
+
|
|
11
|
+
Performance Characteristics:
|
|
12
|
+
- In-memory: FAST - Simple string operation with minimal overhead
|
|
13
|
+
- OpenSearch: MODERATE - Requires post-processing of all results
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
field | lowercase eq 'hello'
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, params: Optional[Dict[str, Any]] = None) -> None:
|
|
20
|
+
super().__init__(params)
|
|
21
|
+
# String operations are very fast in memory
|
|
22
|
+
self.performance_in_memory = PerformanceClass.FAST
|
|
23
|
+
# Post-processing in OpenSearch has moderate impact due to result set iteration
|
|
24
|
+
self.performance_opensearch = PerformanceClass.MODERATE
|
|
25
|
+
|
|
26
|
+
def apply(self, field_name: str, record: Dict[str, Any], value: Any) -> Any:
|
|
27
|
+
if isinstance(value, str):
|
|
28
|
+
return value.lower()
|
|
29
|
+
elif isinstance(value, (list, tuple)):
|
|
30
|
+
# Apply lowercase to each element in the array
|
|
31
|
+
return [self.apply(field_name, record, item) if isinstance(item, str) else item for item in value]
|
|
32
|
+
else:
|
|
33
|
+
# For non-string values, return as-is
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UppercaseMutator(BaseMutator):
|
|
38
|
+
"""Mutator that converts a string value to uppercase.
|
|
39
|
+
|
|
40
|
+
Performance Characteristics:
|
|
41
|
+
- In-memory: FAST - Simple string operation with minimal overhead
|
|
42
|
+
- OpenSearch: MODERATE - Requires post-processing of all results
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
field | uppercase eq 'HELLO'
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, params: Optional[Dict[str, Any]] = None) -> None:
|
|
49
|
+
super().__init__(params)
|
|
50
|
+
self.performance_in_memory = PerformanceClass.FAST
|
|
51
|
+
self.performance_opensearch = PerformanceClass.MODERATE
|
|
52
|
+
|
|
53
|
+
def apply(self, field_name: str, record: Dict[str, Any], value: Any) -> Any:
|
|
54
|
+
if isinstance(value, str):
|
|
55
|
+
return value.upper()
|
|
56
|
+
elif isinstance(value, (list, tuple)):
|
|
57
|
+
# Apply uppercase to each element in the array
|
|
58
|
+
return [self.apply(field_name, record, item) if isinstance(item, str) else item for item in value]
|
|
59
|
+
else:
|
|
60
|
+
# For non-string values, return as-is
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TrimMutator(BaseMutator):
|
|
65
|
+
"""Mutator that trims whitespace from a string value.
|
|
66
|
+
|
|
67
|
+
Performance Characteristics:
|
|
68
|
+
- In-memory: FAST - Simple string operation with minimal overhead
|
|
69
|
+
- OpenSearch: MODERATE - Requires post-processing of all results
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
field | trim eq 'hello world'
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, params: Optional[Dict[str, Any]] = None) -> None:
|
|
76
|
+
super().__init__(params)
|
|
77
|
+
self.performance_in_memory = PerformanceClass.FAST
|
|
78
|
+
self.performance_opensearch = PerformanceClass.MODERATE
|
|
79
|
+
|
|
80
|
+
def apply(self, field_name: str, record: Dict[str, Any], value: Any) -> Any:
|
|
81
|
+
if isinstance(value, str):
|
|
82
|
+
return value.strip()
|
|
83
|
+
elif isinstance(value, (list, tuple)):
|
|
84
|
+
# Apply trim to each element in the array
|
|
85
|
+
return [self.apply(field_name, record, item) if isinstance(item, str) else item for item in value]
|
|
86
|
+
else:
|
|
87
|
+
# For non-string values, return as-is
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class SplitMutator(BaseMutator):
|
|
92
|
+
"""Mutator that splits a string value on a delimiter."""
|
|
93
|
+
|
|
94
|
+
def apply(self, field_name: str, record: Dict[str, Any], value: Any) -> Any:
|
|
95
|
+
"""Apply the split transformation."""
|
|
96
|
+
delimiter = self.params.get("delimiter", " ")
|
|
97
|
+
append_field = self.params.get("field")
|
|
98
|
+
|
|
99
|
+
# Perform the split operation
|
|
100
|
+
if value is None:
|
|
101
|
+
# Handle None - return empty list
|
|
102
|
+
split_result = []
|
|
103
|
+
elif isinstance(value, str):
|
|
104
|
+
split_result = value.split(delimiter)
|
|
105
|
+
elif isinstance(value, list):
|
|
106
|
+
# Split each string in the list
|
|
107
|
+
split_result = []
|
|
108
|
+
for item in value:
|
|
109
|
+
if isinstance(item, str):
|
|
110
|
+
split_result.extend(item.split(delimiter))
|
|
111
|
+
else:
|
|
112
|
+
# Keep non-string items as-is
|
|
113
|
+
split_result.append(item)
|
|
114
|
+
elif isinstance(value, (int, float, bool)):
|
|
115
|
+
# Convert to string first, then split
|
|
116
|
+
split_result = str(value).split(delimiter)
|
|
117
|
+
else:
|
|
118
|
+
# For other types (dict, etc), return as single-item list
|
|
119
|
+
# This maintains list type consistency for the split operation
|
|
120
|
+
split_result = [value]
|
|
121
|
+
|
|
122
|
+
# If append_field is specified, add to record and return original value
|
|
123
|
+
if append_field:
|
|
124
|
+
append_to_result(record, append_field, split_result)
|
|
125
|
+
return value
|
|
126
|
+
else:
|
|
127
|
+
# Return the split result directly
|
|
128
|
+
return split_result
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class LengthMutator(BaseMutator):
|
|
132
|
+
"""Mutator that returns the length of a string or list."""
|
|
133
|
+
|
|
134
|
+
def apply(self, field_name: str, record: Dict[str, Any], value: Any) -> Any:
|
|
135
|
+
"""Apply the length transformation."""
|
|
136
|
+
append_field = self.params.get("field")
|
|
137
|
+
|
|
138
|
+
# Calculate length
|
|
139
|
+
if value is None:
|
|
140
|
+
# Handle None gracefully - treat as empty
|
|
141
|
+
length_value = 0
|
|
142
|
+
elif isinstance(value, (str, list, dict, tuple, set)):
|
|
143
|
+
length_value = len(value)
|
|
144
|
+
elif isinstance(value, (int, float)):
|
|
145
|
+
# For numbers, convert to string and get length
|
|
146
|
+
# This allows checking number of digits
|
|
147
|
+
length_value = len(str(value))
|
|
148
|
+
elif isinstance(value, bool):
|
|
149
|
+
# For booleans, return length of string representation
|
|
150
|
+
length_value = len(str(value))
|
|
151
|
+
else:
|
|
152
|
+
# For other types, try to get length or return 0
|
|
153
|
+
try:
|
|
154
|
+
length_value = len(value)
|
|
155
|
+
except TypeError:
|
|
156
|
+
# If object doesn't support len(), return 0
|
|
157
|
+
length_value = 0
|
|
158
|
+
|
|
159
|
+
# If append_field is specified, add to record and return original value
|
|
160
|
+
if append_field:
|
|
161
|
+
append_to_result(record, append_field, length_value)
|
|
162
|
+
return value
|
|
163
|
+
else:
|
|
164
|
+
# Return the length value directly
|
|
165
|
+
return length_value
|
tql/opensearch.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""OpenSearch backend for TQL.
|
|
2
|
+
|
|
3
|
+
This module provides OpenSearch integration for TQL, converting TQL queries
|
|
4
|
+
to OpenSearch Query DSL with intelligent field selection based on operators.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, Optional, Union
|
|
8
|
+
|
|
9
|
+
from .opensearch_components import FieldMapping, LuceneConverter, QueryConverter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenSearchBackend:
|
|
13
|
+
"""OpenSearch backend for TQL query conversion with intelligent field selection."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, field_mappings: Optional[Dict[str, Union[str, Dict[str, Any]]]] = None):
|
|
16
|
+
"""Initialize the OpenSearch backend.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
field_mappings: Field mappings in two formats:
|
|
20
|
+
1. Simple: {"field_name": "target_field_name"}
|
|
21
|
+
2. Intelligent: {"field_name": {"field_name": "keyword", "field_name.text": "text"}}
|
|
22
|
+
"""
|
|
23
|
+
self.field_mappings = field_mappings or {}
|
|
24
|
+
self.intelligent_mappings = {}
|
|
25
|
+
self.simple_mappings = {}
|
|
26
|
+
|
|
27
|
+
# Parse field mappings
|
|
28
|
+
for field_name, mapping in self.field_mappings.items():
|
|
29
|
+
if isinstance(mapping, dict):
|
|
30
|
+
# Check if this is an OpenSearch-style mapping with "type" and "fields"
|
|
31
|
+
if "type" in mapping and not any(k for k in mapping.keys() if k not in ["type", "fields", "analyzer"]):
|
|
32
|
+
# This is an OpenSearch-style mapping for a single field
|
|
33
|
+
field_mapping = FieldMapping(mapping)
|
|
34
|
+
field_mapping.set_base_field_name(field_name)
|
|
35
|
+
self.intelligent_mappings[field_name] = field_mapping
|
|
36
|
+
else:
|
|
37
|
+
# Traditional intelligent mapping with multiple field variants
|
|
38
|
+
self.intelligent_mappings[field_name] = FieldMapping(mapping)
|
|
39
|
+
elif isinstance(mapping, str):
|
|
40
|
+
# Check if this looks like a type specification (common OpenSearch types)
|
|
41
|
+
if mapping in [
|
|
42
|
+
"keyword",
|
|
43
|
+
"text",
|
|
44
|
+
"long",
|
|
45
|
+
"integer",
|
|
46
|
+
"short",
|
|
47
|
+
"byte",
|
|
48
|
+
"double",
|
|
49
|
+
"float",
|
|
50
|
+
"boolean",
|
|
51
|
+
"date",
|
|
52
|
+
"ip",
|
|
53
|
+
]:
|
|
54
|
+
# This is a type specification, create an intelligent mapping
|
|
55
|
+
self.intelligent_mappings[field_name] = FieldMapping({field_name: mapping})
|
|
56
|
+
else:
|
|
57
|
+
# Simple field name mapping (backward compatibility)
|
|
58
|
+
self.simple_mappings[field_name] = mapping
|
|
59
|
+
|
|
60
|
+
# Initialize converters
|
|
61
|
+
self.query_converter = QueryConverter(self.intelligent_mappings, self.simple_mappings)
|
|
62
|
+
self.lucene_converter = LuceneConverter(self.intelligent_mappings, self.simple_mappings)
|
|
63
|
+
|
|
64
|
+
def convert(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
65
|
+
"""Convert a TQL AST to OpenSearch Query DSL.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
ast: The parsed TQL query AST
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
OpenSearch Query DSL as a dictionary
|
|
72
|
+
"""
|
|
73
|
+
query_dsl = {"query": self.query_converter.convert_node(ast)}
|
|
74
|
+
return query_dsl
|
|
75
|
+
|
|
76
|
+
def convert_lucene(self, ast: Dict[str, Any]) -> str:
|
|
77
|
+
"""Convert a TQL AST to Lucene query string."""
|
|
78
|
+
return self.lucene_converter.convert_lucene(ast)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# OpenSearch Components
|
|
2
|
+
|
|
3
|
+
This package contains the modular components for OpenSearch backend integration.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The OpenSearch components package provides intelligent query conversion from TQL to OpenSearch Query DSL with field-aware optimizations.
|
|
8
|
+
|
|
9
|
+
### Components
|
|
10
|
+
|
|
11
|
+
#### `field_mapping.py` - Field Mapping Logic
|
|
12
|
+
Manages intelligent field selection based on field types and operators:
|
|
13
|
+
- Supports multiple mapping formats (simple, intelligent, OpenSearch-style)
|
|
14
|
+
- Automatic field variant selection (keyword vs text fields)
|
|
15
|
+
- Operator compatibility validation
|
|
16
|
+
- Analyzer-aware field selection
|
|
17
|
+
|
|
18
|
+
**Key Classes:**
|
|
19
|
+
- `FieldMapping` - Represents field mapping configuration
|
|
20
|
+
|
|
21
|
+
**Key Methods:**
|
|
22
|
+
- `get_field_for_operator()` - Select optimal field variant for operator
|
|
23
|
+
- `validate_operator_for_field_type()` - Check operator/field compatibility
|
|
24
|
+
- `needs_wildcard_conversion()` - Determine if wildcard conversion needed
|
|
25
|
+
|
|
26
|
+
#### `query_converter.py` - Query Conversion
|
|
27
|
+
Converts TQL AST to OpenSearch Query DSL:
|
|
28
|
+
- Handles all TQL operators and expressions
|
|
29
|
+
- Intelligent query generation based on field types
|
|
30
|
+
- Post-processing detection for complex operations
|
|
31
|
+
- Special handling for array operators (ANY, ALL)
|
|
32
|
+
|
|
33
|
+
**Key Classes:**
|
|
34
|
+
- `QueryConverter` - Main conversion engine
|
|
35
|
+
|
|
36
|
+
**Key Methods:**
|
|
37
|
+
- `convert_node()` - Convert AST node to OpenSearch query
|
|
38
|
+
- `_convert_comparison()` - Handle comparison operations
|
|
39
|
+
- `_convert_logical_op()` - Handle AND/OR operations
|
|
40
|
+
- `_convert_geo_expr()` - Handle geo expressions
|
|
41
|
+
|
|
42
|
+
#### `lucene_converter.py` - Lucene Query Conversion
|
|
43
|
+
Converts TQL AST to Lucene query strings:
|
|
44
|
+
- Alternative query format for Lucene-based systems
|
|
45
|
+
- Proper escaping of special characters
|
|
46
|
+
- Support for all TQL operators
|
|
47
|
+
- Field name resolution
|
|
48
|
+
|
|
49
|
+
**Key Classes:**
|
|
50
|
+
- `LuceneConverter` - Lucene string converter
|
|
51
|
+
|
|
52
|
+
**Key Methods:**
|
|
53
|
+
- `convert_lucene()` - Convert AST to Lucene query string
|
|
54
|
+
- `_escape_lucene_value()` - Escape special characters
|
|
55
|
+
|
|
56
|
+
## Field Mapping Formats
|
|
57
|
+
|
|
58
|
+
### Simple Mapping
|
|
59
|
+
```python
|
|
60
|
+
field_mappings = {
|
|
61
|
+
"src_ip": "source.ip", # Simple rename
|
|
62
|
+
"status": "event.status"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Type Specification
|
|
67
|
+
```python
|
|
68
|
+
field_mappings = {
|
|
69
|
+
"ip_address": "ip", # Field type
|
|
70
|
+
"user_id": "keyword",
|
|
71
|
+
"score": "float"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Intelligent Mapping
|
|
76
|
+
```python
|
|
77
|
+
field_mappings = {
|
|
78
|
+
"message": {
|
|
79
|
+
"message": "keyword",
|
|
80
|
+
"message.text": {"type": "text", "analyzer": "standard"},
|
|
81
|
+
"message.english": {"type": "text", "analyzer": "english"}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### OpenSearch-Style Mapping
|
|
87
|
+
```python
|
|
88
|
+
field_mappings = {
|
|
89
|
+
"title": {
|
|
90
|
+
"type": "text",
|
|
91
|
+
"fields": {
|
|
92
|
+
"keyword": {"type": "keyword"},
|
|
93
|
+
"autocomplete": {"type": "text", "analyzer": "autocomplete"}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Operator Field Selection
|
|
100
|
+
|
|
101
|
+
The system automatically selects the appropriate field variant based on the operator:
|
|
102
|
+
|
|
103
|
+
| Operator Type | Preferred Field | Example |
|
|
104
|
+
|--------------|-----------------|---------|
|
|
105
|
+
| Exact match (=, !=) | keyword | `title.keyword` |
|
|
106
|
+
| Text search (contains) | text | `title` or `title.text` |
|
|
107
|
+
| Wildcard (startswith) | keyword | `title.keyword` |
|
|
108
|
+
| Range (>, <) | numeric/keyword | `price` or `timestamp` |
|
|
109
|
+
| CIDR | ip/keyword | `source.ip` |
|
|
110
|
+
|
|
111
|
+
## Usage
|
|
112
|
+
|
|
113
|
+
Used internally by `OpenSearchBackend`:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from tql import TQL
|
|
117
|
+
|
|
118
|
+
tql = TQL(field_mappings={
|
|
119
|
+
"message": {
|
|
120
|
+
"message": "keyword",
|
|
121
|
+
"message.text": "text"
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
# Automatically uses message.text for text search
|
|
126
|
+
query = tql.to_opensearch("message contains 'error'")
|
|
127
|
+
|
|
128
|
+
# Automatically uses message (keyword) for exact match
|
|
129
|
+
query = tql.to_opensearch("message = 'ERROR: Failed'")
|
|
130
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""OpenSearch backend components for TQL.
|
|
2
|
+
|
|
3
|
+
This package contains modular components for OpenSearch integration:
|
|
4
|
+
- field_mapping: Field mapping and intelligent field selection
|
|
5
|
+
- query_converter: TQL AST to OpenSearch Query DSL conversion
|
|
6
|
+
- lucene_converter: TQL AST to Lucene query string conversion
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .field_mapping import FieldMapping
|
|
10
|
+
from .lucene_converter import LuceneConverter
|
|
11
|
+
from .query_converter import QueryConverter
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"FieldMapping",
|
|
15
|
+
"QueryConverter",
|
|
16
|
+
"LuceneConverter",
|
|
17
|
+
]
|