pytrilogy 0.0.2.7__py3-none-any.whl → 0.0.2.9__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.

Potentially problematic release.


This version of pytrilogy might be problematic. Click here for more details.

Files changed (34) hide show
  1. {pytrilogy-0.0.2.7.dist-info → pytrilogy-0.0.2.9.dist-info}/METADATA +1 -1
  2. {pytrilogy-0.0.2.7.dist-info → pytrilogy-0.0.2.9.dist-info}/RECORD +34 -34
  3. {pytrilogy-0.0.2.7.dist-info → pytrilogy-0.0.2.9.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +1 -1
  5. trilogy/constants.py +1 -0
  6. trilogy/core/enums.py +1 -0
  7. trilogy/core/models.py +154 -56
  8. trilogy/core/optimization.py +44 -5
  9. trilogy/core/optimizations/inline_datasource.py +14 -8
  10. trilogy/core/optimizations/predicate_pushdown.py +73 -44
  11. trilogy/core/processing/concept_strategies_v3.py +69 -28
  12. trilogy/core/processing/node_generators/common.py +42 -16
  13. trilogy/core/processing/node_generators/filter_node.py +89 -48
  14. trilogy/core/processing/node_generators/group_node.py +3 -1
  15. trilogy/core/processing/node_generators/rowset_node.py +13 -54
  16. trilogy/core/processing/node_generators/select_node.py +10 -13
  17. trilogy/core/processing/node_generators/unnest_node.py +5 -3
  18. trilogy/core/processing/node_generators/window_node.py +23 -2
  19. trilogy/core/processing/nodes/__init__.py +34 -6
  20. trilogy/core/processing/nodes/base_node.py +67 -13
  21. trilogy/core/processing/nodes/filter_node.py +3 -0
  22. trilogy/core/processing/nodes/group_node.py +3 -0
  23. trilogy/core/processing/nodes/merge_node.py +1 -11
  24. trilogy/core/processing/nodes/select_node_v2.py +1 -0
  25. trilogy/core/processing/utility.py +29 -10
  26. trilogy/core/query_processor.py +47 -20
  27. trilogy/dialect/base.py +47 -14
  28. trilogy/dialect/common.py +15 -3
  29. trilogy/dialect/presto.py +2 -1
  30. trilogy/parsing/parse_engine.py +20 -1
  31. trilogy/parsing/trilogy.lark +3 -1
  32. {pytrilogy-0.0.2.7.dist-info → pytrilogy-0.0.2.9.dist-info}/LICENSE.md +0 -0
  33. {pytrilogy-0.0.2.7.dist-info → pytrilogy-0.0.2.9.dist-info}/entry_points.txt +0 -0
  34. {pytrilogy-0.0.2.7.dist-info → pytrilogy-0.0.2.9.dist-info}/top_level.txt +0 -0
trilogy/dialect/common.py CHANGED
@@ -9,6 +9,18 @@ def null_wrapper(lval: str, rval: str, concept: Concept) -> str:
9
9
  return f"{lval} = {rval}"
10
10
 
11
11
 
12
+ def render_unnest(
13
+ unnest_mode: UnnestMode,
14
+ quote_character: str,
15
+ concept: Concept,
16
+ render_func: Callable[[Concept, CTE, bool], str],
17
+ cte: CTE,
18
+ ):
19
+ if unnest_mode == UnnestMode.CROSS_JOIN:
20
+ return f"{render_func(concept, cte, False)} as {quote_character}{concept.safe_address}{quote_character}"
21
+ return f"{render_func(concept, cte, False)} as unnest_wrapper ({quote_character}{concept.safe_address}{quote_character})"
22
+
23
+
12
24
  def render_join(
13
25
  join: Join | InstantiatedUnnestJoin,
14
26
  quote_character: str,
@@ -25,10 +37,10 @@ def render_join(
25
37
  if not cte:
26
38
  raise ValueError("must provide a cte to build an unnest joins")
27
39
  if unnest_mode == UnnestMode.CROSS_JOIN:
28
- return f"CROSS JOIN {render_func(join.concept, cte, False)} as {quote_character}{join.concept.safe_address}{quote_character}"
40
+ return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.concept, render_func, cte)}"
29
41
  if unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
30
- return f"CROSS JOIN {render_func(join.concept, cte, False)} as array_unnest ({quote_character}{join.concept.safe_address}{quote_character})"
31
- return f"FULL JOIN {render_func(join.concept, cte, False)} as unnest_wrapper({quote_character}{join.concept.safe_address}{quote_character})"
42
+ return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.concept, render_func, cte)}"
43
+ return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.concept, render_func, cte)}"
32
44
  left_name = join.left_name
33
45
  right_name = join.right_name
34
46
  right_base = join.right_ref
