pytrilogy 0.0.2.39__tar.gz → 0.0.2.41__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.

Potentially problematic release.


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

Files changed (110) hide show
  1. {pytrilogy-0.0.2.39/pytrilogy.egg-info → pytrilogy-0.0.2.41}/PKG-INFO +2 -2
  2. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/README.md +1 -1
  3. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41/pytrilogy.egg-info}/PKG-INFO +2 -2
  4. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_select.py +4 -4
  5. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_show.py +2 -2
  6. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_statements.py +2 -2
  7. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/__init__.py +1 -1
  8. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/models.py +9 -3
  9. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/optimizations/predicate_pushdown.py +11 -2
  10. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/basic_node.py +30 -2
  11. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/window_node.py +23 -7
  12. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/base.py +9 -6
  13. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parsing/parse_engine.py +4 -4
  14. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parsing/render.py +4 -4
  15. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parsing/trilogy.lark +1 -1
  16. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/LICENSE.md +0 -0
  17. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/pyproject.toml +0 -0
  18. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/pytrilogy.egg-info/SOURCES.txt +0 -0
  19. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/pytrilogy.egg-info/dependency_links.txt +0 -0
  20. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/pytrilogy.egg-info/entry_points.txt +0 -0
  21. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/pytrilogy.egg-info/requires.txt +0 -0
  22. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/pytrilogy.egg-info/top_level.txt +0 -0
  23. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/setup.cfg +0 -0
  24. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/setup.py +0 -0
  25. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_datatypes.py +0 -0
  26. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_declarations.py +0 -0
  27. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_derived_concepts.py +0 -0
  28. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_discovery_nodes.py +0 -0
  29. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_enums.py +0 -0
  30. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_environment.py +0 -0
  31. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_executor.py +0 -0
  32. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_functions.py +0 -0
  33. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_imports.py +0 -0
  34. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_metadata.py +0 -0
  35. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_models.py +0 -0
  36. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_multi_join_assignments.py +0 -0
  37. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_parse_engine.py +0 -0
  38. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_parsing.py +0 -0
  39. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_partial_handling.py +0 -0
  40. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_query_processing.py +0 -0
  41. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_undefined_concept.py +0 -0
  42. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/tests/test_where_clause.py +0 -0
  43. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/compiler.py +0 -0
  44. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/constants.py +0 -0
  45. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/__init__.py +0 -0
  46. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/constants.py +0 -0
  47. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/enums.py +0 -0
  48. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/env_processor.py +0 -0
  49. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/environment_helpers.py +0 -0
  50. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/ergonomics.py +0 -0
  51. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/exceptions.py +0 -0
  52. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/functions.py +0 -0
  53. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/graph_models.py +0 -0
  54. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/internal.py +0 -0
  55. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/optimization.py +0 -0
  56. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/optimizations/__init__.py +0 -0
  57. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/optimizations/base_optimization.py +0 -0
  58. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/optimizations/inline_constant.py +0 -0
  59. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/optimizations/inline_datasource.py +0 -0
  60. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/__init__.py +0 -0
  61. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  62. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/graph_utils.py +0 -0
  63. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/__init__.py +0 -0
  64. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/common.py +0 -0
  65. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  66. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/group_node.py +0 -0
  67. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  68. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  69. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  70. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  71. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  72. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/select_node.py +0 -0
  73. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  74. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/nodes/__init__.py +0 -0
  75. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/nodes/base_node.py +0 -0
  76. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/nodes/filter_node.py +0 -0
  77. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/nodes/group_node.py +0 -0
  78. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/nodes/merge_node.py +0 -0
  79. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  80. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  81. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/nodes/window_node.py +0 -0
  82. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/processing/utility.py +0 -0
  83. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/core/query_processor.py +0 -0
  84. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/__init__.py +0 -0
  85. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/bigquery.py +0 -0
  86. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/common.py +0 -0
  87. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/config.py +0 -0
  88. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/duckdb.py +0 -0
  89. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/enums.py +0 -0
  90. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/postgres.py +0 -0
  91. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/presto.py +0 -0
  92. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/snowflake.py +0 -0
  93. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/dialect/sql_server.py +0 -0
  94. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/engine.py +0 -0
  95. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/executor.py +0 -0
  96. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/hooks/__init__.py +0 -0
  97. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/hooks/base_hook.py +0 -0
  98. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/hooks/graph_hook.py +0 -0
  99. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/hooks/query_debugger.py +0 -0
  100. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/metadata/__init__.py +0 -0
  101. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parser.py +0 -0
  102. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parsing/__init__.py +0 -0
  103. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parsing/common.py +0 -0
  104. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parsing/config.py +0 -0
  105. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parsing/exceptions.py +0 -0
  106. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/parsing/helpers.py +0 -0
  107. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/py.typed +0 -0
  108. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/scripts/__init__.py +0 -0
  109. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/scripts/trilogy.py +0 -0
  110. {pytrilogy-0.0.2.39 → pytrilogy-0.0.2.41}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.39
