pytrilogy 0.0.3.55__py3-none-any.whl → 0.0.3.57__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 (39) hide show
  1. {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.57.dist-info}/METADATA +1 -1
  2. {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.57.dist-info}/RECORD +39 -34
  3. {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.57.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +1 -1
  5. trilogy/authoring/__init__.py +12 -1
  6. trilogy/core/enums.py +1 -0
  7. trilogy/core/models/author.py +6 -4
  8. trilogy/core/models/execute.py +4 -1
  9. trilogy/core/optimization.py +4 -4
  10. trilogy/core/processing/concept_strategies_v3.py +324 -895
  11. trilogy/core/processing/discovery_loop.py +0 -0
  12. trilogy/core/processing/discovery_node_factory.py +475 -0
  13. trilogy/core/processing/discovery_utility.py +123 -0
  14. trilogy/core/processing/discovery_validation.py +155 -0
  15. trilogy/core/processing/node_generators/basic_node.py +29 -11
  16. trilogy/core/processing/node_generators/node_merge_node.py +1 -1
  17. trilogy/core/processing/node_generators/select_node.py +6 -8
  18. trilogy/core/processing/node_generators/synonym_node.py +2 -1
  19. trilogy/core/processing/node_generators/unnest_node.py +7 -1
  20. trilogy/core/processing/nodes/__init__.py +2 -4
  21. trilogy/core/processing/nodes/base_node.py +0 -13
  22. trilogy/core/processing/nodes/group_node.py +1 -1
  23. trilogy/core/processing/utility.py +38 -11
  24. trilogy/core/query_processor.py +3 -3
  25. trilogy/core/statements/author.py +6 -2
  26. trilogy/core/statements/execute.py +3 -2
  27. trilogy/dialect/base.py +3 -30
  28. trilogy/dialect/snowflake.py +1 -1
  29. trilogy/executor.py +13 -4
  30. trilogy/parsing/common.py +1 -3
  31. trilogy/parsing/parse_engine.py +14 -2
  32. trilogy/parsing/trilogy.lark +1 -1
  33. trilogy/std/date.preql +3 -1
  34. trilogy/std/geography.preql +4 -0
  35. trilogy/std/money.preql +65 -4
  36. trilogy/std/net.preql +8 -0
  37. {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.57.dist-info}/entry_points.txt +0 -0
  38. {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.57.dist-info}/licenses/LICENSE.md +0 -0
  39. {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.57.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,155 @@
1
+ from collections import defaultdict
2
+ from enum import Enum
3
+ from typing import List
4
+
5
+ from trilogy.core.models.build import (
6
+ BuildConcept,
7
+ BuildWhereClause,
8
+ )
9
+ from trilogy.core.models.build_environment import BuildEnvironment
10
+ from trilogy.core.processing.nodes import (
11
+ StrategyNode,
12
+ )
13
+ from trilogy.core.processing.utility import (
14
+ get_disconnected_components,
15
+ )
16
+
17
+
18
+ class ValidationResult(Enum):
19
+ COMPLETE = 1
20
+ DISCONNECTED = 2
21
+ INCOMPLETE = 3
22
+ INCOMPLETE_CONDITION = 4
23
+
24
+
25
+ def validate_concept(
26
+ concept: BuildConcept,
27
+ node: StrategyNode,
28
+ found_addresses: set[str],
29
+ non_partial_addresses: set[str],
30
+ partial_addresses: set[str],
31
+ virtual_addresses: set[str],
32
+ found_map: dict[str, set[BuildConcept]],
33
+ accept_partial: bool,
34
+ seen: set[str],
35
+ environment: BuildEnvironment,
36
+ ):
37
+ found_map[str(node)].add(concept)
38
+ seen.add(concept.address)
39
+ if concept not in node.partial_concepts:
40
+ found_addresses.add(concept.address)
41
+ non_partial_addresses.add(concept.address)
42
+ # remove it from our partial tracking
43
+ if concept.address in partial_addresses:
44
+ partial_addresses.remove(concept.address)
45
+ if concept.address in virtual_addresses:
46
+ virtual_addresses.remove(concept.address)
47
+ if concept in node.partial_concepts:
48
+ if concept.address in non_partial_addresses:
49
+ return None
50
+ partial_addresses.add(concept.address)
51
+ if accept_partial:
52
+ found_addresses.add(concept.address)
53
+ found_map[str(node)].add(concept)
54
+ for v_address in concept.pseudonyms:
55
+ if v_address in seen:
56
+ return
57
+ v = environment.concepts[v_address]
58
+ if v.address in seen:
59
+ return
60
+ if v.address == concept.address:
61
+ return
62
+ validate_concept(
63
+ v,
64
+ node,
65
+ found_addresses,
66
+ non_partial_addresses,
67
+ partial_addresses,
68
+ virtual_addresses,
69
+ found_map,
70
+ accept_partial,
71
+ seen=seen,
72
+ environment=environment,
73
+ )
74
+
75
+
76
+ def validate_stack(
77
+ environment: BuildEnvironment,
78
+ stack: List[StrategyNode],
79
+ concepts: List[BuildConcept],
80
+ mandatory_with_filter: List[BuildConcept],
81
+ conditions: BuildWhereClause | None = None,
82
+ accept_partial: bool = False,
83
+ ) -> tuple[ValidationResult, set[str], set[str], set[str], set[str]]:
84
+ found_map: dict[str, set[BuildConcept]] = defaultdict(set)
85
+ found_addresses: set[str] = set()
86
+ non_partial_addresses: set[str] = set()
87
+ partial_addresses: set[str] = set()
88
+ virtual_addresses: set[str] = set()
89
+ seen: set[str] = set()
90
+
91
+ for node in stack:
92
+ resolved = node.resolve()
93
+
94
+ for concept in resolved.output_concepts:
95
+ if concept.address in resolved.hidden_concepts:
96
+ continue
97
+
98
+ validate_concept(
99
+ concept,
100
+ node,
101
+ found_addresses,
102
+ non_partial_addresses,
103
+ partial_addresses,
104
+ virtual_addresses,
105
+ found_map,
106
+ accept_partial,
107
+ seen,
108
+ environment,
109
+ )
110
+ for concept in node.virtual_output_concepts:
111
+ if concept.address in non_partial_addresses:
112
+ continue
113
+ found_addresses.add(concept.address)
114
+ virtual_addresses.add(concept.address)
115
+ if not conditions:
116
+ conditions_met = True
117
+ else:
118
+ conditions_met = all(
119
+ [node.preexisting_conditions == conditions.conditional for node in stack]
120
+ ) or all([c.address in found_addresses for c in mandatory_with_filter])
121
+ # zip in those we know we found
122
+ if not all([c.address in found_addresses for c in concepts]) or not conditions_met:
123
+ if not all([c.address in found_addresses for c in concepts]):
124
+ return (
125
+ ValidationResult.INCOMPLETE,
126
+ found_addresses,
127
+ {c.address for c in concepts if c.address not in found_addresses},
128
+ partial_addresses,
129
+ virtual_addresses,
130
+ )
131
+ return (
132
+ ValidationResult.INCOMPLETE_CONDITION,
133
+ found_addresses,
134
+ {c.address for c in concepts if c.address not in mandatory_with_filter},
135
+ partial_addresses,
136
+ virtual_addresses,
137
+ )
138
+
139
+ graph_count, _ = get_disconnected_components(found_map)
140
+ if graph_count in (0, 1):
141
+ return (
142
+ ValidationResult.COMPLETE,
143
+ found_addresses,
144
+ set(),
145
+ partial_addresses,
146
+ virtual_addresses,
147
+ )
148
+ # if we have too many subgraphs, we need to keep searching
149
+ return (
150
+ ValidationResult.DISCONNECTED,
151
+ found_addresses,
152
+ set(),
153
+ partial_addresses,
154
+ virtual_addresses,
155
+ )
@@ -1,7 +1,7 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.enums import FunctionClass, SourceType
4
+ from trilogy.core.enums import FunctionClass, FunctionType, SourceType
5
5
  from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
6
6
  from trilogy.core.models.build_environment import BuildEnvironment
7
7
  from trilogy.core.processing.node_generators.common import (
@@ -47,13 +47,25 @@ def gen_basic_node(
47
47
  logger.info(
48
48
  f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} with lineage {concept.lineage} has parents {[x for x in parent_concepts]}"
49
49
  )
50
-
50
+ synonyms: list[BuildConcept] = []
51
+ ignored_optional: set[str] = set()
52
+ assert isinstance(concept.lineage, BuildFunction)
53
+ if concept.lineage.operator == FunctionType.ATTR_ACCESS:
54
+ logger.info(
55
+ f"{depth_prefix}{LOGGER_PREFIX} checking for synonyms for attribute access"
56
+ )
57
+ for x in local_optional:
58
+ for z in x.pseudonyms:
59
+ s_concept = environment.alias_origin_lookup[z]
60
+ if is_equivalent_basic_function_lineage(concept, s_concept):
61
+ synonyms.append(s_concept)
62
+ ignored_optional.add(x.address)
51
63
  equivalent_optional = [
52
64
  x
53
65
  for x in local_optional
54
66
  if is_equivalent_basic_function_lineage(concept, x)
55
67
  and x.address != concept.address
56
- ]
68
+ ] + synonyms
57
69
 
58
70
  if equivalent_optional:
59
71
  logger.info(
@@ -66,6 +78,7 @@ def gen_basic_node(
66
78
  for x in local_optional
67
79
  if x not in equivalent_optional
68
80
  and not any(x.address in y.pseudonyms for y in equivalent_optional)
81
+ and x.address not in ignored_optional
69
82
  ]
70
83
  all_parents: list[BuildConcept] = unique(
71
84
  parent_concepts + non_equivalent_optional, "address"
@@ -73,7 +86,7 @@ def gen_basic_node(
73
86
  logger.info(
74
87
  f"{depth_prefix}{LOGGER_PREFIX} Fetching parents {[x.address for x in all_parents]}"
75
88
  )
76
- parent_node: StrategyNode = source_concepts(
89
+ parent_node: StrategyNode | None = source_concepts(
77
90
  mandatory_list=all_parents,
78
91
  environment=environment,
79
92
  g=g,
@@ -92,14 +105,19 @@ def gen_basic_node(
92
105
  parent_node.add_output_concept(concept)
93
106
  for x in equivalent_optional:
94
107
  parent_node.add_output_concept(x)
95
-
96
- parent_node.remove_output_concepts(
97
- [
98
- x
99
- for x in parent_node.output_concepts
100
- if x.address not in [concept] + local_optional
101
- ]
108
+ targets = [concept] + local_optional
109
+ logger.info(
110
+ f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
102
111
  )
112
+ should_hide = [
113
+ x
114
+ for x in parent_node.output_concepts
115
+ if (
116
+ x.address not in targets
117
+ and not any(x.address in y.pseudonyms for y in targets)
118
+ )
119
+ ]
120
+ parent_node.hide_output_concepts(should_hide)
103
121
 
104
122
  logger.info(
105
123
  f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
@@ -143,7 +143,7 @@ def determine_induced_minimal_nodes(
143
143
  if not all([node in final.nodes for node in nodelist]):
144
144
  missing = [node for node in nodelist if node not in final.nodes]
145
145
  logger.debug(
146
- f"Skipping graph for {nodelist} as missing nodes {missing} from {final.nodes}"
146
+ f"Skipping graph for initial list {nodelist} as missing nodes {missing} from final graph {final.nodes}"
147
147
  )
148
148
  return None
149
149
  logger.debug(f"Found final graph {final.nodes}")
@@ -19,8 +19,7 @@ LOGGER_PREFIX = "[GEN_SELECT_NODE]"
19
19
 
20
20
 
21
21
  def gen_select_node(
22
- concept: BuildConcept,
23
- local_optional: list[BuildConcept],
22
+ concepts: list[BuildConcept],
24
23
  environment: BuildEnvironment,
25
24
  g,
26
25
  depth: int,
@@ -28,12 +27,11 @@ def gen_select_node(
28
27
  fail_if_not_found: bool = True,
29
28
  conditions: BuildWhereClause | None = None,
30
29
  ) -> StrategyNode | None:
31
- all_concepts = [concept] + local_optional
32
- all_lcl = LooseBuildConceptList(concepts=all_concepts)
30
+ all_lcl = LooseBuildConceptList(concepts=concepts)
33
31
  materialized_lcl = LooseBuildConceptList(
34
32
  concepts=[
35
33
  x
36
- for x in all_concepts
34
+ for x in concepts
37
35
  if x.address in environment.materialized_concepts
38
36
  or x.derivation == Derivation.CONSTANT
39
37
  ]
@@ -41,15 +39,15 @@ def gen_select_node(
41
39
  if materialized_lcl != all_lcl:
42
40
  missing = all_lcl.difference(materialized_lcl)
43
41
  logger.info(
44
- f"{padding(depth)}{LOGGER_PREFIX} Skipping select node generation for {concept.address}"
42
+ f"{padding(depth)}{LOGGER_PREFIX} Skipping select node generation for {concepts}"
45
43
  f" as it + optional includes non-materialized concepts (looking for all {all_lcl}, missing {missing}) "
46
44
  )
47
45
  if fail_if_not_found:
48
- raise NoDatasourceException(f"No datasource exists for {concept}")
46
+ raise NoDatasourceException(f"No datasource exists for {concepts}")
49
47
  return None
50
48
 
51
49
  return gen_select_merge_node(
52
- [concept] + local_optional,
50
+ concepts,
53
51
  g=g,
54
52
  environment=environment,
55
53
  depth=depth,
@@ -29,7 +29,7 @@ def gen_synonym_node(
29
29
  conditions: BuildWhereClause | None = None,
30
30
  accept_partial: bool = False,
31
31
  ) -> StrategyNode | None:
32
- local_prefix = f"[GEN_SYNONYM_NODE] {padding(depth)}"
32
+ local_prefix = f"{padding(depth)}[GEN_SYNONYM_NODE]"
33
33
  base_fingerprint = tuple([x.address for x in all_concepts])
34
34
  synonyms = defaultdict(list)
35
35
  synonym_count = 0
@@ -64,5 +64,6 @@ def gen_synonym_node(
64
64
  )
65
65
  if attempt:
66
66
  logger.info(f"{local_prefix} found inputs with {combo}")
67
+ print(attempt.output_concepts)
67
68
  return attempt
68
69
  return None
@@ -20,16 +20,22 @@ def gen_unnest_node(
20
20
  conditions: BuildWhereClause | None = None,
21
21
  ) -> StrategyNode | None:
22
22
  arguments = []
23
+ depth_prefix = "\t" * depth
23
24
  if isinstance(concept.lineage, BuildFunction):
24
25
  arguments = concept.lineage.concept_arguments
25
26
 
26
27
  equivalent_optional = [x for x in local_optional if x.lineage == concept.lineage]
28
+
27
29
  non_equivalent_optional = [
28
30
  x for x in local_optional if x not in equivalent_optional
29
31
  ]
32
+ all_parents = arguments + non_equivalent_optional
33
+ logger.info(
34
+ f"{depth_prefix}{LOGGER_PREFIX} unnest node for {concept} with lineage {concept.lineage} has parents {all_parents} and equivalent optional {equivalent_optional}"
35
+ )
30
36
  if arguments or local_optional:
31
37
  parent = source_concepts(
32
- mandatory_list=arguments + non_equivalent_optional,
38
+ mandatory_list=all_parents,
33
39
  environment=environment,
34
40
  g=g,
35
41
  depth=depth + 1,
@@ -150,7 +150,6 @@ class History(BaseModel):
150
150
  environment: BuildEnvironment,
151
151
  g,
152
152
  depth: int,
153
- source_concepts,
154
153
  fail_if_not_found: bool = False,
155
154
  accept_partial: bool = False,
156
155
  accept_partial_optional: bool = False,
@@ -169,8 +168,7 @@ class History(BaseModel):
169
168
  if fingerprint in self.select_history:
170
169
  return self.select_history[fingerprint]
171
170
  gen = gen_select_node(
172
- concept,
173
- local_optional,
171
+ [concept] + local_optional,
174
172
  environment,
175
173
  g,
176
174
  depth + 1,
@@ -190,8 +188,8 @@ __all__ = [
190
188
  "WindowNode",
191
189
  "StrategyNode",
192
190
  "NodeJoin",
193
- "ConstantNode",
194
191
  "UnnestNode",
192
+ "ConstantNode",
195
193
  "UnionNode",
196
194
  "History",
197
195
  "WhereSafetyNode",
@@ -311,19 +311,6 @@ class StrategyNode:
311
311
  self.rebuild_cache()
312
312
  return self
313
313
 
314
- def remove_output_concepts(
315
- self, concepts: List[BuildConcept], rebuild: bool = True
316
- ):
317
- for x in concepts:
318
- self.hidden_concepts.add(x.address)
319
- addresses = [x.address for x in concepts]
320
- self.output_concepts = [
321
- x for x in self.output_concepts if x.address not in addresses
322
- ]
323
- if rebuild:
324
- self.rebuild_cache()
325
- return self
326
-
327
314
  @property
328
315
  def usable_outputs(self) -> list[BuildConcept]:
329
316
  return [
@@ -105,7 +105,7 @@ class GroupNode(StrategyNode):
105
105
  if comp_grain.issubset(target_grain):
106
106
 
107
107
  logger.info(
108
- f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, is subset, no grain required"
108
+ f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, grain is subset of target, no group node required"
109
109
  )
110
110
  return GroupRequiredResponse(target_grain, comp_grain, False)
111
111
  # find out what extra is in the comp grain vs target grain
@@ -409,7 +409,7 @@ def get_node_joins(
409
409
 
410
410
 
411
411
  def get_disconnected_components(
412
- concept_map: Dict[str, Set[BuildConcept]]
412
+ concept_map: Dict[str, Set[BuildConcept]],
413
413
  ) -> Tuple[int, List]:
414
414
  """Find if any of the datasources are not linked"""
415
415
  import networkx as nx
@@ -608,8 +608,24 @@ def sort_select_output_processed(
608
608
  mapping = {x.address: x for x in cte.output_columns}
609
609
 
610
610
  new_output: list[BuildConcept] = []
611
- for x in output_addresses:
612
- new_output.append(mapping[x])
611
+ for x in query.output_columns:
612
+ if x.address in mapping:
613
+ new_output.append(mapping[x.address])
614
+ for oc in cte.output_columns:
615
+ if x.address in oc.pseudonyms:
616
+ # create a wrapper BuildConcept to render the pseudonym under the original name
617
+ new_output.append(
618
+ BuildConcept(
619
+ name=x.name,
620
+ namespace=x.namespace,
621
+ pseudonyms={oc.address},
622
+ datatype=oc.datatype,
623
+ purpose=oc.purpose,
624
+ grain=oc.grain,
625
+ build_is_aggregate=oc.build_is_aggregate,
626
+ )
627
+ )
628
+ break
613
629
 
614
630
  for oc in cte.output_columns:
615
631
  # add hidden back
@@ -637,17 +653,28 @@ def sort_select_output(
637
653
  if isinstance(query, ProcessedQuery):
638
654
  return sort_select_output_processed(cte, query)
639
655
 
640
- output_addresses = [
641
- c.address
642
- for c in query.output_components
643
- # if c.address not in query.hidden_components
644
- ]
645
-
646
656
  mapping = {x.address: x for x in cte.output_columns}
647
657
 
648
658
  new_output: list[BuildConcept] = []
649
- for x in output_addresses:
650
- new_output.append(mapping[x])
659
+ for x in query.output_components:
660
+ if x.address in mapping:
661
+ new_output.append(mapping[x.address])
662
+ else:
663
+ for oc in cte.output_columns:
664
+ if x.address in oc.pseudonyms:
665
+ # create a wrapper BuildConcept to render the pseudonym under the original name
666
+ new_output.append(
667
+ BuildConcept(
668
+ name=x.name,
669
+ namespace=x.namespace,
670
+ pseudonyms={oc.address},
671
+ datatype=oc.datatype,
672
+ purpose=oc.purpose,
673
+ grain=oc.grain,
674
+ build_is_aggregate=oc.build_is_aggregate,
675
+ )
676
+ )
677
+ break
651
678
  cte.output_columns = new_output
652
679
  cte.hidden_concepts = set(
653
680
  [
@@ -432,7 +432,7 @@ def get_query_node(
432
432
  )
433
433
  ds = SelectNode(
434
434
  output_concepts=build_statement.output_components,
435
- input_concepts=ds.output_concepts,
435
+ input_concepts=ds.usable_outputs,
436
436
  parents=[ds],
437
437
  environment=ds.environment,
438
438
  partial_concepts=ds.partial_concepts,
@@ -553,11 +553,11 @@ def process_query(
553
553
  root_cte.hidden_concepts = statement.hidden_components
554
554
 
555
555
  final_ctes = optimize_ctes(deduped_ctes, root_cte, statement)
556
- mapping = {x.address: x for x in cte.output_columns}
556
+
557
557
  return ProcessedQuery(
558
558
  order_by=root_cte.order_by,
559
559
  limit=statement.limit,
560
- output_columns=[mapping[x.address] for x in statement.output_components],
560
+ output_columns=statement.output_components,
561
561
  ctes=final_ctes,
562
562
  base=root_cte,
563
563
  hidden_columns=set([x for x in statement.hidden_components]),
@@ -5,7 +5,7 @@ from typing import Annotated, List, Optional, Union
5
5
  from pydantic import BaseModel, Field, computed_field, field_validator
6
6
  from pydantic.functional_validators import PlainValidator
7
7
 
8
- from trilogy.constants import CONFIG
8
+ from trilogy.constants import CONFIG, DEFAULT_NAMESPACE
9
9
  from trilogy.core.enums import (
10
10
  ConceptSource,
11
11
  FunctionClass,
@@ -281,7 +281,11 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
281
281
  # if the concept is a locally derived concept, it cannot ever be partial
282
282
  # but if it's a concept pulled in from upstream and we have a where clause, it should be partial
283
283
  ColumnAssignment(
284
- alias=c.address.replace(".", "_"),
284
+ alias=(
285
+ c.address.replace(".", "_")
286
+ if c.namespace != DEFAULT_NAMESPACE
287
+ else c.name
288
+ ),
285
289
  concept=environment.concepts[c.address].reference,
286
290
  modifiers=modifiers if c.address not in self.locally_derived else [],
287
291
  )
@@ -3,6 +3,7 @@ from typing import Annotated, List, Optional, Union
3
3
  from pydantic import BaseModel, Field
4
4
  from pydantic.functional_validators import PlainValidator
5
5
 
6
+ from trilogy.core.models.author import ConceptRef
6
7
  from trilogy.core.models.build import (
7
8
  BuildConcept,
8
9
  BuildDatasource,
@@ -14,7 +15,7 @@ from trilogy.core.statements.common import CopyQueryMixin, PersistQueryMixin
14
15
 
15
16
 
16
17
  class ProcessedQuery(BaseModel):
17
- output_columns: List[BuildConcept]
18
+ output_columns: List[ConceptRef]
18
19
  ctes: List[CTE | UnionCTE]
19
20
  base: CTE | UnionCTE
20
21
  hidden_columns: set[str] = Field(default_factory=set)
@@ -38,5 +39,5 @@ class ProcessedRawSQLStatement(BaseModel):
38
39
 
39
40
 
40
41
  class ProcessedShowStatement(BaseModel):
41
- output_columns: List[BuildConcept]
42
+ output_columns: List[ConceptRef]
42
43
  output_values: List[Union[BuildConcept, BuildDatasource, ProcessedQuery]]
trilogy/dialect/base.py CHANGED
@@ -33,7 +33,6 @@ from trilogy.core.models.build import (
33
33
  BuildRowsetItem,
34
34
  BuildSubselectComparison,
35
35
  BuildWindowItem,
36
- Factory,
37
36
  )
38
37
  from trilogy.core.models.core import (
39
38
  DataType,
@@ -904,7 +903,6 @@ class BaseDialect:
904
903
  | ProcessedRawSQLStatement
905
904
  | ProcessedCopyStatement
906
905
  ] = []
907
- factory = Factory(environment=environment)
908
906
  for statement in statements:
909
907
  if isinstance(statement, PersistStatement):
910
908
  if hooks:
@@ -939,11 +937,9 @@ class BaseDialect:
939
937
  output.append(
940
938
  ProcessedShowStatement(
941
939
  output_columns=[
942
- factory.build(
943
- environment.concepts[
944
- DEFAULT_CONCEPTS["query_text"].address
945
- ]
946
- )
940
+ environment.concepts[
941
+ DEFAULT_CONCEPTS["query_text"].address
942
+ ].reference
947
943
  ],
948
944
  output_values=[
949
945
  process_query(
@@ -984,29 +980,6 @@ class BaseDialect:
984
980
  return ";\n".join([str(x) for x in query.output_values])
985
981
  elif isinstance(query, ProcessedRawSQLStatement):
986
982
  return query.text
987
- select_columns: Dict[str, str] = {}
988
- cte_output_map = {}
989
- selected = set()
990
- output_addresses = [
991
- c.address
992
- for c in query.output_columns
993
- if c.address not in query.hidden_columns
994
- ]
995
-
996
- for c in query.base.output_columns:
997
- if c.address not in selected:
998
- select_columns[c.address] = (
999
- f"{query.base.name}.{safe_quote(c.safe_address, self.QUOTE_CHARACTER)}"
1000
- )
1001
- cte_output_map[c.address] = query.base
1002
- if c.address not in query.hidden_columns:
1003
- selected.add(c.address)
1004
- if not all([x in selected for x in output_addresses]):
1005
- missing = [x for x in output_addresses if x not in selected]
1006
- raise ValueError(
1007
- f"Did not get all output addresses in select - missing: {missing}, have"
1008
- f" {selected}"
1009
- )
1010
983
 
1011
984
  recursive = any(isinstance(x, RecursiveCTE) for x in query.ctes)
1012
985
 
@@ -47,7 +47,7 @@ SNOWFLAKE_SQL_TEMPLATE = Template(
47
47
  CREATE OR REPLACE TABLE {{ output.address.location }} AS
48
48
  {% endif %}{%- if ctes %}
49
49
  WITH {% if recursive%}RECURSIVE{% endif %}{% for cte in ctes %}
50
- {{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% else %}
50
+ "{{cte.name}}" as ({{cte.statement}}){% if not loop.last %},{% endif %}{% else %}
51
51
  {% endfor %}{% endif %}
52
52
  {%- if full_select -%}
53
53
  {{full_select}}
trilogy/executor.py CHANGED
@@ -8,8 +8,8 @@ from sqlalchemy.engine import CursorResult
8
8
 
9
9
  from trilogy.constants import Rendering, logger
10
10
  from trilogy.core.enums import FunctionType, Granularity, IOType
11
- from trilogy.core.models.author import Concept, Function
12
- from trilogy.core.models.build import BuildConcept, BuildFunction
11
+ from trilogy.core.models.author import Concept, ConceptRef, Function
12
+ from trilogy.core.models.build import BuildFunction
13
13
  from trilogy.core.models.core import ListWrapper, MapWrapper
14
14
  from trilogy.core.models.datasource import Datasource
15
15
  from trilogy.core.models.environment import Environment
@@ -61,7 +61,7 @@ class MockResult:
61
61
 
62
62
 
63
63
  def generate_result_set(
64
- columns: List[BuildConcept], output_data: list[Any]
64
+ columns: List[ConceptRef], output_data: list[Any]
65
65
  ) -> MockResult:
66
66
  names = [x.address.replace(".", "_") for x in columns]
67
67
  return MockResult(
@@ -90,7 +90,16 @@ class Executor(object):
90
90
  if self.dialect == Dialects.DATAFRAME:
91
91
  self.engine.setup(self.environment, self.connection)
92
92
 
93
- def execute_statement(self, statement) -> Optional[CursorResult]:
93
+ def execute_statement(
94
+ self,
95
+ statement: (
96
+ ProcessedQuery
97
+ | ProcessedCopyStatement
98
+ | ProcessedRawSQLStatement
99
+ | ProcessedQueryPersist
100
+ | ProcessedShowStatement
101
+ ),
102
+ ) -> Optional[CursorResult]:
94
103
  if not isinstance(
95
104
  statement,
96
105
  (
trilogy/parsing/common.py CHANGED
@@ -397,7 +397,7 @@ def group_function_to_concept(
397
397
  modifiers=modifiers,
398
398
  grain=grain,
399
399
  metadata=fmetadata,
400
- derivation=Derivation.BASIC,
400
+ derivation=Derivation.GROUP_TO,
401
401
  granularity=granularity,
402
402
  )
403
403
  return r
@@ -654,7 +654,6 @@ def agg_wrapper_to_concept(
654
654
  fmetadata = metadata or Metadata()
655
655
  aggfunction = parent.function
656
656
  modifiers = get_upstream_modifiers(parent.concept_arguments, environment)
657
- # derivation = Concept.calculate_derivation(parent, Purpose.PROPERTY)
658
657
  grain = Grain.from_concepts(parent.by, environment) if parent.by else Grain()
659
658
  granularity = Concept.calculate_granularity(Derivation.AGGREGATE, grain, parent)
660
659
 
@@ -778,7 +777,6 @@ def rowset_to_concepts(rowset: RowsetDerivationStatement, environment: Environme
778
777
  for x in pre_output:
779
778
  x.lineage = RowsetItem(
780
779
  content=orig_map[x.address].reference,
781
- # where=rowset.select.where_clause,
782
780
  rowset=RowsetLineage(
783
781
  name=rowset.name,
784
782
  derived_concepts=[x.reference for x in pre_output],