pytrilogy 0.0.2.15__py3-none-any.whl → 0.0.2.18__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 (44) hide show
  1. {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/METADATA +12 -8
  2. pytrilogy-0.0.2.18.dist-info/RECORD +83 -0
  3. trilogy/__init__.py +1 -1
  4. trilogy/constants.py +1 -1
  5. trilogy/core/enums.py +1 -0
  6. trilogy/core/functions.py +11 -0
  7. trilogy/core/models.py +105 -59
  8. trilogy/core/optimization.py +15 -9
  9. trilogy/core/processing/concept_strategies_v3.py +372 -145
  10. trilogy/core/processing/node_generators/basic_node.py +27 -55
  11. trilogy/core/processing/node_generators/common.py +6 -7
  12. trilogy/core/processing/node_generators/filter_node.py +28 -31
  13. trilogy/core/processing/node_generators/group_node.py +14 -2
  14. trilogy/core/processing/node_generators/group_to_node.py +3 -1
  15. trilogy/core/processing/node_generators/multiselect_node.py +3 -0
  16. trilogy/core/processing/node_generators/node_merge_node.py +14 -9
  17. trilogy/core/processing/node_generators/rowset_node.py +12 -12
  18. trilogy/core/processing/node_generators/select_merge_node.py +302 -0
  19. trilogy/core/processing/node_generators/select_node.py +7 -511
  20. trilogy/core/processing/node_generators/unnest_node.py +4 -3
  21. trilogy/core/processing/node_generators/window_node.py +12 -37
  22. trilogy/core/processing/nodes/__init__.py +0 -2
  23. trilogy/core/processing/nodes/base_node.py +69 -20
  24. trilogy/core/processing/nodes/filter_node.py +3 -0
  25. trilogy/core/processing/nodes/group_node.py +18 -17
  26. trilogy/core/processing/nodes/merge_node.py +4 -10
  27. trilogy/core/processing/nodes/select_node_v2.py +28 -14
  28. trilogy/core/processing/nodes/window_node.py +1 -2
  29. trilogy/core/processing/utility.py +51 -3
  30. trilogy/core/query_processor.py +17 -73
  31. trilogy/dialect/base.py +8 -3
  32. trilogy/dialect/common.py +65 -10
  33. trilogy/dialect/duckdb.py +4 -1
  34. trilogy/dialect/sql_server.py +3 -3
  35. trilogy/executor.py +5 -0
  36. trilogy/hooks/query_debugger.py +5 -3
  37. trilogy/parsing/parse_engine.py +67 -39
  38. trilogy/parsing/render.py +2 -0
  39. trilogy/parsing/trilogy.lark +6 -3
  40. pytrilogy-0.0.2.15.dist-info/RECORD +0 -82
  41. {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/LICENSE.md +0 -0
  42. {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/WHEEL +0 -0
  43. {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/entry_points.txt +0 -0
  44. {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/top_level.txt +0 -0
@@ -4,9 +4,9 @@ from trilogy.core.env_processor import generate_graph
4
4
  from trilogy.core.graph_models import ReferenceGraph
5
5
  from trilogy.core.constants import CONSTANT_DATASET
6
6
  from trilogy.core.processing.concept_strategies_v3 import source_query_concepts
7
- from trilogy.core.enums import SelectFiltering, BooleanOperator
7
+ from trilogy.core.enums import BooleanOperator
8
8
  from trilogy.constants import CONFIG, DEFAULT_NAMESPACE
9
- from trilogy.core.processing.nodes import GroupNode, SelectNode, StrategyNode, History
9
+ from trilogy.core.processing.nodes import SelectNode, StrategyNode, History
10
10
  from trilogy.core.models import (
11
11
  Concept,
12
12
  Environment,
@@ -193,8 +193,7 @@ def resolve_cte_base_name_and_alias_v2(
193
193
  ) -> Tuple[str | None, str | None]:
194
194
  joins: List[Join] = [join for join in raw_joins if isinstance(join, Join)]
195
195
  if (
196
- len(source.datasources) == 1
197
- and isinstance(source.datasources[0], Datasource)
196
+ isinstance(source.datasources[0], Datasource)
198
197
  and not source.datasources[0].name == CONSTANT_DATASET
199
198
  ):
200
199
  ds = source.datasources[0]
@@ -265,9 +264,7 @@ def datasource_to_ctes(
265
264
  existence_map = source_map
266
265
 
267
266
  human_id = generate_cte_name(query_datasource.full_name, name_map)
268
- logger.info(
269
- f"Finished building source map for {human_id} with {len(parents)} parents, have {source_map}, query_datasource had non-empty keys {[k for k, v in query_datasource.source_map.items() if v]} and existence had non-empty keys {[k for k, v in query_datasource.existence_source_map.items() if v]} "
270
- )
267
+
271
268
  final_joins = [
272
269
  x
273
270
  for x in [base_join_to_join(join, parents) for join in query_datasource.joins]
@@ -317,29 +314,6 @@ def datasource_to_ctes(
317
314
  return output
318
315
 
319
316
 
320
- def append_existence_check(
321
- node: StrategyNode,
322
- environment: Environment,
323
- graph: ReferenceGraph,
324
- history: History | None = None,
325
- ):
326
- # we if we have a where clause doing an existence check
327
- # treat that as separate subquery
328
- if (where := node.conditions) and where.existence_arguments:
329
- for subselect in where.existence_arguments:
330
- if not subselect:
331
- continue
332
-
333
- eds = source_query_concepts(
334
- [*subselect], environment=environment, g=graph, history=history
335
- )
336
- logger.info(
337
- f"{LOGGER_PREFIX} fetching existence clause inputs {[str(c) for c in subselect]}"
338
- )
339
- node.add_parents([eds])
340
- node.add_existence_concepts([*subselect])
341
-
342
-
343
317
  def get_query_node(
344
318
  environment: Environment,
345
319
  statement: SelectStatement | MultiSelectStatement,
@@ -354,16 +328,6 @@ def get_query_node(
354
328
  raise ValueError(f"Statement has no output components {statement}")
355
329
 
356
330
  search_concepts: list[Concept] = statement.output_components
357
- nest_where = statement.where_clause_category == SelectFiltering.IMPLICIT
358
-
359
- # if all are aggregates, we've pushed the filtering inside the aggregates anyway
360
- all_aggregate = all([x.is_aggregate for x in search_concepts])
361
-
362
- if nest_where and statement.where_clause and not all_aggregate:
363
- search_concepts = unique(
364
- statement.where_clause.row_arguments + search_concepts, "address"
365
- )
366
- nest_where = True
367
331
 
368
332
  ods: StrategyNode = source_query_concepts(
369
333
  search_concepts,
@@ -376,45 +340,24 @@ def get_query_node(
376
340
  raise ValueError(
377
341
  f"Could not find source query concepts for {[x.address for x in search_concepts]}"
378
342
  )
379
- ds: StrategyNode
380
- if nest_where and statement.where_clause:
381
- if not all_aggregate:
382
- ods.conditions = statement.where_clause.conditional
383
- ods.set_output_concepts(statement.output_components)
384
- append_existence_check(ods, environment, graph, history)
385
- ds = GroupNode(
386
- output_concepts=statement.output_components,
387
- input_concepts=statement.output_components,
388
- parents=[ods],
389
- environment=ods.environment,
390
- g=ods.g,
391
- partial_concepts=ods.partial_concepts,
392
- )
393
- # we can still check existence here.
394
-
395
- elif statement.where_clause:
396
- ds = SelectNode(
397
- output_concepts=statement.output_components,
398
- input_concepts=ods.input_concepts,
399
- parents=[ods],
400
- environment=ods.environment,
401
- g=ods.g,
402
- partial_concepts=ods.partial_concepts,
403
- conditions=statement.where_clause.conditional,
404
- )
405
- append_existence_check(ds, environment, graph)
406
-
407
- else:
408
- ds = ods
343
+ ds: StrategyNode = ods
409
344
  if statement.having_clause:
345
+ final = statement.having_clause.conditional
410
346
  if ds.conditions:
411
- ds.conditions = Conditional(
347
+ final = Conditional(
412
348
  left=ds.conditions,
413
349
  right=statement.having_clause.conditional,
414
350
  operator=BooleanOperator.AND,
415
351
  )
416
- else:
417
- ds.conditions = statement.having_clause.conditional
352
+ ds = SelectNode(
353
+ output_concepts=statement.output_components,
354
+ input_concepts=ds.output_concepts,
355
+ parents=[ds],
356
+ environment=ds.environment,
357
+ g=ds.g,
358
+ partial_concepts=ds.partial_concepts,
359
+ conditions=final,
360
+ )
418
361
  return ds
419
362
 
420
363
 
@@ -477,6 +420,7 @@ def process_query(
477
420
  hooks: List[BaseHook] | None = None,
478
421
  ) -> ProcessedQuery:
479
422
  hooks = hooks or []
423
+ statement.refresh_bindings(environment)
480
424
  graph = generate_graph(environment)
481
425
  root_datasource = get_query_datasources(
482
426
  environment=environment, graph=graph, statement=statement, hooks=hooks
trilogy/dialect/base.py CHANGED
@@ -121,6 +121,7 @@ FUNCTION_MAP = {
121
121
  FunctionType.CASE: lambda x: render_case(x),
122
122
  FunctionType.SPLIT: lambda x: f"split({x[0]}, {x[1]})",
123
123
  FunctionType.IS_NULL: lambda x: f"isnull({x[0]})",
124
+ FunctionType.BOOL: lambda x: f"CASE WHEN {x[0]} THEN TRUE ELSE FALSE END",
124
125
  # complex
125
126
  FunctionType.INDEX_ACCESS: lambda x: f"{x[0]}[{x[1]}]",
126
127
  FunctionType.MAP_ACCESS: lambda x: f"{x[0]}[{x[1]}][1]",
@@ -176,8 +177,8 @@ FUNCTION_MAP = {
176
177
 
177
178
  FUNCTION_GRAIN_MATCH_MAP = {
178
179
  **FUNCTION_MAP,
179
- FunctionType.COUNT_DISTINCT: lambda args: f"{args[0]}",
180
- FunctionType.COUNT: lambda args: f"{args[0]}",
180
+ FunctionType.COUNT_DISTINCT: lambda args: f"CASE WHEN{args[0]} IS NOT NULL THEN 1 ELSE 0 END",
181
+ FunctionType.COUNT: lambda args: f"CASE WHEN {args[0]} IS NOT NULL THEN 1 ELSE 0 END",
181
182
  FunctionType.SUM: lambda args: f"{args[0]}",
182
183
  FunctionType.AVG: lambda args: f"{args[0]}",
183
184
  FunctionType.MAX: lambda args: f"{args[0]}",
@@ -582,8 +583,11 @@ class BaseDialect:
582
583
  having: Conditional | Parenthetical | Comparison | None = None
583
584
  materialized = {x for x, v in cte.source_map.items() if v}
584
585
  if cte.condition:
585
- if is_scalar_condition(cte.condition, materialized=materialized):
586
+ if not cte.group_to_grain or is_scalar_condition(
587
+ cte.condition, materialized=materialized
588
+ ):
586
589
  where = cte.condition
590
+
587
591
  else:
588
592
  components = decompose_condition(cte.condition)
589
593
  for x in components:
@@ -608,6 +612,7 @@ class BaseDialect:
608
612
  join,
609
613
  self.QUOTE_CHARACTER,
610
614
  self.render_concept_sql,
615
+ self.render_expr,
611
616
  cte,
612
617
  self.UNNEST_MODE,
613
618
  )
trilogy/dialect/common.py CHANGED
@@ -1,6 +1,13 @@
1
- from trilogy.core.models import Join, InstantiatedUnnestJoin, CTE, Concept, Datasource
1
+ from trilogy.core.models import (
2
+ Join,
3
+ InstantiatedUnnestJoin,
4
+ CTE,
5
+ Concept,
6
+ Function,
7
+ RawColumnExpr,
8
+ )
2
9
  from trilogy.core.enums import UnnestMode, Modifier
3
- from typing import Optional, Callable
10
+ from typing import Callable
4
11
 
5
12
 
6
13
  def null_wrapper(lval: str, rval: str, modifiers: list[Modifier]) -> str:
@@ -21,19 +28,39 @@ def render_unnest(
21
28
  return f"{render_func(concept, cte, False)} as unnest_wrapper ({quote_character}{concept.safe_address}{quote_character})"
22
29
 
23
30
 
31
+ def render_join_concept(
32
+ name: str,
33
+ quote_character: str,
34
+ cte: CTE,
35
+ concept: Concept,
36
+ render_expr,
37
+ inlined_ctes: set[str],
38
+ ):
39
+ if cte.name in inlined_ctes:
40
+ ds = cte.source.datasources[0]
41
+ raw_content = ds.get_alias(concept)
42
+ if isinstance(raw_content, RawColumnExpr):
43
+ rval = raw_content.text
44
+ return rval
45
+ elif isinstance(raw_content, Function):
46
+ rval = render_expr(raw_content, cte=cte)
47
+ return rval
48
+ return f"{name}.{quote_character}{raw_content}{quote_character}"
49
+ return f"{name}.{quote_character}{concept.safe_address}{quote_character}"
50
+
51
+
24
52
  def render_join(
25
53
  join: Join | InstantiatedUnnestJoin,
26
54
  quote_character: str,
27
- render_func: Optional[Callable[[Concept, CTE, bool], str]] = None,
28
- cte: Optional[CTE] = None,
55
+ render_func: Callable[[Concept, CTE, bool], str],
56
+ render_expr_func: Callable[[Concept, CTE], str],
57
+ cte: CTE,
29
58
  unnest_mode: UnnestMode = UnnestMode.CROSS_APPLY,
30
59
  ) -> str | None:
31
60
  # {% for key in join.joinkeys %}{{ key.inner }} = {{ key.outer}}{% endfor %}
32
61
  if isinstance(join, InstantiatedUnnestJoin):
33
62
  if unnest_mode == UnnestMode.DIRECT:
34
63
  return None
35
- if not render_func:
36
- raise ValueError("must provide a render function to build an unnest joins")
37
64
  if not cte:
38
65
  raise ValueError("must provide a cte to build an unnest joins")
39
66
  if unnest_mode == UnnestMode.CROSS_JOIN:
@@ -46,8 +73,22 @@ def render_join(
46
73
  right_base = join.right_ref
47
74
  base_joinkeys = [
48
75
  null_wrapper(
49
- f"{left_name}.{quote_character}{join.left_cte.get_alias(key.concept) if isinstance(join.left_cte, Datasource) else key.concept.safe_address}{quote_character}",
50
- f"{right_name}.{quote_character}{join.right_cte.get_alias(key.concept) if isinstance(join.right_cte, Datasource) else key.concept.safe_address}{quote_character}",
76
+ render_join_concept(
77
+ left_name,
78
+ quote_character,
79
+ join.left_cte,
80
+ key.concept,
81
+ render_expr_func,
82
+ join.inlined_ctes,
83
+ ),
84
+ render_join_concept(
85
+ right_name,
86
+ quote_character,
87
+ join.right_cte,
88
+ key.concept,
89
+ render_expr_func,
90
+ join.inlined_ctes,
91
+ ),
51
92
  modifiers=key.concept.modifiers or [],
52
93
  )
53
94
  for key in join.joinkeys
@@ -56,8 +97,22 @@ def render_join(
56
97
  base_joinkeys.extend(
57
98
  [
58
99
  null_wrapper(
59
- f"{left_name}.{quote_character}{join.left_cte.get_alias(pair.left) if isinstance(join.left_cte, Datasource) else pair.left.safe_address}{quote_character}",
60
- f"{right_name}.{quote_character}{join.right_cte.get_alias(pair.right) if isinstance(join.right_cte, Datasource) else pair.right.safe_address}{quote_character}",
100
+ render_join_concept(
101
+ left_name,
102
+ quote_character,
103
+ join.left_cte,
104
+ pair.left,
105
+ render_expr_func,
106
+ join.inlined_ctes,
107
+ ),
108
+ render_join_concept(
109
+ right_name,
110
+ quote_character,
111
+ join.right_cte,
112
+ pair.right,
113
+ render_expr_func,
114
+ join.inlined_ctes,
115
+ ),
61
116
  modifiers=pair.modifiers
62
117
  + (pair.left.modifiers or [])
63
118
  + (pair.right.modifiers or []),
trilogy/dialect/duckdb.py CHANGED
@@ -36,9 +36,12 @@ FUNCTION_MAP = {
36
36
  # we may return a static value
37
37
  FUNCTION_GRAIN_MATCH_MAP = {
38
38
  **FUNCTION_MAP,
39
- FunctionType.COUNT: lambda args: f"{args[0]}",
39
+ FunctionType.COUNT_DISTINCT: lambda args: f"CASE WHEN{args[0]} IS NOT NULL THEN 1 ELSE 0 END",
40
+ FunctionType.COUNT: lambda args: f"CASE WHEN {args[0]} IS NOT NULL THEN 1 ELSE 0 END",
40
41
  FunctionType.SUM: lambda args: f"{args[0]}",
41
42
  FunctionType.AVG: lambda args: f"{args[0]}",
43
+ FunctionType.MAX: lambda args: f"{args[0]}",
44
+ FunctionType.MIN: lambda args: f"{args[0]}",
42
45
  }
43
46
 
44
47
  DUCKDB_TEMPLATE = Template(
@@ -41,9 +41,9 @@ TSQL_TEMPLATE = Template(
41
41
  """{%- if ctes %}
42
42
  WITH {% for cte in ctes %}
43
43
  {{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% endfor %}{% endif %}
44
- {%- if full_select -%}
45
- {{full_select}}
46
- {%- else -%}
44
+ {%- if full_select -%}{{full_select}}
45
+ {%- else -%}{%- if comment %}
46
+ -- {{ comment }}{% endif %}
47
47
  SELECT
48
48
  {%- if limit is not none %}
49
49
  TOP {{ limit }}{% endif %}
trilogy/executor.py CHANGED
@@ -129,6 +129,11 @@ class Executor(object):
129
129
  ["name"],
130
130
  )
131
131
 
132
+ @execute_query.register
133
+ def _(self, query: str) -> CursorResult:
134
+
135
+ return self.execute_text(query)[-1]
136
+
132
137
  @execute_query.register
133
138
  def _(self, query: SelectStatement) -> CursorResult:
134
139
  sql = self.generator.generate_queries(
@@ -31,6 +31,9 @@ def print_recursive_resolved(
31
31
  extra.append("filter")
32
32
  if input.group_required:
33
33
  extra.append("group")
34
+ output = [c.address for c in input.output_concepts[:3]]
35
+ if len(input.output_concepts) > 3:
36
+ output.append("...")
34
37
  display = [
35
38
  (
36
39
  " " * depth,
@@ -40,7 +43,7 @@ def print_recursive_resolved(
40
43
  ">",
41
44
  # [c.address for c in input.input_concepts],
42
45
  "->",
43
- [c.address for c in input.output_concepts],
46
+ output,
44
47
  )
45
48
  ]
46
49
  if isinstance(input, QueryDatasource):
@@ -86,7 +89,6 @@ def print_recursive_ctes(input: CTE, depth: int = 0, max_depth: int | None = Non
86
89
  sql = renderer.render_cte(input).statement
87
90
  for line in sql.split("\n"):
88
91
  logger.debug(" " * (depth) + line)
89
- print("-----")
90
92
  if isinstance(input, CTE):
91
93
  for child in input.parent_ctes:
92
94
  print_recursive_ctes(child, depth + 1)
@@ -130,5 +132,5 @@ class DebuggingHook(BaseHook):
130
132
  if self.process_nodes != PrintMode.OFF:
131
133
  printed = print_recursive_nodes(node, mode=self.process_nodes)
132
134
  for row in printed:
133
- logger.info("".join([str(v) for v in row]))
135
+ # logger.info("".join([str(v) for v in row]))
134
136
  print("".join([str(v) for v in row]))
@@ -30,7 +30,7 @@ from trilogy.core.enums import (
30
30
  WindowType,
31
31
  DatePart,
32
32
  ShowCategory,
33
- SelectFiltering,
33
+ FunctionClass,
34
34
  )
35
35
  from trilogy.core.exceptions import InvalidSyntaxException, UndefinedConceptException
36
36
  from trilogy.core.functions import (
@@ -50,6 +50,7 @@ from trilogy.core.functions import (
50
50
  CurrentDate,
51
51
  CurrentDatetime,
52
52
  IsNull,
53
+ Bool,
53
54
  SubString,
54
55
  StrPos,
55
56
  )
@@ -600,7 +601,7 @@ class ParseToObjects(Transformer):
600
601
  return args[3:-3]
601
602
 
602
603
  def raw_column_assignment(self, args):
603
- return RawColumnExpr(text=args[0])
604
+ return RawColumnExpr(text=args[1])
604
605
 
605
606
  @v_args(meta=True)
606
607
  def datasource(self, meta: Meta, args):
@@ -840,6 +841,7 @@ class ParseToObjects(Transformer):
840
841
  grain: Grain | None = args[3]
841
842
  else:
842
843
  grain = None
844
+
843
845
  new_datasource = select.to_datasource(
844
846
  namespace=(
845
847
  self.environment.namespace
@@ -930,7 +932,8 @@ class ParseToObjects(Transformer):
930
932
  order_by=order_by,
931
933
  meta=Metadata(line_number=meta.line),
932
934
  )
933
-
935
+ locally_derived: set[str] = set()
936
+ all_in_output: set[str] = set()
934
937
  for item in select_items:
935
938
  # we don't know the grain of an aggregate at assignment time
936
939
  # so rebuild at this point in the tree
@@ -938,59 +941,79 @@ class ParseToObjects(Transformer):
938
941
  if isinstance(item.content, ConceptTransform):
939
942
  new_concept = item.content.output.with_select_context(
940
943
  output.grain,
941
- conditional=(
942
- output.where_clause.conditional
943
- if output.where_clause
944
- and output.where_clause_category == SelectFiltering.IMPLICIT
945
- else None
946
- ),
944
+ conditional=None,
945
+ # conditional=(
946
+ # output.where_clause.conditional
947
+ # if output.where_clause
948
+ # and output.where_clause_category == SelectFiltering.IMPLICIT
949
+ # else None
950
+ # ),
947
951
  environment=self.environment,
948
952
  )
949
953
  self.environment.add_concept(new_concept, meta=meta)
950
954
  item.content.output = new_concept
955
+ locally_derived.add(new_concept.address)
956
+ all_in_output.add(new_concept.address)
951
957
  elif isinstance(item.content, Concept):
952
958
  # Sometimes cached values here don't have the latest info
953
- # bug we can't just use environment, as it might not have the right grain.
959
+ # but we can't just use environment, as it might not have the right grain.
954
960
  item.content = self.environment.concepts[
955
961
  item.content.address
956
962
  ].with_grain(item.content.grain)
957
- # TODO: revisit if we can push down every filter
958
- # else:
959
- # item.content = (
960
- # item.content.with_filter(
961
- # output.where_clause.conditional, environment=self.environment
962
- # )
963
- # if output.where_clause
964
- # and output.where_clause_category == SelectFiltering.IMPLICIT
965
- # else item.content
966
- # )
967
-
963
+ all_in_output.add(item.content.address)
968
964
  if order_by:
969
965
  for orderitem in order_by.items:
970
966
  if isinstance(orderitem.expr, Concept):
971
967
  if orderitem.expr.purpose == Purpose.METRIC:
972
968
  orderitem.expr = orderitem.expr.with_select_context(
973
969
  output.grain,
974
- conditional=(
975
- output.where_clause.conditional
976
- if output.where_clause
977
- and output.where_clause_category
978
- == SelectFiltering.IMPLICIT
979
- else None
980
- ),
970
+ conditional=None,
971
+ # conditional=(
972
+ # output.where_clause.conditional
973
+ # if output.where_clause
974
+ # and output.where_clause_category
975
+ # == SelectFiltering.IMPLICIT
976
+ # else None
977
+ # ),
981
978
  environment=self.environment,
982
979
  )
983
- # TODO :push down every filter
984
- # else:
985
- # orderitem.expr = (
986
- # orderitem.expr.with_filter(
987
- # output.where_clause.conditional,
988
- # environment=self.environment,
989
- # )
990
- # if output.where_clause
991
- # and output.where_clause_category == SelectFiltering.IMPLICIT
992
- # else orderitem.expr
993
- # )
980
+ if output.where_clause:
981
+ for concept in output.where_clause.concept_arguments:
982
+
983
+ if (
984
+ concept.lineage
985
+ and isinstance(concept.lineage, Function)
986
+ and concept.lineage.operator
987
+ in FunctionClass.AGGREGATE_FUNCTIONS.value
988
+ ):
989
+ if concept.address in locally_derived:
990
+ raise SyntaxError(
991
+ f"Cannot reference an aggregate derived in the select ({concept.address}) in the same statement where clause; move to the HAVING clause instead; Line: {meta.line}"
992
+ )
993
+
994
+ if (
995
+ concept.lineage
996
+ and isinstance(concept.lineage, AggregateWrapper)
997
+ and concept.lineage.function.operator
998
+ in FunctionClass.AGGREGATE_FUNCTIONS.value
999
+ ):
1000
+ if concept.address in locally_derived:
1001
+ raise SyntaxError(
1002
+ f"Cannot reference an aggregate derived in the select ({concept.address}) in the same statement where clause; move to the HAVING clause instead; Line: {meta.line}"
1003
+ )
1004
+ if output.having_clause:
1005
+ for concept in output.having_clause.concept_arguments:
1006
+ if concept.address not in all_in_output:
1007
+ raise SyntaxError(
1008
+ f"Cannot reference a column ({concept.address}) that is not in the select projection in the HAVING clause, move to WHERE; Line: {meta.line}"
1009
+ )
1010
+ if output.order_by:
1011
+ for concept in output.order_by.concept_arguments:
1012
+ if concept.address not in all_in_output:
1013
+ raise SyntaxError(
1014
+ f"Cannot order by a column that is not in the output projection; {meta.line}"
1015
+ )
1016
+
994
1017
  return output
995
1018
 
996
1019
  @v_args(meta=True)
@@ -1832,6 +1855,11 @@ class ParseToObjects(Transformer):
1832
1855
  args = process_function_args(args, meta=meta, environment=self.environment)
1833
1856
  return IsNull(args)
1834
1857
 
1858
+ @v_args(meta=True)
1859
+ def fbool(self, meta, args):
1860
+ args = process_function_args(args, meta=meta, environment=self.environment)
1861
+ return Bool(args)
1862
+
1835
1863
 
1836
1864
  def unpack_visit_error(e: VisitError):
1837
1865
  """This is required to get exceptions from imports, which would
trilogy/parsing/render.py CHANGED
@@ -328,6 +328,8 @@ class Renderer:
328
328
  inputs = ",".join(self.to_string(c) for c in arg.arguments)
329
329
  if arg.operator == FunctionType.CONSTANT:
330
330
  return f"{inputs}"
331
+ if arg.operator == FunctionType.INDEX_ACCESS:
332
+ return f"{self.to_string(arg.arguments[0])}[{self.to_string(arg.arguments[1])}]"
331
333
  return f"{arg.operator.value}({inputs})"
332
334
 
333
335
  @to_string.register
@@ -47,9 +47,11 @@
47
47
 
48
48
  //column_assignment
49
49
  //figure out if we want static
50
- column_assignment: ((IDENTIFIER | _static_functions | raw_column_assignment ) ":" concept_assignment)
50
+ column_assignment: (raw_column_assignment | IDENTIFIER | _static_functions ) ":" concept_assignment
51
+
52
+ RAW_ENTRY.1: /raw\s*\(/s
51
53
 
52
- raw_column_assignment: "raw" "(" MULTILINE_STRING ")"
54
+ raw_column_assignment: RAW_ENTRY MULTILINE_STRING ")"
53
55
 
54
56
  column_assignment_list : column_assignment ("," column_assignment)* ","?
55
57
 
@@ -185,8 +187,9 @@
185
187
  fcase: "CASE"i (fcase_when)* (fcase_else)? "END"i
186
188
  len: "len"i "(" expr ")"
187
189
  fnot: "NOT"i expr
190
+ fbool: "bool"i "(" expr ")"
188
191
 
189
- _generic_functions: fcast | concat | fcoalesce | fcase | len | fnot
192
+ _generic_functions: fcast | concat | fcoalesce | fcase | len | fnot | fbool
190
193
 
191
194
  //constant
192
195
  CURRENT_DATE.1: /current_date\(\)/
@@ -1,82 +0,0 @@
1
- trilogy/__init__.py,sha256=blYi5mQGhWKBA8TAqC2T8y01GnT4mPEI1MaT2P3gwAI,291
2
- trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- trilogy/constants.py,sha256=Ijos7_TEajKhZ7OJ_TreEYFddW1V33AVymDDrxP-ZHk,1234
4
- trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
5
- trilogy/executor.py,sha256=PZr7IF8wS1Oi2WJGE-B3lp70Y8ue2uuauODw02chjdQ,11175
6
- trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
7
- trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- trilogy/utility.py,sha256=zM__8r29EsyDW7K9VOHz8yvZC2bXFzh7xKy3cL7GKsk,707
9
- trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- trilogy/core/constants.py,sha256=LL8NLvxb3HRnAjvofyLRXqQJijLcYiXAQYQzGarVD-g,128
11
- trilogy/core/enums.py,sha256=BRYqy-NgIacCYTJo0B11m5XQWSHq5pfxhoLd8pzA3ho,6025
12
- trilogy/core/env_processor.py,sha256=l7TAB0LalxjTYJdTlcmFIkLXuyxa9lrenWLeZfa9qw0,2276
13
- trilogy/core/environment_helpers.py,sha256=1miP4is4FEoci01KSAy2VZVYmlmT5TOCOALBekd2muQ,7211
14
- trilogy/core/ergonomics.py,sha256=w3gwXdgrxNHCuaRdyKg73t6F36tj-wIjQf47WZkHmJk,1465
15
- trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,561
16
- trilogy/core/functions.py,sha256=ARJAyBjeS415-54k3G_bx807rkPZonEulMaLRxSP7vU,10371
17
- trilogy/core/graph_models.py,sha256=oJUMSpmYhqXlavckHLpR07GJxuQ8dZ1VbB1fB0KaS8c,2036
18
- trilogy/core/internal.py,sha256=jNGFHKENnbMiMCtAgsnLZYVSENDK4b5ALecXFZpTDzQ,1075
19
- trilogy/core/models.py,sha256=bWCklm8A9I0vx8fXWoN0jKJjLkXuyMUUQetT_zbhyRc,149031
20
- trilogy/core/optimization.py,sha256=7E-Ol51u6ZAxF56F_bzLxgRO-Hu6Yl1ZbPopZJB2tqk,7533
21
- trilogy/core/query_processor.py,sha256=qMVkaK1Lvr9jEftJwAidMdkb_tRx12G07qynEyl91C8,18801
22
- trilogy/core/optimizations/__init__.py,sha256=bWQecbeiwiDx9LJnLsa7dkWxdbl2wcnkcTN69JyP8iI,356
23
- trilogy/core/optimizations/base_optimization.py,sha256=tWWT-xnTbnEU-mNi_isMNbywm8B9WTRsNFwGpeh3rqE,468
24
- trilogy/core/optimizations/inline_constant.py,sha256=kHNyc2UoaPVdYfVAPAFwnWuk4sJ_IF5faRtVcDOrBtw,1110
25
- trilogy/core/optimizations/inline_datasource.py,sha256=AATzQ6YrtW_1-aQFjQyTYqEYKBoMFhek7ADfBr4uUdQ,3634
26
- trilogy/core/optimizations/predicate_pushdown.py,sha256=1l9WnFOSv79e341typG3tTdk0XGl1J_ToQih3LYoGIY,8435
27
- trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- trilogy/core/processing/concept_strategies_v3.py,sha256=ae6FmwiKNiEbOU2GhnzggFMh82MhqxBj9bgr0ituT2w,25633
29
- trilogy/core/processing/graph_utils.py,sha256=aq-kqk4Iado2HywDxWEejWc-7PGO6Oa-ZQLAM6XWPHw,1199
30
- trilogy/core/processing/utility.py,sha256=jFLZmzxHq94q29FInr8XjS5YiqJOPPBYqh8Tlgs432Y,17722
31
- trilogy/core/processing/node_generators/__init__.py,sha256=-mzYkRsaRNa_dfTckYkKVFSR8h8a3ihEiPJDU_tAmDo,672
32
- trilogy/core/processing/node_generators/basic_node.py,sha256=IHj5jEloUe5yojGRLAzt35FcfHqGviWQdS8ETyvr39Q,3292
33
- trilogy/core/processing/node_generators/common.py,sha256=3_Ivrq_wersDZ5pnvyHvsAUc07mRggxRGTiDq47O0Rk,8840
34
- trilogy/core/processing/node_generators/filter_node.py,sha256=gCiv76Cu4idkZRyGkZG44BO50mTqrxYdUSDcZdpd0i4,7724
35
- trilogy/core/processing/node_generators/group_node.py,sha256=G7SrU2X5kjgzeqzzpnPscQBTDcFMc4m7TR6n8VHLC_A,3762
36
- trilogy/core/processing/node_generators/group_to_node.py,sha256=yX0uw6YMxhyWVRMZoMFzEkJe3tB5ByFqrTnuRWVcRh4,2446
37
- trilogy/core/processing/node_generators/multiselect_node.py,sha256=OUjndYjA8xR6yKr-J7R-JxDeYfO6DxmMNNcJiFJzk7g,6138
38
- trilogy/core/processing/node_generators/node_merge_node.py,sha256=D_jsnfoLMrQc08_JvT0wEDvjyzJAxBpdcZFyDN-feV0,13192
39
- trilogy/core/processing/node_generators/rowset_node.py,sha256=tc8jt9bMq_HIdLM24sx_ivc7tTlpucQDEvsC2nkOtrY,4454
40
- trilogy/core/processing/node_generators/select_node.py,sha256=XSMA4kvFdoXlfCpbciXXkbexXkemwUorcAU6P3EwuZY,19843
41
- trilogy/core/processing/node_generators/unnest_node.py,sha256=aZeixbOzMtXi7BPahKr9bOkIhTciyD9Klsj0kZ56F6s,2189
42
- trilogy/core/processing/node_generators/window_node.py,sha256=LSlXe41elFGVRlxRX3MEFimhduGn3o5WE0kLx2JtA4M,3322
43
- trilogy/core/processing/nodes/__init__.py,sha256=jyduHk96j5fpju72sc8swOiBjR3Md866kt8JZGkp3ZU,4866
44
- trilogy/core/processing/nodes/base_node.py,sha256=11Evv2LErwlzCU9ebLWlzldz7VbVMgYiR_sUkVYylKQ,13916
45
- trilogy/core/processing/nodes/filter_node.py,sha256=DBOSGFfkiILrZa1BlLv2uxUSkgWtSIKiZplqyKXPjg8,2132
46
- trilogy/core/processing/nodes/group_node.py,sha256=3zyEs11hv9CoGpO62cUKfClcS58clTUB0IMIkmOV998,6897
47
- trilogy/core/processing/nodes/merge_node.py,sha256=bn7CwZwbYFx-OjNLb9oQuYL_abwAAd_KSSYJGFSEiP8,15022
48
- trilogy/core/processing/nodes/select_node_v2.py,sha256=yoU2PWu-BkiUDECd7V7CKAPjznB_LObpl52HU9Sk5Yc,7433
49
- trilogy/core/processing/nodes/unnest_node.py,sha256=mAmFluzm2yeeiQ6NfIB7BU_8atRGh-UJfPf9ROwbhr8,2152
50
- trilogy/core/processing/nodes/window_node.py,sha256=X7qxLUKd3tekjUUsmH_4vz5b-U89gMnGd04VBxuu2Ns,1280
51
- trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- trilogy/dialect/base.py,sha256=kQek_ufZC9HDVOKlWasvIx6xyew8wv3JNIU6r53_IR4,32842
53
- trilogy/dialect/bigquery.py,sha256=15KJ-cOpBlk9O7FPviPgmg8xIydJeKx7WfmL3SSsPE8,2953
54
- trilogy/dialect/common.py,sha256=QCsqo5morOOL6kwaCYh1RBmaInaoPI6lKtzdgroWvuM,3440
55
- trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
56
- trilogy/dialect/duckdb.py,sha256=u_gpL35kouWxoBLas1h0ABYY2QzlVtEh22hm5h0lCOM,3182
57
- trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
58
- trilogy/dialect/postgres.py,sha256=ev1RJZsC8BB3vJSxJ4q-TTYqZ4Hk1NXUtuRkLrQEBX0,3254
59
- trilogy/dialect/presto.py,sha256=2Rs53UfPxKU0rJTcEbiS-Lxm-CDiqUGojh7yRpQgyRE,3416
60
- trilogy/dialect/snowflake.py,sha256=_Bf4XO7-nImMv9XCSsTfVM3g2f_KHdO17VTa9J-HgSM,2989
61
- trilogy/dialect/sql_server.py,sha256=OtXbm1v6NIGyXeC5i18ojUvLeqescc_Pbv3EASUBB94,3074
62
- trilogy/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
- trilogy/hooks/base_hook.py,sha256=Xkb-A2qCHozYjum0A36zOy5PwTVwrP3NLDF0U2GpgHo,1100
64
- trilogy/hooks/graph_hook.py,sha256=onHvMQPwj_KOS3HOTpRFiy7QLLKAiycq2MzJ_Q0Oh5Y,2467
65
- trilogy/hooks/query_debugger.py,sha256=NDChfkPmmW-KINa4TaQmDe_adGiwsKFdGLDSYpbodeU,4282
66
- trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
- trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
- trilogy/parsing/common.py,sha256=-4LM71ocidA8DI2RngqFEOmhzBrIt8VdBTO4x2BpD8E,9502
69
- trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
70
- trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
71
- trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
72
- trilogy/parsing/parse_engine.py,sha256=_DNpdZOQoZZio_mW8nxcCKoJHny7hnQQtOGG1msoCFU,63265
73
- trilogy/parsing/render.py,sha256=Gy_6wVYPwYLf35Iota08sbqveuWILtUhI8MYStcvtJM,12174
74
- trilogy/parsing/trilogy.lark,sha256=-9y4oVAIlKi-6pE58G4RwGGTBeG7P3T_V4gV8mILd8w,11549
75
- trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
- trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
77
- pytrilogy-0.0.2.15.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
78
- pytrilogy-0.0.2.15.dist-info/METADATA,sha256=u9wzNtWDmhTjKdAmncckSd1lZv1j_Rkefq6kxifmvCI,7907
79
- pytrilogy-0.0.2.15.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
80
- pytrilogy-0.0.2.15.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
81
- pytrilogy-0.0.2.15.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
82
- pytrilogy-0.0.2.15.dist-info/RECORD,,