3
+ Version: 0.0.2.41
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -186,7 +186,7 @@ datasource usa_names(
186
186
  gender:gender,
187
187
  state:state
188
188
  )
189
- address bigquery-public-data.usa_names.usa_1910_2013;
189
+ address `bigquery-public-data.usa_names.usa_1910_2013`;
190
190
 
191
191
  '''
192
192
  )
@@ -156,7 +156,7 @@ datasource usa_names(
156
156
  gender:gender,
157
157
  state:state
158
158
  )
159
- address bigquery-public-data.usa_names.usa_1910_2013;
159
+ address `bigquery-public-data.usa_names.usa_1910_2013`;
160
160
 
161
161
  '''
162
162
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.39
3
+ Version: 0.0.2.41
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -186,7 +186,7 @@ datasource usa_names(
186
186
  gender:gender,
187
187
  state:state
188
188
  )
189
- address bigquery-public-data.usa_names.usa_1910_2013;
189
+ address `bigquery-public-data.usa_names.usa_1910_2013`;
190
190
 
191
191
  '''
192
192
  )
@@ -19,7 +19,7 @@ datasource posts (
19
19
  id: post_id
20
20
  )
21
21
  grain (post_id)
22
- address bigquery-public-data.stackoverflow.post_history
22
+ address `bigquery-public-data.stackoverflow.post_history`
23
23
  ;
24
24
 
25
25
 
@@ -29,7 +29,7 @@ datasource users (
29
29
  about_me: about_me,
30
30
  )
31
31
  grain (user_id)
32
- address bigquery-public-data.stackoverflow.users
32
+ address `bigquery-public-data.stackoverflow.users`
33
33
  ;
34
34
 
35
35
 
@@ -70,7 +70,7 @@ def test_double_aggregate():
70
70
  id: post_id
71
71
  )
72
72
  grain (post_id)
73
- address bigquery-public-data.stackoverflow.post_history
73
+ address `bigquery-public-data.stackoverflow.post_history`
74
74
  ;
75
75
 
76
76
 
@@ -80,7 +80,7 @@ def test_double_aggregate():
80
80
  about_me: about_me,
81
81
  )
82
82
  grain (user_id)
83
- address bigquery-public-data.stackoverflow.users
83
+ address `bigquery-public-data.stackoverflow.users`
84
84
  ;
85
85
 
86
86
 
@@ -16,7 +16,7 @@ def test_show_bigquery():
16
16
  id: post_id
17
17
  )
18
18
  grain (post_id)
19
- address bigquery-public-data.stackoverflow.post_history
19
+ address `bigquery-public-data.stackoverflow.post_history`
20
20
  ;
21
21
 
22
22
 
@@ -26,7 +26,7 @@ def test_show_bigquery():
26
26
  about_me: about_me,
27
27
  )
28
28
  grain (user_id)
29
- address bigquery-public-data.stackoverflow.users
29
+ address `bigquery-public-data.stackoverflow.users`
30
30
  ;
31
31
 
32
32
 
@@ -40,7 +40,7 @@ datasource posts (
40
40
  id: post_id
41
41
  )
42
42
  grain (post_id)
43
- address bigquery-public-data.stackoverflow.post_history
43
+ address `bigquery-public-data.stackoverflow.post_history`
44
44
  ;
45
45
  """
46
46
  parse(text)
@@ -76,7 +76,7 @@ datasource x_posts (
76
76
  id: post_id
77
77
  )
78
78
  grain (post_id)
79
- address bigquery-public-data.stackoverflow.post_history
79
+ address `bigquery-public-data.stackoverflow.post_history`
80
80
  where post_id = 2
81
81
  ;
82
82
  """
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.2.39"
7
+ __version__ = "0.0.2.41"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -448,8 +448,8 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
448
448
  ] = None
449
449
  namespace: Optional[str] = Field(default=DEFAULT_NAMESPACE, validate_default=True)
450
450
  keys: Optional[Tuple["Concept", ...]] = None
451
- grain: "Grain" = Field(default=None, validate_default=True)
452
- modifiers: Optional[List[Modifier]] = Field(default_factory=list)
451
+ grain: "Grain" = Field(default=None, validate_default=True) # type: ignore
452
+ modifiers: List[Modifier] = Field(default_factory=list) # type: ignore
453
453
  pseudonyms: set[str] = Field(default_factory=set)
454
454
  _address_cache: str | None = None
455
455
 
@@ -1375,6 +1375,9 @@ class WindowItem(Mergeable, Namespaced, SelectContext, BaseModel):
1375
1375
  over: List["Concept"] = Field(default_factory=list)
1376
1376
  index: Optional[int] = None
1377
1377
 
1378
+ def __repr__(self) -> str:
1379
+ return f"{self.type}({self.content} {self.index}, {self.over}, {self.order_by})"
1380
+
1378
1381
  def with_merge(
1379
1382
  self, source: Concept, target: Concept, modifiers: List[Modifier]
1380
1383
  ) -> "WindowItem":
@@ -1383,6 +1386,7 @@ class WindowItem(Mergeable, Namespaced, SelectContext, BaseModel):
1383
1386
  content=self.content.with_merge(source, target, modifiers),
1384
1387
  over=[x.with_merge(source, target, modifiers) for x in self.over],
1385
1388
  order_by=[x.with_merge(source, target, modifiers) for x in self.order_by],
1389
+ index=self.index,
1386
1390
  )
1387
1391
 
1388
1392
  def with_namespace(self, namespace: str) -> "WindowItem":
@@ -1391,6 +1395,7 @@ class WindowItem(Mergeable, Namespaced, SelectContext, BaseModel):
1391
1395
  content=self.content.with_namespace(namespace),
1392
1396
  over=[x.with_namespace(namespace) for x in self.over],
1393
1397
  order_by=[x.with_namespace(namespace) for x in self.order_by],
1398
+ index=self.index,
1394
1399
  )
1395
1400
 
1396
1401
  def with_select_context(
@@ -1410,6 +1415,7 @@ class WindowItem(Mergeable, Namespaced, SelectContext, BaseModel):
1410
1415
  x.with_select_context(grain, conditional, environment)
1411
1416
  for x in self.order_by
1412
1417
  ],
1418
+ index=self.index,
1413
1419
  )
1414
1420
 
1415
1421
  @property
@@ -3372,7 +3378,7 @@ class Environment(BaseModel):
3372
3378
  functions: Dict[str, Function] = Field(default_factory=dict)
3373
3379
  data_types: Dict[str, DataType] = Field(default_factory=dict)
3374
3380
  imports: Dict[str, list[ImportStatement]] = Field(
3375
- default_factory=lambda: defaultdict(list)
3381
+ default_factory=lambda: defaultdict(list) # type: ignore
3376
3382
  )
3377
3383
  namespace: str = DEFAULT_NAMESPACE
3378
3384
  working_path: str | Path = Field(default_factory=lambda: os.getcwd())
@@ -6,6 +6,7 @@ from trilogy.core.models import (
6
6
  ConceptArgs,
7
7
  Comparison,
8
8
  Parenthetical,
9
+ WindowItem,
9
10
  )
10
11
  from trilogy.core.optimizations.base_optimization import OptimizationRule
11
12
  from trilogy.core.processing.utility import is_scalar_condition
@@ -45,7 +46,15 @@ class PredicatePushdown(OptimizationRule):
45
46
  all_inputs = {x.address for x in candidate.concept_arguments}
46
47
  if is_child_of(candidate, parent_cte.condition):
47
48
  return False
48
-
49
+ non_materialized = [k for k, v in parent_cte.source_map.items() if v == []]
50
+ concrete = [
51
+ x for x in parent_cte.output_columns if x.address in non_materialized
52
+ ]
53
+ if any(isinstance(x.lineage, WindowItem) for x in concrete):
54
+ self.debug(
55
+ f"CTE {parent_cte.name} has window clause calculation, cannot push up to this without changing results"
56
+ )
57
+ return False
49
58
  materialized = {k for k, v in parent_cte.source_map.items() if v != []}
50
59
  if not row_conditions or not materialized:
51
60
  return False
@@ -108,12 +117,12 @@ class PredicatePushdown(OptimizationRule):
108
117
 
109
118
  if not cte.parent_ctes:
110
119
  self.debug(f"No parent CTEs for {cte.name}")
111
-
112
120
  return False
113
121
 
114
122
  if not cte.condition:
115
123
  self.debug(f"No CTE condition for {cte.name}")
116
124
  return False
125
+
117
126
  if self.complete.get(cte.name):
118
127
  self.debug("Have done this CTE before")
119
128
  return False
@@ -1,7 +1,12 @@
1
1
  # directly select out a basic derivation
2
2
  from typing import List
3
3
 
4
- from trilogy.core.models import Concept, WhereClause
4
+ from trilogy.core.models import (
5
+ Concept,
6
+ WhereClause,
7
+ Function,
8
+ FunctionClass,
9
+ )
5
10
  from trilogy.core.processing.nodes import StrategyNode, History
6
11
  from trilogy.core.processing.node_generators.common import (
7
12
  resolve_function_parent_concepts,
@@ -12,6 +17,22 @@ from trilogy.core.enums import SourceType
12
17
  LOGGER_PREFIX = "[GEN_BASIC_NODE]"
13
18
 
14
19
 
20
+ def is_equivalent_basic_function_lineage(
21
+ x: Concept,
22
+ y: Concept,
23
+ ):
24
+ if not isinstance(x.lineage, Function) or not isinstance(y.lineage, Function):
25
+ return False
26
+ if x.lineage.operator == y.lineage.operator:
27
+ return True
28
+ if (
29
+ y.lineage.operator in FunctionClass.AGGREGATE_FUNCTIONS.value
30
+ or y.lineage.operator in FunctionClass.ONE_TO_MANY.value
31
+ ):
32
+ return False
33
+ return True
34
+
35
+
15
36
  def gen_basic_node(
16
37
  concept: Concept,
17
38
  local_optional: List[Concept],
@@ -32,8 +53,15 @@ def gen_basic_node(
32
53
  equivalent_optional = [
33
54
  x
34
55
  for x in local_optional
35
- if x.lineage == concept.lineage and x.address != concept.address
56
+ if is_equivalent_basic_function_lineage(concept, x)
57
+ and x.address != concept.address
36
58
  ]
59
+ if equivalent_optional:
60
+ logger.info(
61
+ f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} has equivalent optional {[x.address for x in equivalent_optional]}"
62
+ )
63
+ for eo in equivalent_optional:
64
+ parent_concepts += resolve_function_parent_concepts(eo)
37
65
  non_equivalent_optional = [
38
66
  x for x in local_optional if x not in equivalent_optional
39
67
  ]
@@ -10,16 +10,16 @@ from trilogy.core.processing.utility import padding
10
10
  LOGGER_PREFIX = "[GEN_WINDOW_NODE]"
11
11
 
12
12
 
13
- def resolve_window_parent_concepts(concept: Concept) -> List[Concept]:
13
+ def resolve_window_parent_concepts(concept: Concept) -> tuple[Concept, List[Concept]]:
14
14
  if not isinstance(concept.lineage, WindowItem):
15
15
  raise ValueError
16
- base = [concept.lineage.content]
16
+ base = []
17
17
  if concept.lineage.over:
18
18
  base += concept.lineage.over
19
19
  if concept.lineage.order_by:
20
20
  for item in concept.lineage.order_by:
21
21
  base += [item.expr.output]
22
- return unique(base, "address")
22
+ return concept.lineage.content, unique(base, "address")
23
23
 
24
24
 
25
25
  def gen_window_node(
@@ -32,9 +32,25 @@ def gen_window_node(
32
32
  history: History | None = None,
33
33
  conditions: WhereClause | None = None,
34
34
  ) -> StrategyNode | None:
35
- parent_concepts = resolve_window_parent_concepts(concept)
36
- parent_node = source_concepts(
37
- mandatory_list=parent_concepts + local_optional,
35
+ base, parent_concepts = resolve_window_parent_concepts(concept)
36
+ equivalent_optional = [
37
+ x
38
+ for x in local_optional
39
+ if isinstance(x.lineage, WindowItem)
40
+ and resolve_window_parent_concepts(x)[1] == parent_concepts
41
+ ]
42
+
43
+ non_equivalent_optional = [
44
+ x for x in local_optional if x.address not in equivalent_optional
45
+ ]
46
+ targets = [base]
47
+ if equivalent_optional:
48
+ for x in equivalent_optional:
49
+ assert isinstance(x.lineage, WindowItem)
50
+ targets.append(x.lineage.content)
51
+
52
+ parent_node: StrategyNode = source_concepts(
53
+ mandatory_list=parent_concepts + targets + non_equivalent_optional,
38
54
  environment=environment,
39
55
  g=g,
40
56
  depth=depth + 1,
@@ -61,7 +77,7 @@ def gen_window_node(
61
77
  )
62
78
  raise SyntaxError
63
79
  _window_node = WindowNode(
64
- input_concepts=parent_concepts + local_optional,
80
+ input_concepts=parent_concepts + targets + non_equivalent_optional,
65
81
  output_concepts=[concept] + parent_concepts + local_optional,
66
82
  environment=environment,
67
83
  g=g,
@@ -75,7 +75,7 @@ def window_factory(string: str, include_concept: bool = False) -> Callable:
75
75
  ) -> str:
76
76
  if not include_concept:
77
77
  concept = ""
78
- if offset:
78
+ if offset is not None:
79
79
  base = f"{string}({concept}, {offset})"
80
80
  else:
81
81
  base = f"{string}({concept})"
@@ -138,10 +138,10 @@ FUNCTION_MAP = {
138
138
  FunctionType.STRUCT: lambda x: f"{{{', '.join(struct_arg(x))}}}",
139
139
  FunctionType.ARRAY: lambda x: f"[{', '.join(x)}]",
140
140
  # math
141
- FunctionType.ADD: lambda x: f"{x[0]} + {x[1]}",
142
- FunctionType.SUBTRACT: lambda x: f"{x[0]} - {x[1]}",
143
- FunctionType.DIVIDE: lambda x: f"{x[0]} / {x[1]}",
144
- FunctionType.MULTIPLY: lambda x: f"{x[0]} * {x[1]}",
141
+ FunctionType.ADD: lambda x: " + ".join(x),
142
+ FunctionType.SUBTRACT: lambda x: " - ".join(x),
143
+ FunctionType.DIVIDE: lambda x: " / ".join(x),
144
+ FunctionType.MULTIPLY: lambda x: " * ".join(x),
145
145
  FunctionType.ROUND: lambda x: f"round({x[0]},{x[1]})",
146
146
  FunctionType.MOD: lambda x: f"({x[0]} % {x[1]})",
147
147
  # aggregate types
@@ -313,7 +313,10 @@ class BaseDialect:
313
313
  )
314
314
  for x in c.lineage.over
315
315
  ]
316
- rval = f"{self.WINDOW_FUNCTION_MAP[c.lineage.type](concept = self.render_concept_sql(c.lineage.content, cte=cte, alias=False, raise_invalid=raise_invalid), window=','.join(rendered_over_components), sort=','.join(rendered_order_components))}" # noqa: E501
316
+ rval = f"{self.WINDOW_FUNCTION_MAP[c.lineage.type](concept = self.render_concept_sql(c.lineage.content,
317
+ cte=cte, alias=False, raise_invalid=raise_invalid),
318
+ window=','.join(rendered_over_components), sort=','.join(rendered_order_components),
319
+ offset=c.lineage.index)}" # noqa: E501
317
320
  elif isinstance(c.lineage, FilterItem):
318
321
  # for cases when we've optimized this
319
322
  if cte.condition == c.lineage.where.conditional:
@@ -1777,7 +1777,7 @@ class ParseToObjects(Transformer):
1777
1777
  output_datatype=output_datatype,
1778
1778
  output_purpose=function_args_to_output_purpose(args),
1779
1779
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
1780
- arg_count=2,
1780
+ arg_count=-1,
1781
1781
  )
1782
1782
 
1783
1783
  @v_args(meta=True)
@@ -1790,7 +1790,7 @@ class ParseToObjects(Transformer):
1790
1790
  output_datatype=output_datatype,
1791
1791
  output_purpose=function_args_to_output_purpose(args),
1792
1792
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
1793
- arg_count=2,
1793
+ arg_count=-1,
1794
1794
  )
1795
1795
 
1796
1796
  @v_args(meta=True)
@@ -1803,7 +1803,7 @@ class ParseToObjects(Transformer):
1803
1803
  output_datatype=output_datatype,
1804
1804
  output_purpose=function_args_to_output_purpose(args),
1805
1805
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
1806
- arg_count=2,
1806
+ arg_count=-1,
1807
1807
  )
1808
1808
 
1809
1809
  @v_args(meta=True)
@@ -1817,7 +1817,7 @@ class ParseToObjects(Transformer):
1817
1817
  output_datatype=DataType.FLOAT, # division always returns a float
1818
1818
  output_purpose=function_args_to_output_purpose(args),
1819
1819
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
1820
- arg_count=2,
1820
+ arg_count=-1,
1821
1821
  )
1822
1822
 
1823
1823
  @v_args(meta=True)
@@ -395,13 +395,13 @@ class Renderer:
395
395
  args = [self.to_string(c) for c in arg.arguments]
396
396
 
397
397
  if arg.operator == FunctionType.SUBTRACT:
398
- return f"{args[0]} - {args[1]}"
398
+ return " - ".join(args)
399
399
  if arg.operator == FunctionType.ADD:
400
- return f"{args[0]} + {args[1]}"
400
+ return " + ".join(args)
401
401
  if arg.operator == FunctionType.MULTIPLY:
402
- return f"{args[0]} * {args[1]}"
402
+ return " * ".join(args)
403
403
  if arg.operator == FunctionType.DIVIDE:
404
- return f"{args[0]} / {args[1]}"
404
+ return " / ".join(args)
405
405
  if arg.operator == FunctionType.MOD:
406
406
  return f"{args[0]} % {args[1]}"
407
407
 
@@ -290,7 +290,7 @@
290
290
 
291
291
  // base language constructs
292
292
  concept_lit: IDENTIFIER
293
- IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.]*/
293
+ IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\.]*/
294
294
  WILDCARD_IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.\*]*/
295
295
  QUOTED_IDENTIFIER: /`[a-zA-Z\_][a-zA-Z0-9\_\.\-\*\:\s]*`/
296
296
  QUOTED_ADDRESS: /`[a-zA-Z\_][a-zA-Z0-9\_\.\-\*\:]*`/
File without changes
File without changes
File without changes