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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.10
3
+ Version: 0.0.2.11
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,4 +1,4 @@
1
- trilogy/__init__.py,sha256=-fwTLWZDBATA0SPkOXcsRS3py_W_F1oq7wt2cVysbNg,291
1
+ trilogy/__init__.py,sha256=rSlG7hlvpZjtLdU_7j6ldvO5bmiafhgPB40sdcOu3j0,291
2
2
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  trilogy/constants.py,sha256=HRQq4i3cpSEJCywt61QKEzRO1jd4tEPZNSBuxUA_7yg,922
4
4
  trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
@@ -16,26 +16,26 @@ trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,56
16
16
  trilogy/core/functions.py,sha256=ARJAyBjeS415-54k3G_bx807rkPZonEulMaLRxSP7vU,10371
17
17
  trilogy/core/graph_models.py,sha256=oJUMSpmYhqXlavckHLpR07GJxuQ8dZ1VbB1fB0KaS8c,2036
18
18
  trilogy/core/internal.py,sha256=jNGFHKENnbMiMCtAgsnLZYVSENDK4b5ALecXFZpTDzQ,1075
19
- trilogy/core/models.py,sha256=mjORKd_RFvNc_c7QqCHCM4GrJSb5VA50tMqo7fX5-Bw,142855
19
+ trilogy/core/models.py,sha256=rIeYw173UjfTfc7194y-jqv7u51Uy6LufwbiwKI2Cgs,143834
20
20
  trilogy/core/optimization.py,sha256=RJmDr2f9hyFePF-B6LpmHkj69-AzUm-nNtvO59899O8,6601
21
- trilogy/core/query_processor.py,sha256=N5Jdbb8D8_mdeJufIch0uCbfuf2psSSkLxGXwC5CqCg,18663
21
+ trilogy/core/query_processor.py,sha256=50FHJ9Rw89ZNBflZK8R9jn7O_WdDHplS1hZSNXCozN8,19352
22
22
  trilogy/core/optimizations/__init__.py,sha256=pxRzNzd2g8oRMy4f_ub5va6bNS2pd4hnyp9JBzTKc1E,300
23
23
  trilogy/core/optimizations/base_optimization.py,sha256=tWWT-xnTbnEU-mNi_isMNbywm8B9WTRsNFwGpeh3rqE,468
24
24
  trilogy/core/optimizations/inline_constant.py,sha256=kHNyc2UoaPVdYfVAPAFwnWuk4sJ_IF5faRtVcDOrBtw,1110
25
25
  trilogy/core/optimizations/inline_datasource.py,sha256=AATzQ6YrtW_1-aQFjQyTYqEYKBoMFhek7ADfBr4uUdQ,3634
26
26
  trilogy/core/optimizations/predicate_pushdown.py,sha256=4Y6zfJN3VVexkD6p9IYWN4BTL8RwW6hyNb0VQZ8ETdI,8020
27
27
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- trilogy/core/processing/concept_strategies_v3.py,sha256=yjCiGKzghjAc0E0QAcdJ1Xr5RXHAQL2zMPOz-dMhEzM,25431
28
+ trilogy/core/processing/concept_strategies_v3.py,sha256=5yZQRWlEyNR2sddq7kx12_PRUNhekEu6bWO2yIvjjUA,25511
29
29
  trilogy/core/processing/graph_utils.py,sha256=aq-kqk4Iado2HywDxWEejWc-7PGO6Oa-ZQLAM6XWPHw,1199
30
30
  trilogy/core/processing/utility.py,sha256=15Qi68ktmnQmMt5jxrpueQ34oG5fSSgst3kIEejwF8A,14519
31
31
  trilogy/core/processing/node_generators/__init__.py,sha256=-mzYkRsaRNa_dfTckYkKVFSR8h8a3ihEiPJDU_tAmDo,672
32
32
  trilogy/core/processing/node_generators/basic_node.py,sha256=4242PNGTCm2tklqMIkqVu5Iv4m_IeTnOYXxDveuCDZM,2856
33
33
  trilogy/core/processing/node_generators/common.py,sha256=lDBRq9X6dQ_xSwXxLLNDq2pW8D-XwAY-ylTJLMugkLw,9525
