tellaro-query-language 0.1.5__py3-none-any.whl → 0.1.7__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.5
3
+ Version: 0.1.7
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
@@ -8,8 +8,8 @@ tql/core.py,sha256=zAkTStN_3logY9ARABDZNsQJfXV5rITiiTKoX5bo274,40859
8
8
  tql/core_components/README.md,sha256=Rm7w4UHdQ0vPBEFybE5b62IOvSA5Nzq2GRvtBHOapmc,3068
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
- tql/core_components/opensearch_operations.py,sha256=x0GPdQfMX-NYCzzAlJCbtzYFa6HmaKxGEVl2JdAhKJY,35447
12
- tql/core_components/stats_operations.py,sha256=9WSEMdAvtZA6deckGwyetPAtnDODFqlnrhvlBbNkBcs,7469
11
+ tql/core_components/opensearch_operations.py,sha256=X9xtVnrC37O-9eSTbdilvi_cjvm0jXHZIPdldM0yM7g,38028
12
+ tql/core_components/stats_operations.py,sha256=zAfDhVOFFPMrRIMw6Qtjxbobbdi7ao_HuHBCcVc3BGY,7579
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
@@ -36,21 +36,21 @@ tql/opensearch_components/field_mapping.py,sha256=N4r7VkzNeXjIhNDt2cfnd1LbkvaS_9
36
36
  tql/opensearch_components/lucene_converter.py,sha256=ZbupWZ2smGhWfE9cSIrNo7MZVY35l2t86HYM-bd7nKw,12436
37
37
  tql/opensearch_components/query_converter.py,sha256=9Nkhehb8X7jIQcyRaHCa5WCKWtovdRGKuSdDsBulrG0,36722
38
38
  tql/opensearch_mappings.py,sha256=zJCCdMrxK7mswrkxd5LiOhunQ9GIJNZdhktVoGXgVgk,11529
39
- tql/opensearch_stats.py,sha256=svcE23lD1gDe1iYGlC388WE8X3x9C8tjfBylUVy2Uqo,17216
39
+ tql/opensearch_stats.py,sha256=03mjvnUJpNtCDpU-E1ZkugDJMIKC-F76Hekd4gppPxU,17812
40
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=WtmgANV49Jx1uHNBeiyElpXqKy3feQOWj9aYdxGn3Ww,19182
46
+ tql/parser_components/grammar.py,sha256=lSvjABvEBaH29-ad-_UGD4WmofdNwC_pO2OKQJ_It-U,19309
47
47
  tql/post_processor.py,sha256=-vA2wgbuLij2FVnj5I9HDHtw5bKj9Cu3EE9mtoeSWk8,28859
48
48
  tql/scripts.py,sha256=VOr5vCjIvKlW36kwvJx7JGFIRM16IJZlbJcWlBexBtk,3728
49
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.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,,
52
+ tellaro_query_language-0.1.7.dist-info/LICENSE,sha256=zRhQ85LnW55fWgAjQctckwQ67DX5Jmt64lq343ThZFU,1063
53
+ tellaro_query_language-0.1.7.dist-info/METADATA,sha256=IDzVPrFm_GQJTRZRX4iLNw45Nqszf3TqBW6ZW6UUKBo,15109
54
+ tellaro_query_language-0.1.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
55
+ tellaro_query_language-0.1.7.dist-info/entry_points.txt,sha256=H43APfGBMsZkKsUCnFTaqprQPW-Kce2yz2qsBL3dZrw,164
56
+ tellaro_query_language-0.1.7.dist-info/RECORD,,
@@ -441,49 +441,93 @@ class OpenSearchOperations:
441
441
  fields = [agg.get("field") for agg in aggregations]
442
442
 
443
443
  stats_results = {
444
- "type": "stats",
444
+ "type": "stats_grouped",
445
445
  "operation": operations[0] if len(operations) == 1 else operations,
446
446
  "field": fields[0] if len(fields) == 1 else fields,
447
- "values": buckets, # Array of buckets for grouped results
447
+ "results": buckets, # Array of buckets for grouped results
448
448
  "group_by": group_by_fields,
449
449
  }
450
450
  else:
451
451
  # Simple aggregations without grouping
452
452
  if aggregations:
453
- first_agg = aggregations[0]
454
- func = first_agg.get("function", "")
455
- field = first_agg.get("field", "*")
456
-
457
- # Get the aggregation result
458
- # The alias is typically func_field_0 for the first aggregation
459
- alias = first_agg.get("alias") or f"{func}_{field}_0"
460
- agg_result = aggs_response.get(alias, {})
461
-
462
- # Extract the value based on aggregation type
463
- if func == "count":
464
- value = agg_result.get("value", 0)
465
- elif func in ["sum", "min", "max", "avg", "average"]:
466
- value = agg_result.get("value", 0)
467
- elif func == "unique_count":
468
- value = agg_result.get("value", 0)
469
- elif func in ["percentile", "percentiles", "p", "pct"]:
470
- # Percentiles return a values dict
471
- values_dict = agg_result.get("values", {})
472
- # For a single percentile, extract the value
473
- if len(values_dict) == 1:
474
- value = list(values_dict.values())[0]
453
+ if len(aggregations) == 1:
454
+ # Single aggregation
455
+ first_agg = aggregations[0]
456
+ func = first_agg.get("function", "")
457
+ field = first_agg.get("field", "*")
458
+
459
+ # Get the aggregation result
460
+ # The alias is typically func_field_0 for the first aggregation
461
+ alias = first_agg.get("alias") or f"{func}_{field}_0"
462
+ agg_result = aggs_response.get(alias, {})
463
+
464
+ # Extract the value based on aggregation type
465
+ if func == "count":
466
+ value = agg_result.get("value", 0)
467
+ elif func in ["sum", "min", "max", "avg", "average"]:
468
+ value = agg_result.get("value", 0)
469
+ elif func == "unique_count":
470
+ value = agg_result.get("value", 0)
471
+ elif func in ["percentile", "percentiles", "p", "pct"]:
472
+ # Percentiles return a values dict
473
+ values_dict = agg_result.get("values", {})
474
+ # For a single percentile, extract the value
475
+ if len(values_dict) == 1:
476
+ value = list(values_dict.values())[0]
477
+ else:
478
+ value = values_dict
479
+ elif func in ["values", "unique"]:
480
+ # Extract buckets from terms aggregation
481
+ buckets = agg_result.get("buckets", [])
482
+ value = [bucket["key"] for bucket in buckets]
475
483
  else:
