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.
Files changed (56) hide show
  1. tellaro_query_language-0.1.0.dist-info/LICENSE +21 -0
  2. tellaro_query_language-0.1.0.dist-info/METADATA +401 -0
  3. tellaro_query_language-0.1.0.dist-info/RECORD +56 -0
  4. tellaro_query_language-0.1.0.dist-info/WHEEL +4 -0
  5. tellaro_query_language-0.1.0.dist-info/entry_points.txt +7 -0
  6. tql/__init__.py +47 -0
  7. tql/analyzer.py +385 -0
  8. tql/cache/__init__.py +7 -0
  9. tql/cache/base.py +25 -0
  10. tql/cache/memory.py +63 -0
  11. tql/cache/redis.py +68 -0
  12. tql/core.py +929 -0
  13. tql/core_components/README.md +92 -0
  14. tql/core_components/__init__.py +20 -0
  15. tql/core_components/file_operations.py +113 -0
  16. tql/core_components/opensearch_operations.py +869 -0
  17. tql/core_components/stats_operations.py +200 -0
  18. tql/core_components/validation_operations.py +599 -0
  19. tql/evaluator.py +379 -0
  20. tql/evaluator_components/README.md +131 -0
  21. tql/evaluator_components/__init__.py +17 -0
  22. tql/evaluator_components/field_access.py +176 -0
  23. tql/evaluator_components/special_expressions.py +296 -0
  24. tql/evaluator_components/value_comparison.py +315 -0
  25. tql/exceptions.py +160 -0
  26. tql/geoip_normalizer.py +233 -0
  27. tql/mutator_analyzer.py +830 -0
  28. tql/mutators/__init__.py +222 -0
  29. tql/mutators/base.py +78 -0
  30. tql/mutators/dns.py +316 -0
  31. tql/mutators/encoding.py +218 -0
  32. tql/mutators/geo.py +363 -0
  33. tql/mutators/list.py +212 -0
  34. tql/mutators/network.py +163 -0
  35. tql/mutators/security.py +225 -0
  36. tql/mutators/string.py +165 -0
  37. tql/opensearch.py +78 -0
  38. tql/opensearch_components/README.md +130 -0
  39. tql/opensearch_components/__init__.py +17 -0
  40. tql/opensearch_components/field_mapping.py +399 -0
  41. tql/opensearch_components/lucene_converter.py +305 -0
  42. tql/opensearch_components/query_converter.py +775 -0
  43. tql/opensearch_mappings.py +309 -0
  44. tql/opensearch_stats.py +451 -0
  45. tql/parser.py +1363 -0
  46. tql/parser_components/README.md +72 -0
  47. tql/parser_components/__init__.py +20 -0
  48. tql/parser_components/ast_builder.py +162 -0
  49. tql/parser_components/error_analyzer.py +101 -0
  50. tql/parser_components/field_extractor.py +112 -0
  51. tql/parser_components/grammar.py +473 -0
  52. tql/post_processor.py +737 -0
  53. tql/scripts.py +124 -0
  54. tql/stats_evaluator.py +444 -0
  55. tql/stats_transformer.py +184 -0
  56. 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
+ ]