34
- trilogy/core/processing/node_generators/filter_node.py,sha256=N8PoPvj-_ZQbucTyNqpZ1EyPTDAy6oB7DuEYCQxQBOs,7411
35
- trilogy/core/processing/node_generators/group_node.py,sha256=JwT8qYeGbozrZNGAlpndc9aaIN7K9ROYkkc2rAS8Qz8,3079
34
+ trilogy/core/processing/node_generators/filter_node.py,sha256=Ij2WqyOsu-TFxhAcL50PLMGpghsSWXJnWEJ8yTqOwrY,8228
35
+ trilogy/core/processing/node_generators/group_node.py,sha256=Du-9uFXD0M-aHq2MV7v5R3QCrAL0JZBFMW-YQwgb6Bw,3135
36
36
  trilogy/core/processing/node_generators/group_to_node.py,sha256=nzITnhaALIT7FMonyo16nNo-kSrLfefa9sZBYecrvkU,2887
37
37
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=vP84dnLQy6dtypi6mUbt9sMAcmmrTgQ1Oz4GI6X1IEo,6421
38
- trilogy/core/processing/node_generators/node_merge_node.py,sha256=yZ75lsl8RtM8kr-XQxqZEU6fm-JN5DTJxNbneayu36c,12319
38
+ trilogy/core/processing/node_generators/node_merge_node.py,sha256=qS8VKppi10NKfqNb7BXTKyLBBn4wJuMmFYhrtMUkYb8,12580
39
39
  trilogy/core/processing/node_generators/rowset_node.py,sha256=6KVnuk75mRzWJ-jIk7e8azN8BIPPuCn-VxPlxDqfPVE,4616
40
40
  trilogy/core/processing/node_generators/select_node.py,sha256=E8bKOAUpwLwZy1iiaFVD5sM4XK-eFpHgijdyIWLMyH4,18904
41
41
  trilogy/core/processing/node_generators/unnest_node.py,sha256=gHjurhr86JFkbq6vxTHDQGDKt95EdotSFHVKgrR_Z3Q,1860
@@ -49,7 +49,7 @@ trilogy/core/processing/nodes/select_node_v2.py,sha256=QuXNcwgjTRYamOoIooGrp4ie6
49
49
  trilogy/core/processing/nodes/unnest_node.py,sha256=JFtm90IVM-46aCYkTNIaJah6v9ApAfonjVhcVM1HmDE,1903
50
50
  trilogy/core/processing/nodes/window_node.py,sha256=X7qxLUKd3tekjUUsmH_4vz5b-U89gMnGd04VBxuu2Ns,1280
51
51
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- trilogy/dialect/base.py,sha256=_WKy2YJyDZdelHVX4mcnwKzAp2J-56UjoBpS5hG2yrg,30819
52
+ trilogy/dialect/base.py,sha256=xbZXcWvwdRadyb77J2qat67R_gRAmKeeeI4JRH4P928,30726
53
53
  trilogy/dialect/bigquery.py,sha256=15KJ-cOpBlk9O7FPviPgmg8xIydJeKx7WfmL3SSsPE8,2953
54
54
  trilogy/dialect/common.py,sha256=HVNPL8dGyQjT2REruV5C2YPpBbVR7KlD0akVM7GcuPI,3329
55
55
  trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
@@ -69,14 +69,14 @@ trilogy/parsing/common.py,sha256=zNd5buKxK4z9WSszOk4zOI0GexDFukzZtdNfQxg3kVw,621
69
69
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
70
70
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
71
71
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
72
- trilogy/parsing/parse_engine.py,sha256=TD7dr9f4ffRcNB0BpzgaACqc-gD3Hy4_pOyLA7rmK7A,62563
72
+ trilogy/parsing/parse_engine.py,sha256=riXOhMqYGvzyy1aeTZTjPIz3jOszue2VTEH7hn59zD4,63808
73
73
  trilogy/parsing/render.py,sha256=Gy_6wVYPwYLf35Iota08sbqveuWILtUhI8MYStcvtJM,12174
74
- trilogy/parsing/trilogy.lark,sha256=kj1DXkqRFAb20PXN4gGDtXtQI61nUv1U8nfPWSWCr1Y,11407
74
+ trilogy/parsing/trilogy.lark,sha256=QNJnExOdvJyKTrQA4ffh-SGIz7rYd93kf2Ccs0m3cn4,11498
75
75
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
76
  trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
