tellaro-query-language 0.1.4__py3-none-any.whl → 0.1.5__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.
@@ -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.5
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
@@ -9,7 +9,7 @@ tql/core_components/README.md,sha256=Rm7w4UHdQ0vPBEFybE5b62IOvSA5Nzq2GRvtBHOapmc
9
9
  tql/core_components/__init__.py,sha256=v8BBybPlqV7dkVY9mw1mblvqyAFJZ7Pf_bEc-jAL7FI,643
10
10
  tql/core_components/file_operations.py,sha256=Jr0kkxz_OP2KHOAsIr7KMtYe_lbu8LuBUySt2LQbjJw,3925
11
11
  tql/core_components/opensearch_operations.py,sha256=x0GPdQfMX-NYCzzAlJCbtzYFa6HmaKxGEVl2JdAhKJY,35447
12
- tql/core_components/stats_operations.py,sha256=jNk7cQZLwwsIoN__hdFHME8e17AjDHYLvZ-5kwPxNUY,6853
12
+ tql/core_components/stats_operations.py,sha256=9WSEMdAvtZA6deckGwyetPAtnDODFqlnrhvlBbNkBcs,7469
13
13
  tql/core_components/validation_operations.py,sha256=_VPXh0HABBjsXF99jFT7B6-5QAPsADOCy6poinGrxeE,22454
14
14
  tql/evaluator.py,sha256=YdgS1vuxUEPAHhUsZey-Y4NydeS8CTYOy_O8R5_K8cE,15421
15
15
  tql/evaluator_components/README.md,sha256=c59yf2au34yPhrru7JWgGop_ORteB6w5vfMhsac8j3k,3882
@@ -37,20 +37,20 @@ tql/opensearch_components/lucene_converter.py,sha256=ZbupWZ2smGhWfE9cSIrNo7MZVY3
37
37
  tql/opensearch_components/query_converter.py,sha256=9Nkhehb8X7jIQcyRaHCa5WCKWtovdRGKuSdDsBulrG0,36722
38
38
  tql/opensearch_mappings.py,sha256=zJCCdMrxK7mswrkxd5LiOhunQ9GIJNZdhktVoGXgVgk,11529
39
39
  tql/opensearch_stats.py,sha256=svcE23lD1gDe1iYGlC388WE8X3x9C8tjfBylUVy2Uqo,17216
40
- tql/parser.py,sha256=6WbCpMuYOJymSYHEgTFS7yTgCOPXsCmGsi9m_u5oQzc,73954
40
+ tql/parser.py,sha256=dnjgc-sDVihe-VIVPT_SRULeng4OWaLtkbM23dluT6M,75532
41
41
  tql/parser_components/README.md,sha256=lvQX72ckq2zyotGs8QIHHCIFqaA7bOHwkP44wU8Zoiw,2322
42
42
  tql/parser_components/__init__.py,sha256=zBwHBMPJyHSBbaOojf6qTrJYjJg5A6tPUE8nHFdRiQs,521
43
43
  tql/parser_components/ast_builder.py,sha256=-pbcYhZNoRm0AnjmJRAAlXLCAwHfauchTpX_6KO0plE,6793
44
44
  tql/parser_components/error_analyzer.py,sha256=qlCD9vKyW73aeKQYI33P1OjIWSJ3LPd08wuN9cis2fU,4012
45
45
  tql/parser_components/field_extractor.py,sha256=TumeuUo2c5gPYVbTPsmU43C3TJFC8chAAWERu5v_Q3c,4182
46
- tql/parser_components/grammar.py,sha256=bfThefrhdbCMPLWb9ZXwZDXx8ZW8_PXihjwCL-P3Gi8,18669
46
+ tql/parser_components/grammar.py,sha256=WtmgANV49Jx1uHNBeiyElpXqKy3feQOWj9aYdxGn3Ww,19182
47
47
  tql/post_processor.py,sha256=-vA2wgbuLij2FVnj5I9HDHtw5bKj9Cu3EE9mtoeSWk8,28859
48
48
  tql/scripts.py,sha256=VOr5vCjIvKlW36kwvJx7JGFIRM16IJZlbJcWlBexBtk,3728
49
- tql/stats_evaluator.py,sha256=7ymCebPOepY3QKHGz0QEhkTmYYHjck648rrI8cWhhDI,15158
49
+ tql/stats_evaluator.py,sha256=GtmygLdPPMhAMR5bN1h69cQsNBYqg-jQl6bN5rwEx6Q,15692
50
50
  tql/stats_transformer.py,sha256=MT-4rDWZSySgn4Fuq9H0c-mvwFYLM6FqWpPv2rHX-rE,7588
51
51
  tql/validators.py,sha256=e9MlX-zQ_O3M8YP8vXyMjKU8iiJMTh6mMK0iv0_4gTY,3771
52
- tellaro_query_language-0.1.4.dist-info/LICENSE,sha256=zRhQ85LnW55fWgAjQctckwQ67DX5Jmt64lq343ThZFU,1063
53
- tellaro_query_language-0.1.4.dist-info/METADATA,sha256=vpS-ejEu2AU7SSXTML7q0x9jFwm3zZo13MwfLQxCPyI,15109
54
- tellaro_query_language-0.1.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
55
- tellaro_query_language-0.1.4.dist-info/entry_points.txt,sha256=H43APfGBMsZkKsUCnFTaqprQPW-Kce2yz2qsBL3dZrw,164
56
- tellaro_query_language-0.1.4.dist-info/RECORD,,
52
+ tellaro_query_language-0.1.5.dist-info/LICENSE,sha256=zRhQ85LnW55fWgAjQctckwQ67DX5Jmt64lq343ThZFU,1063
53
+ tellaro_query_language-0.1.5.dist-info/METADATA,sha256=QPSlAjalj-9Q3VWOBx2DVcQwogyR3e6OlI7vHk_2sxw,15109
54
+ tellaro_query_language-0.1.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
55
+ tellaro_query_language-0.1.5.dist-info/entry_points.txt,sha256=H43APfGBMsZkKsUCnFTaqprQPW-Kce2yz2qsBL3dZrw,164
56
+ tellaro_query_language-0.1.5.dist-info/RECORD,,
@@ -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":
tql/parser.py CHANGED
@@ -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
  )
tql/stats_evaluator.py CHANGED
@@ -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