tellaro-query-language 0.1.8__tar.gz → 0.2.0__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.
Files changed (55) hide show
  1. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/PKG-INFO +1 -1
  2. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/pyproject.toml +1 -1
  3. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/core.py +26 -3
  4. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/core_components/opensearch_operations.py +13 -6
  5. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/evaluator.py +4 -1
  6. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/opensearch_components/lucene_converter.py +4 -1
  7. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/opensearch_components/query_converter.py +4 -1
  8. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/parser.py +5 -0
  9. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/LICENSE +0 -0
  10. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/README.md +0 -0
  11. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/__init__.py +0 -0
  12. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/analyzer.py +0 -0
  13. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/cache/__init__.py +0 -0
  14. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/cache/base.py +0 -0
  15. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/cache/memory.py +0 -0
  16. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/cache/redis.py +0 -0
  17. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/core_components/README.md +0 -0
  18. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/core_components/__init__.py +0 -0
  19. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/core_components/file_operations.py +0 -0
  20. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/core_components/stats_operations.py +0 -0
  21. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/core_components/validation_operations.py +0 -0
  22. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/evaluator_components/README.md +0 -0
  23. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/evaluator_components/__init__.py +0 -0
  24. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/evaluator_components/field_access.py +0 -0
  25. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/evaluator_components/special_expressions.py +0 -0
  26. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/evaluator_components/value_comparison.py +0 -0
  27. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/exceptions.py +0 -0
  28. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/geoip_normalizer.py +0 -0
  29. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutator_analyzer.py +0 -0
  30. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/__init__.py +0 -0
  31. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/base.py +0 -0
  32. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/dns.py +0 -0
  33. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/encoding.py +0 -0
  34. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/geo.py +0 -0
  35. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/list.py +0 -0
  36. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/network.py +0 -0
  37. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/security.py +0 -0
  38. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/mutators/string.py +0 -0
  39. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/opensearch.py +0 -0
  40. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/opensearch_components/README.md +0 -0
  41. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/opensearch_components/__init__.py +0 -0
  42. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/opensearch_components/field_mapping.py +0 -0
  43. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/opensearch_mappings.py +0 -0
  44. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/opensearch_stats.py +0 -0
  45. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/parser_components/README.md +0 -0
  46. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/parser_components/__init__.py +0 -0
  47. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/parser_components/ast_builder.py +0 -0
  48. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/parser_components/error_analyzer.py +0 -0
  49. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/parser_components/field_extractor.py +0 -0
  50. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/parser_components/grammar.py +0 -0
  51. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/post_processor.py +0 -0
  52. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/scripts.py +0 -0
  53. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/stats_evaluator.py +0 -0
  54. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/stats_transformer.py +0 -0
  55. {tellaro_query_language-0.1.8 → tellaro_query_language-0.2.0}/src/tql/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tellaro-query-language
3
- Version: 0.1.8
3
+ Version: 0.2.0
4
4
  Summary: A flexible, human-friendly query language for searching and filtering structured data
5
5
  Home-page: https://github.com/tellaro/tellaro-query-language
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "tellaro-query-language"
3
- version = "0.1.8"
3
+ version = "0.2.0"
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 = "MIT"
@@ -450,7 +450,8 @@ class TQL:
450
450
  index: Optional[str] = None,
451
451
  query: Optional[str] = None,
452
452
  size: int = 500,
453
- from_: int = 0,
453
+ search_after: Optional[List[Any]] = None,
454
+ sort: Optional[List[Dict[str, Any]]] = None,
454
455
  timestamp_field: str = "@timestamp",
455
456
  time_range: Optional[Dict[str, str]] = None,
456
457
  scan_all: bool = False,
@@ -468,7 +469,8 @@ class TQL:
468
469
  index: Index name to search
469
470
  query: The TQL query string
470
471
  size: Number of results to return (default: 500)
471
- from_: Starting offset for pagination (default: 0)
472
+ search_after: Values from previous result's sort field for pagination
473
+ sort: Sort order specification (e.g., [{"@timestamp": "desc"}, {"_id": "asc"}])
472
474
  timestamp_field: Field name for timestamp filtering (default: "@timestamp")
473
475
  time_range: Optional time range dict with 'gte' and/or 'lte' keys
474
476
  scan_all: If True, use scroll API to retrieve all matching documents
@@ -480,6 +482,7 @@ class TQL:
480
482
  Dictionary containing:
481
483
  - results: List of processed results
482
484
  - total: Total number of matching documents
485
+ - sort_values: Sort values of the last document (for search_after pagination)
483
486
  - post_processing_applied: Whether post-processing was applied
484
487
  - health_status: Query health status
485
488
  - health_reasons: List of health issues
@@ -503,6 +506,24 @@ class TQL:
503
506
  # Remove parameters that the new implementation doesn't understand
504
507
  filtered_kwargs = {k: v for k, v in kwargs.items() if k not in ["save_enrichment", "opensearch_client"]}
505
508
 
509
+ # Check for backward compatibility - if from_ is in kwargs, it's old usage
510
+ if "from_" in kwargs:
511
+ # Old style pagination - convert to search_after with default sort
512
+ from_ = kwargs.pop("from_")
513
+ if from_ > 0 and not sort:
514
+ # Need a sort for proper pagination
515
+ sort = [{"_id": "asc"}]
516
+ # Note: We can't directly convert from_ to search_after without previous results
517
+ # This is a limitation of moving to search_after
518
+ if from_ > 0:
519
+ import warnings
520
+
521
+ warnings.warn(
522
+ "from_ parameter is deprecated. Use search_after with sort for efficient pagination.",
523
+ DeprecationWarning,
524
+ stacklevel=2,
525
+ )
526
+
506
527
  # Add the supported parameters