77
- pytrilogy-0.0.2.10.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
78
- pytrilogy-0.0.2.10.dist-info/METADATA,sha256=sH_c8ZeuuG6nuSMIrKQvhLoHi67-4qWu3DZ5KM27FJQ,7907
79
- pytrilogy-0.0.2.10.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
80
- pytrilogy-0.0.2.10.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
81
- pytrilogy-0.0.2.10.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
82
- pytrilogy-0.0.2.10.dist-info/RECORD,,
77
+ pytrilogy-0.0.2.11.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
78
+ pytrilogy-0.0.2.11.dist-info/METADATA,sha256=mZ6V2gwihh9nv-bIGkimK2-bKNvsY_3JIUSNzP_m2Wg,7907
79
+ pytrilogy-0.0.2.11.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
80
+ pytrilogy-0.0.2.11.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
81
+ pytrilogy-0.0.2.11.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
82
+ pytrilogy-0.0.2.11.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -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.10"
7
+ __version__ = "0.0.2.11"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/core/models.py CHANGED
@@ -801,15 +801,18 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
801
801
  ) -> "Concept":
802
802
  from trilogy.utility import string_to_hash
803
803
 
804
- name = string_to_hash(self.name + str(condition))
804
+ if self.lineage and isinstance(self.lineage, FilterItem):
805
+ if self.lineage.where.conditional == condition:
806
+ return self
807
+ hash = string_to_hash(self.name + str(condition))
805
808
  new = Concept(
806
- name=f"{self.name}_{name}",
809
+ name=f"{self.name}_filter_{hash}",
807
810
  datatype=self.datatype,
808
811
  purpose=self.purpose,
809
812
  metadata=self.metadata,
810
813
  lineage=FilterItem(content=self, where=WhereClause(conditional=condition)),
811
- keys=None,
812
- grain=(self.grain if self.purpose == Purpose.PROPERTY else Grain()),
814
+ keys=(self.keys if self.purpose == Purpose.PROPERTY else None),
815
+ grain=self.grain if self.grain else Grain(components=[]),
813
816
  namespace=self.namespace,
814
817
  modifiers=self.modifiers,
815
818
  pseudonyms=self.pseudonyms,
@@ -842,6 +845,16 @@ class Grain(Mergeable, BaseModel):
842
845
  v2 = sorted(final, key=lambda x: x.name)
843
846
  return v2
844
847
 
848
+ def with_filter(
849
+ self,
850
+ condition: "Conditional | Comparison | Parenthetical",
851
+ environment: Environment | None = None,
852
+ ) -> "Grain":
853
+ return Grain(
854
+ components=[c.with_filter(condition, environment) for c in self.components],
855
+ nested=self.nested,
856
+ )
857
+
845
858
  @property
846
859
  def components_copy(self) -> List[Concept]:
847
860
  return [*self.components]
@@ -1680,6 +1693,9 @@ class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
1680
1693
  )
1681
1694
  ):
1682
1695
  output.append(item)
1696
+ # TODO: explore implicit filtering more
1697
+ # if self.where_clause.conditional and self.where_clause_category == SelectFiltering.IMPLICIT:
1698
+ # output =[x.with_filter(self.where_clause.conditional) for x in output]
1683
1699
  return Grain(
1684
1700
  components=unique(output, "address"), where_clause=self.where_clause
1685
1701
  )
@@ -4074,8 +4090,13 @@ class RowsetDerivationStatement(Namespaced, BaseModel):
4074
4090
  output: list[Concept] = []
4075
4091
  orig: dict[str, Concept] = {}
4076
4092
  for orig_concept in self.select.output_components:
4093
+ name = orig_concept.name
4094
+ if isinstance(orig_concept.lineage, FilterItem):
4095
+ if orig_concept.lineage.where == self.select.where_clause:
4096
+ name = orig_concept.lineage.content.name
4097
+
4077
4098
  new_concept = Concept(
4078
- name=orig_concept.name,
4099
+ name=name,
4079
4100
  datatype=orig_concept.datatype,
4080
4101
  purpose=orig_concept.purpose,
4081
4102
  lineage=RowsetItem(
@@ -191,6 +191,7 @@ def generate_candidates_restrictive(
191
191
  ):
192
192
  combos.append(local_candidates)
193
193
  combos.append(grain_check)
194
+ # combos.append(local_candidates)
194
195
  # append the empty set for sourcing concept by itself last
195
196
  combos.append([])
196
197
  return combos
@@ -645,6 +646,7 @@ def _search_concepts(
645
646
  depth=depth,
646
647
  source_concepts=search_concepts,
647
648
  history=history,
649
+ search_conditions=conditions,
648
650
  )
649
651
 
650
652
  if expanded:
@@ -39,6 +39,15 @@ def gen_filter_node(
39
39
  raise SyntaxError('Filter node must have a lineage of type "FilterItem"')
40
40
  where = concept.lineage.where
41
41
 
42
+ optional_included: list[Concept] = []
43
+ for x in local_optional:
44
+ if isinstance(x.lineage, FilterItem):
45
+ if concept.lineage.where == where:
46
+ logger.info(
47
+ f"{padding(depth)}{LOGGER_PREFIX} fetching {x.lineage.content.address} as optional parent with same filter conditions "
48
+ )
49
+ parent_row_concepts.append(x.lineage.content)
50
+ optional_included.append(x)
42
51
  logger.info(
43
52
  f"{padding(depth)}{LOGGER_PREFIX} filter {concept.address} derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
44
53
  )
@@ -49,6 +58,7 @@ def gen_filter_node(
49
58
  g=g,
50
59
  depth=depth + 1,
51
60
  history=history,
61
+ conditions=conditions,
52
62
  )
53
63
 
54
64
  flattened_existence = [x for y in parent_existence_concepts for x in y]
@@ -88,6 +98,11 @@ def gen_filter_node(
88
98
  f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
89
99
  )
90
100
  optimized_pushdown = True
101
+ elif optional_included == local_optional:
102
+ logger.info(
103
+ f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
104
+ )
105
+ optimized_pushdown = True
91
106
  if optimized_pushdown:
92
107
  if isinstance(row_parent, SelectNode):
93
108
  logger.info(
@@ -116,6 +131,7 @@ def gen_filter_node(
116
131
  x
117
132
  for x in local_optional
118
133
  if x.address in [y.address for y in parent.output_concepts]
134
+ or x.address in [y.address for y in optional_included]
119
135
  ]
120
136
  parent.add_parents(core_parents)
121
137
  parent.add_condition(where.conditional)
@@ -175,6 +191,7 @@ def gen_filter_node(
175
191
  ] + outputs
176
192
  filter_node.rebuild_cache()
177
193
  return filter_node
194
+
178
195
  enrich_node = source_concepts( # this fetches the parent + join keys
179
196
  # to then connect to the rest of the query
180
197
  mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
@@ -182,10 +199,11 @@ def gen_filter_node(
182
199
  g=g,
183
200
  depth=depth + 1,
184
201
  history=history,
202
+ conditions=conditions,
185
203
  )
186
204
  if not enrich_node:
187
205
  return filter_node
188
- x = MergeNode(
206
+ return MergeNode(
189
207
  input_concepts=[concept, immediate_parent] + local_optional,
190
208
  output_concepts=[
191
209
  concept,
@@ -206,8 +224,7 @@ def gen_filter_node(
206
224
  [immediate_parent] + parent_row_concepts
207
225
  ),
208
226
  join_type=JoinType.LEFT_OUTER,
209
- filter_to_mutual=False,
227
+ filter_to_mutual=True,
210
228
  )
211
229
  ],
212
230
  )
213
- return x
@@ -91,4 +91,6 @@ def gen_group_node(
91
91
  depth=depth,
92
92
  source_concepts=source_concepts,
93
93
  log_lambda=create_log_lambda(LOGGER_PREFIX, depth, logger),
94
+ history=history,
95
+ conditions=conditions,
94
96
  )
@@ -1,6 +1,6 @@
1
1
  from typing import List, Optional
2
2
 
3
- from trilogy.core.models import Concept, Environment, Conditional
3
+ from trilogy.core.models import Concept, Environment, Conditional, WhereClause
4
4
  from trilogy.core.processing.nodes import MergeNode, History, StrategyNode
5
5
  import networkx as nx
6
6
  from trilogy.core.graph_models import concept_to_node
@@ -260,6 +260,7 @@ def subgraphs_to_merge_node(
260
260
  source_concepts,
261
261
  history,
262
262
  conditions,
263
+ search_conditions: WhereClause | None = None,
263
264
  enable_early_exit: bool = True,
264
265
  ):
265
266
  parents: List[StrategyNode] = []
@@ -277,6 +278,7 @@ def subgraphs_to_merge_node(
277
278
  g=g,
278
279
  depth=depth + 1,
279
280
  history=history,
281
+ conditions=search_conditions,
280
282
  )
281
283
  if not parent:
282
284
  logger.info(
@@ -315,6 +317,7 @@ def gen_merge_node(
315
317
  accept_partial: bool = False,
316
318
  history: History | None = None,
317
319
  conditions: Conditional | None = None,
320
+ search_conditions: WhereClause | None = None,
318
321
  ) -> Optional[MergeNode]:
319
322
 
320
323
  for filter_downstream in [True, False]:
@@ -339,6 +342,7 @@ def gen_merge_node(
339
342
  source_concepts=source_concepts,
340
343
  history=history,
341
344
  conditions=conditions,
345
+ search_conditions=search_conditions,
342
346
  )
343
347
  # one concept handling may need to be kicked to alias
344
348
  if len(all_concepts) == 1:
@@ -354,6 +358,7 @@ def gen_merge_node(
354
358
  history=history,
355
359
  conditions=conditions,
356
360
  enable_early_exit=False,
361
+ search_conditions=search_conditions,
357
362
  )
358
363
  if test:
359
364
  return test
@@ -145,7 +145,9 @@ def generate_source_map(
145
145
  names = set([x.name for x in ev])
146
146
  ematches = [cte.name for cte in all_new_ctes if cte.source.name in names]
147
147
  existence_source_map[ek] = ematches
148
- return {k: [] if not v else v for k, v in source_map.items()}, existence_source_map
148
+ return {
149
+ k: [] if not v else list(set(v)) for k, v in source_map.items()
150
+ }, existence_source_map
149
151
 
150
152
 
151
153
  def datasource_to_query_datasource(datasource: Datasource) -> QueryDatasource:
@@ -191,6 +193,8 @@ def resolve_cte_base_name_and_alias_v2(
191
193
  raw_joins: List[Join | InstantiatedUnnestJoin],
192
194
  ) -> Tuple[str | None, str | None]:
193
195
  joins: List[Join] = [join for join in raw_joins if isinstance(join, Join)]
196
+ # INFO trilogy:query_processor.py:263 Finished building source map for civet with 3 parents, have {'local.relevant_customers': ['fowl', 'fowl'],
197
+ # 'customer.demographics.gender': ['mandrill'], 'customer.id': ['mandrill'], 'customer.demographics.id': ['mandrill'], 'customer.id_9268029262289908': [], 'customer.demographics.gender_1513806568509111': []}, query_datasource had non-empty keys ['local.relevant_customers', 'customer.demographics.gender', 'customer.id', 'customer.demographics.id'] and existence had non-empty keys []
194
198
  if (
195
199
  len(source.datasources) == 1
196
200
  and isinstance(source.datasources[0], Datasource)
@@ -212,12 +216,16 @@ def resolve_cte_base_name_and_alias_v2(
212
216
 
213
217
  counts: dict[str, int] = defaultdict(lambda: 0)
214
218
  output_addresses = [x.address for x in source.output_concepts]
219
+ input_address = [x.address for x in source.input_concepts]
215
220
  for k, v in source_map.items():
216
221
  for vx in v:
217
222
  if k in output_addresses:
218
223
  counts[vx] = counts[vx] + 1
219
- else:
220
- counts[vx] = counts[vx]
224
+
225
+ if k in input_address:
226
+ counts[vx] = counts[vx] + 1
227
+
228
+ counts[vx] = counts[vx]
221
229
  if counts:
222
230
  return max(counts, key=counts.get), max(counts, key=counts.get) # type: ignore
223
231
  return None, None
trilogy/dialect/base.py CHANGED
@@ -281,10 +281,7 @@ class BaseDialect:
281
281
  rval = f"{self.WINDOW_FUNCTION_MAP[c.lineage.type](concept = self.render_concept_sql(c.lineage.content, cte=cte, alias=False), window=','.join(rendered_over_components), sort=','.join(rendered_order_components))}" # noqa: E501
282
282
  elif isinstance(c.lineage, FilterItem):
283
283
  # for cases when we've optimized this
284
- if (
285
- len(cte.output_columns) == 1
286
- and cte.condition == c.lineage.where.conditional
287
- ):
284
+ if cte.condition == c.lineage.where.conditional:
288
285
  rval = self.render_expr(c.lineage.content, cte=cte)
289
286
  else:
290
287
  rval = f"CASE WHEN {self.render_expr(c.lineage.where.conditional, cte=cte)} THEN {self.render_concept_sql(c.lineage.content, cte=cte, alias=False)} ELSE NULL END"
@@ -983,6 +983,7 @@ class ParseToObjects(Transformer):
983
983
  order_by=order_by,
984
984
  meta=Metadata(line_number=meta.line),
985
985
  )
986
+
986
987
  for item in select_items:
987
988
  # we don't know the grain of an aggregate at assignment time
988
989
  # so rebuild at this point in the tree
@@ -1000,21 +1001,43 @@ class ParseToObjects(Transformer):
1000
1001
  )
1001
1002
  self.environment.add_concept(new_concept, meta=meta)
1002
1003
  item.content.output = new_concept
1004
+ # TODO: revisit if we can push down every filter
1005
+ # else:
1006
+ # item.content = (
1007
+ # item.content.with_filter(
1008
+ # output.where_clause.conditional, environment=self.environment
1009
+ # )
1010
+ # if output.where_clause
1011
+ # and output.where_clause_category == SelectFiltering.IMPLICIT
1012
+ # else item.content
1013
+ # )
1014
+
1003
1015
  if order_by:
1004
1016
  for orderitem in order_by.items:
1005
- if (
1006
- isinstance(orderitem.expr, Concept)
1007
- and orderitem.expr.purpose == Purpose.METRIC
1008
- ):
1009
- orderitem.expr = orderitem.expr.with_select_context(
1010
- output.grain,
1011
- conditional=(
1012
- output.where_clause.conditional
1013
- if output.where_clause
1014
- and output.where_clause_category == SelectFiltering.IMPLICIT
1015
- else None
1016
- ),
1017
- )
1017
+ if isinstance(orderitem.expr, Concept):
1018
+ if orderitem.expr.purpose == Purpose.METRIC:
1019
+ orderitem.expr = orderitem.expr.with_select_context(
1020
+ output.grain,
1021
+ conditional=(
1022
+ output.where_clause.conditional
1023
+ if output.where_clause
1024
+ and output.where_clause_category
1025
+ == SelectFiltering.IMPLICIT
1026
+ else None
1027
+ ),
1028
+ environment=self.environment,
1029
+ )
1030
+ # TODO :push down every filter
1031
+ # else:
1032
+ # orderitem.expr = (
1033
+ # orderitem.expr.with_filter(
1034
+ # output.where_clause.conditional,
1035
+ # environment=self.environment,
1036
+ # )
1037
+ # if output.where_clause
1038
+ # and output.where_clause_category == SelectFiltering.IMPLICIT
1039
+ # else orderitem.expr
1040
+ # )
1018
1041
  return output
1019
1042
 
1020
1043
  @v_args(meta=True)
@@ -1237,7 +1260,11 @@ class ParseToObjects(Transformer):
1237
1260
 
1238
1261
  def filter_item(self, args) -> FilterItem:
1239
1262
  where: WhereClause
1240
- string_concept, where = args
1263
+ string_concept, raw = args
1264
+ if isinstance(raw, WhereClause):
1265
+ where = raw
1266
+ else:
1267
+ where = WhereClause(conditional=raw)
1241
1268
  concept = self.environment.concepts[string_concept]
1242
1269
  return FilterItem(content=concept, where=where)
1243
1270
 
@@ -83,7 +83,11 @@
83
83
  raw_function: "bind" "sql" IDENTIFIER "(" function_binding_list ")" "->" data_type "as"i MULTILINE_STRING
84
84
 
85
85
  // user_id where state = Mexico
86
- filter_item: "filter"i IDENTIFIER where
86
+ _filter_alt: IDENTIFIER "?" conditional
87
+ _filter_base: "filter"i IDENTIFIER where
88
+ filter_item: _filter_base | _filter_alt
89
+
90
+
87
91
 
88
92
  // rank/lag/lead
89
93
  WINDOW_TYPE: ("row_number"i|"rank"i|"lag"i|"lead"i | "sum"i) /[\s]+/