trilogy/dialect/presto.py CHANGED
@@ -33,6 +33,7 @@ FUNCTION_MAP = {
33
33
  FunctionType.DATE_ADD: lambda x: f"DATE_ADD('{x[1]}', {x[2]}, {x[0]})",
34
34
  FunctionType.CURRENT_DATE: lambda x: "CURRENT_DATE",
35
35
  FunctionType.CURRENT_DATETIME: lambda x: "CURRENT_TIMESTAMP",
36
+ FunctionType.ARRAY: lambda x: f"ARRAY[{', '.join(x)}]",
36
37
  }
37
38
 
38
39
  FUNCTION_GRAIN_MATCH_MAP = {
@@ -86,7 +87,7 @@ class PrestoDialect(BaseDialect):
86
87
  QUOTE_CHARACTER = '"'
87
88
  SQL_TEMPLATE = SQL_TEMPLATE
88
89
  DATATYPE_MAP = {**BaseDialect.DATATYPE_MAP, DataType.NUMERIC: "DECIMAL"}
89
- UNNEST_MODE = UnnestMode.CROSS_JOIN
90
+ UNNEST_MODE = UnnestMode.CROSS_JOIN_ALIAS
90
91
 
91
92
 
92
93
  class TrinoDialect(PrestoDialect):
@@ -108,6 +108,7 @@ from trilogy.core.models import (
108
108
  list_to_wrapper,
109
109
  dict_to_map_wrapper,
110
110
  NumericType,
111
+ HavingClause,
111
112
  )
112
113
  from trilogy.parsing.exceptions import ParseError
113
114
  from trilogy.utility import string_to_hash
@@ -960,6 +961,7 @@ class ParseToObjects(Transformer):
960
961
  limit = None
961
962
  order_by = None
962
963
  where = None
964
+ having = None
963
965
  for arg in args:
964
966
  if isinstance(arg, List):
965
967
  select_items = arg
@@ -967,13 +969,16 @@ class ParseToObjects(Transformer):
967
969
  limit = arg.count
968
970
  elif isinstance(arg, OrderBy):
969
971
  order_by = arg
970
- elif isinstance(arg, WhereClause):
972
+ elif isinstance(arg, WhereClause) and not isinstance(arg, HavingClause):
971
973
  where = arg
974
+ elif isinstance(arg, HavingClause):
975
+ having = arg
972
976
  if not select_items:
973
977
  raise ValueError("Malformed select, missing select items")
974
978
  output = SelectStatement(
975
979
  selection=select_items,
976
980
  where_clause=where,
981
+ having_clause=having,
977
982
  limit=limit,
978
983
  order_by=order_by,
979
984
  meta=Metadata(line_number=meta.line),
@@ -991,6 +996,7 @@ class ParseToObjects(Transformer):
991
996
  and output.where_clause_category == SelectFiltering.IMPLICIT
992
997
  else None
993
998
  ),
999
+ environment=self.environment,
994
1000
  )
995
1001
  self.environment.add_concept(new_concept, meta=meta)
996
1002
  item.content.output = new_concept
@@ -1032,6 +1038,19 @@ class ParseToObjects(Transformer):
1032
1038
  )
1033
1039
  return WhereClause(conditional=root)
1034
1040
 
1041
+ def having(self, args):
1042
+ root = args[0]
1043
+ if not isinstance(root, (Comparison, Conditional, Parenthetical)):
1044
+ if arg_to_datatype(root) == DataType.BOOL:
1045
+ root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
1046
+ else:
1047
+ root = Comparison(
1048
+ left=root,
1049
+ right=MagicConstants.NULL,
1050
+ operator=ComparisonOperator.IS_NOT,
1051
+ )
1052
+ return HavingClause(conditional=root)
1053
+
1035
1054
  @v_args(meta=True)
1036
1055
  def function_binding_list(self, meta: Meta, args) -> Concept:
1037
1056
  return args
@@ -61,7 +61,7 @@
61
61
  persist_statement: "persist"i IDENTIFIER "into"i IDENTIFIER "from"i select_statement grain_clause?
62
62
 
63
63
  // select statement
64
- select_statement: "select"i select_list where? order_by? limit?
64
+ select_statement: where? "select"i select_list where? having? order_by? limit?
65
65
 
66
66
  // multiple_selects
67
67
  multi_select_statement: select_statement ("merge" select_statement)+ "align"i align_clause where? order_by? limit?
@@ -134,6 +134,8 @@
134
134
 
135
135
  where: "WHERE"i conditional
136
136
 
137
+ having: "HAVING"i conditional
138
+
137
139
  !array_comparison: ( ("NOT"i "IN"i) | "IN"i)
138
140
 
139
141
  COMPARISON_OPERATOR: (/is[\s]+not/ | "is" |"=" | ">" | "<" | ">=" | "<=" | "!=")