507
528
  filtered_kwargs.update(
508
529
  {
@@ -511,6 +532,8 @@ class TQL:
511
532
  "scan_all": scan_all,
512
533
  "scroll_size": scroll_size,
513
534
  "scroll_timeout": scroll_timeout,
535
+ "search_after": search_after,
536
+ "sort": sort,
514
537
  }
515
538
  )
516
539
 
@@ -519,7 +542,7 @@ class TQL:
519
542
  filtered_kwargs["client"] = opensearch_client
520
543
 
521
544
  # Execute using new implementation
522
- results = self.opensearch_ops.execute_opensearch(query, index=index, size=size, from_=from_, **filtered_kwargs)
545
+ results = self.opensearch_ops.execute_opensearch(query, index=index, size=size, **filtered_kwargs)
523
546
 
524
547
  # Convert to old format if needed
525
548
  if isinstance(results, list):
@@ -141,7 +141,7 @@ class OpenSearchOperations:
141
141
  query: str,
142
142
  index: Optional[str] = None,
143
143
  size: int = 10000,
144
- from_: int = 0,
144
+ search_after: Optional[List[Any]] = None,
145
145
  sort: Optional[List[Dict[str, Any]]] = None,
146
146
  source_includes: Optional[List[str]] = None,
147
147
  source_excludes: Optional[List[str]] = None,
@@ -175,8 +175,8 @@ class OpenSearchOperations:
175
175
  query: TQL query string
176
176
  index: OpenSearch index name (uses environment variable if not provided)
177
177
  size: Maximum number of results to return (default: 10000)
178
- from_: Offset for pagination (default: 0)
179
- sort: List of sort specifications
178
+ search_after: Values from previous result for pagination
179
+ sort: List of sort specifications (required for search_after)
180
180
  source_includes: Fields to include in response
181
181
  source_excludes: Fields to exclude from response
182
182
  track_total_hits: Whether to track total hit count
@@ -322,11 +322,13 @@ class OpenSearchOperations:
322
322
  else:
323
323
  search_body["query"] = time_filter
324
324
 
325
- search_body.update({"size": size, "from": from_, "track_total_hits": track_total_hits})
325
+ search_body.update({"size": size, "track_total_hits": track_total_hits})
326
326
 
327
327
  # Add optional parameters
328
328
  if sort:
329
329
  search_body["sort"] = sort
330
+ if search_after:
331
+ search_body["search_after"] = search_after
330
332
  if source_includes or source_excludes:
331
333
  search_body["_source"] = {}
332
334
  if source_includes:
@@ -705,11 +707,16 @@ class OpenSearchOperations:
705
707
  if not scan_all:
706
708
  result["pagination"] = {
707
709
  "size": size,
708
- "from": from_,
709
710
  "total": opensearch_total,
710
- "has_more": opensearch_total > (from_ + len(results)),
711
+ "has_more": len(hits) == size, # If we got a full page, there might be more
711
712
  }
712
713
 
714
+ # Add sort values from the last hit for search_after pagination
715
+ if hits and sort:
716
+ last_hit = hits[-1]
717
+ if "sort" in last_hit:
718
+ result["sort_values"] = last_hit["sort"]
719
+
713
720
  return result
714
721
 
715
722
  def _docs_match(self, doc1: Dict[str, Any], doc2: Dict[str, Any]) -> bool:
@@ -81,7 +81,10 @@ class TQLEvaluator:
81
81
  if isinstance(node, dict):
82
82
  node_type = node.get("type")
83
83
 
84
- if node_type == "comparison":
84
+ if node_type == "match_all":
85
+ # Empty query matches all records
86
+ return True
87
+ elif node_type == "comparison":
85
88
  return self._evaluate_comparison(node, record, field_mappings)
86
89
  elif node_type == "logical_op":
87
90
  return self._evaluate_logical_op(node, record, field_mappings)
@@ -31,7 +31,10 @@ class LuceneConverter:
31
31
  if isinstance(node, dict):
32
32
  node_type = node.get("type")
33
33
 
34
- if node_type == "comparison":
34
+ if node_type == "match_all":
35
+ # Empty query matches all documents in Lucene
36
+ return "*:*"
37
+ elif node_type == "comparison":
35
38
  return self._convert_comparison_to_lucene(node)
36
39
  elif node_type == "logical_op":
37
40
  return self._convert_logical_op_to_lucene(node)
@@ -27,7 +27,10 @@ class QueryConverter:
27
27
  if isinstance(node, dict):
28
28
  node_type = node.get("type")
29
29
 
30
- if node_type == "comparison":
30
+ if node_type == "match_all":
31
+ # Empty query matches all documents
32
+ return {"match_all": {}}
33
+ elif node_type == "comparison":
31
34
  return self._convert_comparison(node)
32
35
  elif node_type == "logical_op":
33
36
  return self._convert_logical_op(node)
@@ -43,6 +43,11 @@ class TQLParser:
43
43
  Raises:
44
44
  TQLParseError: If the query has invalid syntax
45
45
  """
46
+ # Handle empty or whitespace-only queries
47
+ if not query or not query.strip():
48
+ # Return a special AST that matches all records
49
+ return {"type": "match_all"}
50
+
46
51
  try:
47
52
  # Parse the query
48
53
  parsed_result = self.grammar.tql_expr.parseString(query, parseAll=True)