476
- value = values_dict
484
+ value = agg_result
485
+
486
+ stats_results = {
487
+ "type": "stats",
488
+ "operation": func,
489
+ "field": field,
490
+ "values": value,
491
+ "group_by": [],
492
+ }
477
493
  else:
478
- value = agg_result
479
-
480
- stats_results = {
481
- "type": "stats",
482
- "operation": func,
483
- "field": field,
484
- "values": value,
485
- "group_by": [],
486
- }
494
+ # Multiple aggregations
495
+ results = {}
496
+ for i, agg in enumerate(aggregations):
497
+ func = agg.get("function", "")
498
+ field = agg.get("field", "*")
499
+ alias = agg.get("alias") or f"{func}_{field}_{i}"
500
+ agg_result = aggs_response.get(alias, {})
501
+
502
+ # Extract the value based on aggregation type
503
+ if func == "count":
504
+ value = agg_result.get("value", 0)
505
+ elif func in ["sum", "min", "max", "avg", "average"]:
506
+ value = agg_result.get("value", 0)
507
+ elif func == "unique_count":
508
+ value = agg_result.get("value", 0)
509
+ elif func in ["percentile", "percentiles", "p", "pct"]:
510
+ # Percentiles return a values dict
511
+ values_dict = agg_result.get("values", {})
512
+ # For a single percentile, extract the value
513
+ if len(values_dict) == 1:
514
+ value = list(values_dict.values())[0]
515
+ else:
516
+ value = values_dict
517
+ elif func in ["values", "unique"]:
518
+ # Extract buckets from terms aggregation
519
+ buckets = agg_result.get("buckets", [])
520
+ value = [bucket["key"] for bucket in buckets]
521
+ else:
522
+ value = agg_result
523
+
524
+ key = agg.get("alias") or f"{func}_{field}"
525
+ results[key] = value
526
+
527
+ stats_results = {
528
+ "type": "stats",
529
+ "results": results,
530
+ }
487
531
  else:
488
532
  stats_results = {"type": "stats", "operation": "unknown", "field": "*", "values": 0, "group_by": []}
489
533
 
@@ -133,7 +133,8 @@ class StatsOperations:
133
133
  """
134
134
  # Parse the query
135
135
  try:
136
- if not query.strip().startswith("| stats") and "|" not in query:
136
+ # Don't add prefix if query already starts with "stats"
137
+ if not query.strip().startswith("| stats") and not query.strip().startswith("stats") and "|" not in query:
137
138
  query = "| stats " + query.strip()
138
139
 
139
140
  ast = self.parser.parse(query)
tql/opensearch_stats.py CHANGED
@@ -33,6 +33,9 @@ class OpenSearchStatsTranslator:
33
33
  "pct_rank": "percentile_ranks",
34
34
  "pct_ranks": "percentile_ranks",
35
35
  "zscore": None, # Requires post-processing
36
+ "values": "terms", # Return unique values
37
+ "unique": "terms", # Alias for values
38
+ "cardinality": "cardinality", # Count of unique values
36
39
  }
37
40
 
38
41
  # Aggregations that require numeric fields
@@ -146,6 +149,9 @@ class OpenSearchStatsTranslator:
146
149
  if not rank_values:
147
150
  raise TQLError("percentile_rank requires at least one value")
148
151
  aggs_dsl[alias] = {"percentile_ranks": {"field": field, "values": rank_values}}
152
+ elif func in ["values", "unique"]:
153
+ # Terms aggregation to get unique values
154
+ aggs_dsl[alias] = {"terms": {"field": field, "size": 10000}} # Large size to get all values
149
155
  else:
150
156
  # Direct mapping
151
157
  aggs_dsl[alias] = {os_agg_type: {"field": field}}
@@ -447,5 +453,9 @@ class OpenSearchStatsTranslator:
447
453
  for k, v in values.items():
448
454
  result[f"rank_{k}"] = v
449
455
  return result
456
+ elif function in ["values", "unique"]:
457
+ # Extract buckets from terms aggregation
458
+ buckets = agg_result.get("buckets", [])
459
+ return [bucket["key"] for bucket in buckets]
450
460
  else:
451
461
  return None
@@ -380,8 +380,9 @@ class TQLGrammar:
380
380
  caseless=True,
381
381
  )
382
382
 
383
- # Special case for count(*)
383
+ # Special case for count(*) and count()
384
384
  self.count_all = CaselessKeyword("count") + Suppress("(") + Suppress("*") + Suppress(")")
385
+ self.count_empty = CaselessKeyword("count") + Suppress("(") + Suppress(")")
385
386
 
386
387
  # Aggregation function with field
387
388
  self.agg_function = (
@@ -395,6 +396,7 @@ class TQLGrammar:
395
396
  + Suppress(")")
396
397
  )
397
398
  | self.count_all
399
+ | self.count_empty
398
400
  )
399
401
 
400
402
  # Support for aliasing: sum(revenue) as total_revenue