tellaro-query-language 0.1.4__tar.gz → 0.1.6__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.4 → tellaro_query_language-0.1.6}/PKG-INFO +1 -1
  2. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/pyproject.toml +1 -1
  3. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/core_components/stats_operations.py +19 -7
  4. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/parser.py +32 -2
  5. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/parser_components/grammar.py +14 -4
  6. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/stats_evaluator.py +12 -1
  7. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/LICENSE +0 -0
  8. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/README.md +0 -0
  9. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/__init__.py +0 -0
  10. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/analyzer.py +0 -0
  11. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/cache/__init__.py +0 -0
  12. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/cache/base.py +0 -0
  13. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/cache/memory.py +0 -0
  14. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/cache/redis.py +0 -0
  15. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/core.py +0 -0
  16. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/core_components/README.md +0 -0
  17. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/core_components/__init__.py +0 -0
  18. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/core_components/file_operations.py +0 -0
  19. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/core_components/opensearch_operations.py +0 -0
  20. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/core_components/validation_operations.py +0 -0
  21. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/evaluator.py +0 -0
  22. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/evaluator_components/README.md +0 -0
  23. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/evaluator_components/__init__.py +0 -0
  24. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/evaluator_components/field_access.py +0 -0
  25. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/evaluator_components/special_expressions.py +0 -0
  26. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/evaluator_components/value_comparison.py +0 -0
  27. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/exceptions.py +0 -0
  28. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/geoip_normalizer.py +0 -0
  29. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutator_analyzer.py +0 -0
  30. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/__init__.py +0 -0
  31. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/base.py +0 -0
  32. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/dns.py +0 -0
  33. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/encoding.py +0 -0
  34. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/geo.py +0 -0
  35. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/list.py +0 -0
  36. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/network.py +0 -0
  37. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/security.py +0 -0
  38. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/mutators/string.py +0 -0
  39. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/opensearch.py +0 -0
  40. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/opensearch_components/README.md +0 -0
  41. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/opensearch_components/__init__.py +0 -0
  42. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/opensearch_components/field_mapping.py +0 -0
  43. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/opensearch_components/lucene_converter.py +0 -0
  44. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/opensearch_components/query_converter.py +0 -0
  45. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/opensearch_mappings.py +0 -0
  46. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/opensearch_stats.py +0 -0
  47. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/parser_components/README.md +0 -0
  48. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/parser_components/__init__.py +0 -0
  49. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/parser_components/ast_builder.py +0 -0
  50. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/parser_components/error_analyzer.py +0 -0
  51. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/parser_components/field_extractor.py +0 -0
  52. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/post_processor.py +0 -0
  53. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/scripts.py +0 -0
  54. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/src/tql/stats_transformer.py +0 -0
  55. {tellaro_query_language-0.1.4 → tellaro_query_language-0.1.6}/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.4
3
+ Version: 0.1.6
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.4"
3
+ version = "0.1.6"
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"
@@ -38,15 +38,27 @@ class StatsOperations:
38
38
  TQLParseError: If query parsing fails
39
39
  TQLValueError: If query is invalid
40
40
  """
41
- # Ensure the query starts with | stats
42
- if not stats_query.strip().startswith("| stats"):
43
- stats_query = "| stats " + stats_query.strip()
44
-
45
- # Parse the stats expression
41
+ # First try to parse as-is
46
42
  try:
47
43
  parsed = self.parser.parse(stats_query)
48
- except TQLParseError as e:
49
- raise TQLParseError(f"Invalid stats query: {str(e)}")
44
+ # If it's already a stats expression, use it
45
+ if parsed.get("type") == "stats_expr":
46
+ # Already a valid stats expression, proceed
47
+ pass
48
+ else:
49
+ # Not a stats expression, try adding | stats prefix
50
+ stats_query_with_pipe = "| stats " + stats_query.strip()
51
+ parsed = self.parser.parse(stats_query_with_pipe)
52
+ except TQLParseError:
53
+ # If parsing failed, try with | stats prefix
54
+ if not stats_query.strip().startswith("| stats"):
55
+ stats_query_with_pipe = "| stats " + stats_query.strip()
56
+ try:
57
+ parsed = self.parser.parse(stats_query_with_pipe)
58
+ except TQLParseError as e:
59
+ raise TQLParseError(f"Invalid stats query: {str(e)}")
60
+ else:
61
+ raise
50
62
 
51
63
  # Verify it's a stats expression
52
64
  if parsed.get("type") != "stats_expr":
@@ -123,6 +123,10 @@ class TQLParser:
123
123
  # Single item, check if it's a field with is_private/is_global mutator
124
124
  item = parsed[0]
125
125
  if isinstance(item, list):
126
+ # Check if this is a stats expression (starts with 'stats')
127
+ if len(item) >= 2 and isinstance(item[0], str) and item[0].lower() == "stats":
128
+ # This is a stats expression without filter
129
+ return self._build_stats_ast(item)
126
130
  # Could be a typed_field
127
131
  field_name, type_hint, field_mutators = self.ast_builder.extract_field_info(item)
128
132
  if field_mutators:
@@ -1219,8 +1223,23 @@ class TQLParser:
1219
1223
  result["aggregations"].append({"function": "count", "field": "*", "alias": None})
1220
1224
  i += 1
1221
1225
  elif isinstance(parsed[i], list):
1222
- # This is a list of aggregations
1223
- for item in parsed[i]:
1226
+ # Check if this is a single aggregation with alias pattern
1227
+ # Pattern: [['func', 'field'], 'as', 'alias'] or ['count', 'as', 'alias']
1228
+ if (
1229
+ len(parsed[i]) >= 3
1230
+ and isinstance(parsed[i][1], str)
1231
+ and parsed[i][1].lower() == "as"
1232
+ and (
1233
+ isinstance(parsed[i][0], list)
1234
+ or (isinstance(parsed[i][0], str) and parsed[i][0].lower() == "count")
1235
+ )
1236
+ ):
1237
+ # This is a single aggregation with alias
1238
+ items_to_process = [parsed[i]]
1239
+ else:
1240
+ # This is a list of aggregations
1241
+ items_to_process = parsed[i]
1242
+ for item in items_to_process:
1224
1243
  agg_dict: Dict[str, Any] = {}
1225
1244
 
1226
1245
  if isinstance(item, str) and item.lower() == "count":
@@ -1228,6 +1247,17 @@ class TQLParser:
1228
1247
  agg_dict["function"] = "count"
1229
1248
  agg_dict["field"] = "*"
1230
1249
  agg_dict["alias"] = None
1250
+ elif (
1251
+ isinstance(item, list)
1252
+ and len(item) >= 3
1253
+ and isinstance(item[0], str)
1254
+ and item[0].lower() == "count"
1255
+ and item[1].lower() == "as"
1256
+ ):
1257
+ # count(*) with alias: ['count', 'as', 'alias']
1258
+ agg_dict["function"] = "count"
1259
+ agg_dict["field"] = "*"
1260
+ agg_dict["alias"] = item[2]
1231
1261
  elif isinstance(item, list):
1232
1262
  # Regular aggregation: [func, field, ...] or [[func, field], 'as', 'alias']
1233
1263
  if len(item) >= 2 and isinstance(item[0], list):
@@ -375,7 +375,8 @@ class TQLGrammar:
375
375
  # Aggregation function names - including aliases
376
376
  self.agg_function_name = oneOf(
377
377
  "count unique_count sum min max average avg median med std standard_deviation "
378
- "percentile percentiles p pct percentile_rank percentile_ranks pct_rank pct_ranks",
378
+ "percentile percentiles p pct percentile_rank percentile_ranks pct_rank pct_ranks "
379
+ "values unique cardinality",
379
380
  caseless=True,
380
381
  )
381
382
 
@@ -407,10 +408,18 @@ class TQLGrammar:
407
408
  self.group_by_fields = delimitedList(self.field_name)
408
409
 
409
410
  # Complete stats expression: | stats agg_functions [by group_fields]
410
- self.stats_expr = Group(
411
+ self.stats_expr_with_pipe = Group(
411
412
  Suppress("|") + self.stats_kw + self.agg_list + PyparsingOptional(self.by_kw + self.group_by_fields)
412
413
  )
413
414
 
415
+ # Stats expression without pipe (for standalone use)
416
+ self.stats_expr_no_pipe = Group(
417
+ self.stats_kw + self.agg_list + PyparsingOptional(self.by_kw + self.group_by_fields)
418
+ )
419
+
420
+ # Combined stats expression (with or without pipe)
421
+ self.stats_expr = self.stats_expr_with_pipe | self.stats_expr_no_pipe
422
+
414
423
  def _setup_final_expressions(self):
415
424
  """Set up final expression definitions."""
416
425
  # Define all forms of comparison
@@ -447,8 +456,9 @@ class TQLGrammar:
447
456
  self.tql_expr << (
448
457
  # filter | stats
449
458
  (
450
- Group(self.filter_expr + self.stats_expr) # filter | stats
451
- | self.stats_expr # just stats (applies to all records)
459
+ self.stats_expr_no_pipe # just stats without pipe (applies to all records)
460
+ | Group(self.filter_expr + self.stats_expr_with_pipe) # filter | stats
461
+ | self.stats_expr # just stats with pipe (applies to all records)
452
462
  | self.filter_expr
453
463
  ) # just filter (no stats)
454
464
  )
@@ -40,7 +40,7 @@ class TQLStatsEvaluator:
40
40
  }
41
41
 
42
42
  # Aggregation functions that work with any field type
43
- ANY_TYPE_AGGREGATIONS = {"count", "unique_count"}
43
+ ANY_TYPE_AGGREGATIONS = {"count", "unique_count", "values", "unique", "cardinality"}
44
44
 
45
45
  # Numeric types supported by OpenSearch
46
46
  NUMERIC_TYPES = {
@@ -281,6 +281,17 @@ class TQLStatsEvaluator:
281
281
  for v in rank_values:
282
282
  result[f"rank_{v}"] = self._calculate_percentile_rank(numeric_values, v)
283
283
  return result
284
+ elif func in ["values", "unique", "cardinality"]:
285
+ # Return unique values from the field
286
+ unique_values = list(set(values)) if values else []
287
+ # Sort the values for consistent output
288
+ try:
289
+ # Try to sort if values are comparable
290
+ unique_values.sort()
291
+ except TypeError:
292
+ # If values aren't comparable (mixed types), just return unsorted
293
+ pass
294
+ return unique_values
284
295
  else:
285
296
  raise TQLError(f"Unsupported aggregation function: {func}")
286
297