pytrilogy 0.0.3.20__py3-none-any.whl → 0.0.3.22__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
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.20
3
+ Version: 0.0.3.22
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -31,6 +31,7 @@ Dynamic: author-email
31
31
  Dynamic: classifier
32
32
  Dynamic: description
33
33
  Dynamic: description-content-type
34
+ Dynamic: license-file
34
35
  Dynamic: provides-extra
35
36
  Dynamic: requires-dist
36
37
  Dynamic: summary
@@ -1,11 +1,12 @@
1
- trilogy/__init__.py,sha256=D0yQKpNnrX3HHQADMjE2XqRYe5vQ_m9_eUHfx-spBqs,303
1
+ pytrilogy-0.0.3.22.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=1qcH3mcGJGfd8RueLao8BkMgttF4E-nwXO82_8H_zUg,303
2
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- trilogy/constants.py,sha256=qZ1d0hoKPPV2HHCoFwPYTVB7b6bXjpWvXd3lE-zEhy8,1494
4
+ trilogy/constants.py,sha256=5eQxk1A0pv-TQk3CCvgZCFA9_K-6nxrOm7E5Lxd7KIY,1652
4
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
5
- trilogy/executor.py,sha256=CU-T7hl5hQab17KkJz9XhwlyI4-7MQL-JGdTDMVsE4E,16025
6
+ trilogy/executor.py,sha256=_xihzIaUEbE5lzwHECsvQ75Dm5fdRPBdMCVz6gNBpV4,16091
6
7
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
7
8
  trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- trilogy/render.py,sha256=D6rI1RNtn0StJeSe4e18lnlc-U--cNu4lh5C_NkU_uM,1218
9
+ trilogy/render.py,sha256=qQWwduymauOlB517UtM-VGbVe8Cswa4UJub5aGbSO6c,1512
9
10
  trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
10
11
  trilogy/authoring/__init__.py,sha256=ohkYA3_LGYZh3fwzEYKTN6ofACDI5GYl3VCbGxVvlzY,2233
11
12
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -27,14 +28,14 @@ trilogy/core/models/build_environment.py,sha256=8UggvlPU708GZWYPJMc_ou2r7M3TY2g6
27
28
  trilogy/core/models/core.py,sha256=nb4h1HHm5_qwmUkYth4zRhEttS1EtsMZCP4vT20EEAE,10326
28
29
  trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
29
30
  trilogy/core/models/environment.py,sha256=RlHNrRer4p1uSQM030iwGJL82M1hMyY5p8a550XTfUI,26606
30
- trilogy/core/models/execute.py,sha256=4jbfwRt6Qv0kNzVU8b_z10Ln0Nk-CDvnbMEP1gCAbck,34204
31
+ trilogy/core/models/execute.py,sha256=SG_qXK3hrorzcXi85iie1Z5FGnrSwCE4Zs9Ntj4Q1ok,34223
31
32
  trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
32
33
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
33
34
  trilogy/core/optimizations/inline_constant.py,sha256=lvNTIXaLNkw3HseJyXyDNk5R52doLU9sIg3pmU2_S08,1332
34
35
  trilogy/core/optimizations/inline_datasource.py,sha256=AHuTGh2x0GQ8usOe0NiFncfTFQ_KogdgDl4uucmhIbI,4241
35
36
  trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
36
37
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- trilogy/core/processing/concept_strategies_v3.py,sha256=yZAAyrdLBhArAX87SWAqTB0E3DRt0I5XuQ3or2FHyas,40302
38
+ trilogy/core/processing/concept_strategies_v3.py,sha256=pDajPY0b3fa8qgg2jR6Qj30RMu-OBLztlsVRTgGJbeU,40500
38
39
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
39
40
  trilogy/core/processing/utility.py,sha256=Oc5tLGeDDpzhbfo2ZcF8ex1kez-NcJDMcG2Lm5BjS4c,20548
40
41
  trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW6gjUJCsiXumdbJNozHUf-Y,800
@@ -64,18 +65,18 @@ trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ
64
65
  trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
65
66
  trilogy/core/processing/nodes/window_node.py,sha256=STvwheVttxSWVHB-yUQUSo-Pyz7Uk8G1txFDAbWMp-s,1380
66
67
  trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
- trilogy/core/statements/author.py,sha256=1cJ4nIBInTD3YcS89yTBda2ypghIhMvb-Mut66m_sxM,14615
68
+ trilogy/core/statements/author.py,sha256=sPrItQEKXzE7IR3SGOTVN4OBvGHldUCqXCzbcmEpb7I,14575
68
69
  trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
70
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
70
71
  trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
71
72
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
- trilogy/dialect/base.py,sha256=FE3JX0Q5c2ypJ-cweHFbwgHXYUN1zeDJ2rvZBr0hBPk,40415
73
+ trilogy/dialect/base.py,sha256=QxBqbMop8l1eD36RNxSNL8XWd0mbI71FryvW1XK5NuQ,40797
73
74
  trilogy/dialect/bigquery.py,sha256=PkjFcNGZHYOe655PmJhb8a0afdFULuovqP0qQVO8m0I,2953
74
- trilogy/dialect/common.py,sha256=oZr4EKYItbCeVA-vw-Q6Tv2-xy54s9vWaY6gevuQJIc,4619
75
- trilogy/dialect/config.py,sha256=EGYRQIbrkeMuud5Bkds7jSD5dCJR5hEYZUYcy-lYZl4,3308
75
+ trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
76
+ trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
76
77
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
77
78
  trilogy/dialect/duckdb.py,sha256=TepCOhYWYw1oUuOT6ZGlB3l4X6S8rYcldWe3zZm3HoU,3710
78
- trilogy/dialect/enums.py,sha256=QYIcVr5RgpYMA1Wl0nWeojVVxJxy0V2_sn8uqSFNx20,4615
79
+ trilogy/dialect/enums.py,sha256=FRNYQ5-w-B6-X0yXKNU5g9GowsMlERFogTC5u2nxL_s,4740
79
80
  trilogy/dialect/postgres.py,sha256=VH4EB4myjIeZTHeFU6vK00GxY9c53rCBjg2mLbdaCEE,3254
80
81
  trilogy/dialect/presto.py,sha256=Mw7_F8h19mWfuZHkHQJizQWbpu1lIHe6t2PA0r88gsY,3392
81
82
  trilogy/dialect/snowflake.py,sha256=vc0374Og0O5OIB7-Z7jbwoJJg0iomjvnUqHlxM8B0rg,3120
@@ -90,14 +91,13 @@ trilogy/parsing/common.py,sha256=99tDKrpQTk-SpyTXUqKFtm-lfmhjCOQIn25hxbQvRRg,214
90
91
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
91
92
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
92
93
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
93
- trilogy/parsing/parse_engine.py,sha256=kohD9ZTR8Atxd-Kctqsuu_QnLVJIz1xTs_6kmEFNa8U,59792
94
+ trilogy/parsing/parse_engine.py,sha256=UiTrjU6Lpp25S-wly_pNS0wUYEsZpjHExxduArtE1vQ,60602
94
95
  trilogy/parsing/render.py,sha256=o_XuQWhcwx1lD9eGVqkqZEwkmQK0HdmWWokGBtdeH4I,17837
95
96
  trilogy/parsing/trilogy.lark,sha256=7libFS5HNiyHJYzr5_lEiY-Lpqit04_PgyIPHMZT7-w,12928
96
97
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
98
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
98
- pytrilogy-0.0.3.20.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
99
- pytrilogy-0.0.3.20.dist-info/METADATA,sha256=LD5aYI8WQnZwSVATyt5cCjPv674PS1ca3TB2ZaS5jGE,9078
100
- pytrilogy-0.0.3.20.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
101
- pytrilogy-0.0.3.20.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
102
- pytrilogy-0.0.3.20.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
- pytrilogy-0.0.3.20.dist-info/RECORD,,
99
+ pytrilogy-0.0.3.22.dist-info/METADATA,sha256=hRYKLlss7MfsGa488K07_o93KgVoI77O7QBqJ30vR4I,9100
100
+ pytrilogy-0.0.3.22.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
101
+ pytrilogy-0.0.3.22.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
102
+ pytrilogy-0.0.3.22.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
+ pytrilogy-0.0.3.22.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ trilogy = trilogy.scripts.trilogy:cli
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.20"
7
+ __version__ = "0.0.3.22"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/constants.py CHANGED
@@ -47,6 +47,14 @@ class Rendering:
47
47
  concise: bool = False
48
48
 
49
49
 
50
+ @dataclass
51
+ class Parsing:
52
+ """Control Parsing"""
53
+
54
+ strict_name_shadow_enforcement: bool = False
55
+ select_as_definition: bool = True
56
+
57
+
50
58
  # TODO: support loading from environments
51
59
  @dataclass
52
60
  class Config:
@@ -57,7 +65,7 @@ class Config:
57
65
  comments: Comments = field(default_factory=Comments)
58
66
  optimizations: Optimizations = field(default_factory=Optimizations)
59
67
  rendering: Rendering = field(default_factory=Rendering)
60
- select_as_definition: bool = True
68
+ parsing: Parsing = field(default_factory=Parsing)
61
69
 
62
70
  @property
63
71
  def show_comments(self) -> bool:
@@ -162,7 +162,7 @@ class CTE(BaseModel):
162
162
  base += (
163
163
  f"\n-- Nullable: {', '.join([str(x) for x in self.nullable_concepts])}."
164
164
  )
165
-
165
+ base +='\n'
166
166
  return base
167
167
 
168
168
  def inline_parent_datasource(
@@ -787,15 +787,17 @@ def _search_concepts(
787
787
  )
788
788
  # if anything we need to get is in the filter set and it's a computed value
789
789
  # we need to get _everything_ in this loop
790
- if any(
791
- [
792
- x.derivation not in (Derivation.ROOT, Derivation.CONSTANT)
790
+ required_filters = [
791
+ x
792
+ for x in mandatory_list if x.derivation not in (Derivation.ROOT, Derivation.CONSTANT)
793
+ and not (x.derivation == Derivation.AGGREGATE and x.granularity == Granularity.SINGLE_ROW)
793
794
  and x.address in conditions.row_arguments
794
- for x in mandatory_list
795
795
  ]
796
+ if any(
797
+ required_filters
796
798
  ):
797
799
  logger.info(
798
- f"{depth_to_prefix(depth)}{LOGGER_PREFIX} derived condition row input present in mandatory list, forcing condition evaluation at this level. "
800
+ f"{depth_to_prefix(depth)}{LOGGER_PREFIX} derived condition row inputs {[x.address for x in required_filters]} present in mandatory list, forcing condition evaluation at this level. "
799
801
  )
800
802
  mandatory_list = completion_mandatory
801
803
  must_evaluate_condition_on_this_level_not_push_down = True
@@ -142,7 +142,7 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
142
142
  if isinstance(x.content.output, UndefinedConcept):
143
143
  continue
144
144
  if (
145
- CONFIG.select_as_definition
145
+ CONFIG.parsing.select_as_definition
146
146
  and not environment.frozen
147
147
  and x.concept.address not in environment.concepts
148
148
  ):
@@ -156,7 +156,7 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
156
156
  elif isinstance(x.content, ConceptRef):
157
157
  output.local_concepts[x.content.address] = environment.concepts[
158
158
  x.content.address
159
- ] # .set_select_grain(output.grain, environment)
159
+ ]
160
160
  output.validate_syntax(environment)
161
161
  return output
162
162
 
trilogy/dialect/base.py CHANGED
@@ -3,7 +3,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Union
3
3
 
4
4
  from jinja2 import Template
5
5
 
6
- from trilogy.constants import CONFIG, MagicConstants, logger
6
+ from trilogy.constants import CONFIG, MagicConstants, Rendering, logger
7
7
  from trilogy.core.enums import (
8
8
  DatePart,
9
9
  FunctionType,
@@ -283,6 +283,9 @@ class BaseDialect:
283
283
  DATATYPE_MAP = DATATYPE_MAP
284
284
  UNNEST_MODE = UnnestMode.CROSS_APPLY
285
285
 
286
+ def __init__(self, rendering: Rendering | None = None):
287
+ self.rendering = rendering or CONFIG.rendering
288
+
286
289
  def render_order_item(
287
290
  self,
288
291
  order_item: BuildOrderItem,
@@ -416,7 +419,7 @@ class BaseDialect:
416
419
  elif (
417
420
  isinstance(c.lineage, FUNCTION_ITEMS)
418
421
  and c.lineage.operator == FunctionType.CONSTANT
419
- and CONFIG.rendering.parameters is True
422
+ and self.rendering.parameters is True
420
423
  and c.datatype.data_type != DataType.MAP
421
424
  ):
422
425
  rval = f":{c.safe_address}"
@@ -633,7 +636,7 @@ class BaseDialect:
633
636
  if (
634
637
  isinstance(e.lineage, FUNCTION_ITEMS)
635
638
  and e.lineage.operator == FunctionType.CONSTANT
636
- and CONFIG.rendering.parameters is True
639
+ and self.rendering.parameters is True
637
640
  and e.datatype.data_type != DataType.MAP
638
641
  ):
639
642
  return f":{e.safe_address}"
@@ -723,9 +726,13 @@ class BaseDialect:
723
726
  UnnestMode.CROSS_JOIN_ALIAS,
724
727
  UnnestMode.CROSS_JOIN,
725
728
  UnnestMode.CROSS_APPLY,
726
- UnnestMode.SNOWFLAKE,
727
729
  ):
728
730
 
731
+ source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_concept_sql, cte)}"
732
+ elif (
733
+ cte.join_derived_concepts
734
+ and self.UNNEST_MODE == UnnestMode.SNOWFLAKE
735
+ ):
729
736
  source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_concept_sql, cte)}"
730
737
  # direct - eg DUCK DB - can be directly selected inline
731
738
  elif (
trilogy/dialect/common.py CHANGED
@@ -28,7 +28,10 @@ def render_unnest(
28
28
  elif unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
29
29
  return f"{render_func(concept, cte, False)} as unnest_wrapper ({quote_character}{concept.safe_address}{quote_character})"
30
30
  elif unnest_mode == UnnestMode.SNOWFLAKE:
31
-
31
+ # if we don't actually have a join, we're directly unnesting a concept, and we can skip the flatten
32
+ if not cte.render_from_clause:
33
+ return f"{render_func(concept, cte, False)} as unnest_wrapper ( unnest1, unnest2, unnest3, unnest4, {quote_character}{cte.join_derived_concepts[0].safe_address}{quote_character})"
34
+ # otherwise, flatten the concept for the join
32
35
  return f"flatten({render_func(concept, cte, False)}) as unnest_wrapper ( unnest1, unnest2, unnest3, unnest4, {quote_character}{cte.join_derived_concepts[0].safe_address}{quote_character})"
33
36
  return f"{render_func(concept, cte, False)} as {quote_character}{concept.safe_address}{quote_character}"
34
37
 
trilogy/dialect/config.py CHANGED
@@ -76,12 +76,22 @@ class SnowflakeConfig(DialectConfig):
76
76
  account: str,
77
77
  username: str,
78
78
  password: str,
79
+ database: str | None = None,
80
+ schema: str | None = None,
79
81
  ):
80
82
  self.account = account
81
83
  self.username = username
82
84
  self.password = password
85
+ self.database = database
86
+ self.schema = schema
87
+ if self.schema and not self.database:
88
+ raise ValueError("Setting snowflake schema also requires setting database")
83
89
 
84
90
  def connection_string(self) -> str:
91
+ if self.schema:
92
+ return f"snowflake://{self.username}:{self.password}@{self.account}/{self.database}/{self.schema}"
93
+ if self.database:
94
+ return f"snowflake://{self.username}:{self.password}@{self.account}/{self.database}"
85
95
  return f"snowflake://{self.username}:{self.password}@{self.account}"
86
96
 
87
97
 
trilogy/dialect/enums.py CHANGED
@@ -7,7 +7,7 @@ if TYPE_CHECKING:
7
7
  from trilogy import Executor
8
8
  from trilogy.hooks.base_hook import BaseHook
9
9
 
10
- from trilogy.constants import logger
10
+ from trilogy.constants import Rendering, logger
11
11
  from trilogy.dialect.config import DialectConfig
12
12
 
13
13
 
@@ -114,6 +114,7 @@ class Dialects(Enum):
114
114
  environment: Optional["Environment"] = None,
115
115
  hooks: List["BaseHook"] | None = None,
116
116
  conf: DialectConfig | None = None,
117
+ rendering: Rendering | None = None,
117
118
  _engine_factory: Callable | None = None,
118
119
  ) -> "Executor":
119
120
  from trilogy import Executor
@@ -123,6 +124,7 @@ class Dialects(Enum):
123
124
  engine=self.default_engine(conf=conf, _engine_factory=_engine_factory),
124
125
  environment=environment or Environment(),
125
126
  dialect=self,
127
+ rendering=rendering,
126
128
  hooks=hooks,
127
129
  )
128
130
 
@@ -130,5 +132,6 @@ class Dialects(Enum):
130
132
  engine=self.default_engine(conf=conf),
131
133
  environment=environment or Environment(),
132
134
  dialect=self,
135
+ rendering=rendering,
133
136
  hooks=hooks,
134
137
  )
trilogy/executor.py CHANGED
@@ -6,7 +6,7 @@ from typing import Any, Generator, List, Optional, Protocol
6
6
  from sqlalchemy import text
7
7
  from sqlalchemy.engine import CursorResult
8
8
 
9
- from trilogy.constants import logger
9
+ from trilogy.constants import Rendering, logger
10
10
  from trilogy.core.enums import FunctionType, Granularity, IOType
11
11
  from trilogy.core.models.author import Concept, Function
12
12
  from trilogy.core.models.build import BuildConcept, BuildFunction
@@ -75,6 +75,7 @@ class Executor(object):
75
75
  dialect: Dialects,
76
76
  engine: ExecutionEngine,
77
77
  environment: Optional[Environment] = None,
78
+ rendering: Rendering | None = None,
78
79
  hooks: List[BaseHook] | None = None,
79
80
  ):
80
81
  self.dialect: Dialects = dialect
@@ -83,7 +84,7 @@ class Executor(object):
83
84
  self.generator: BaseDialect
84
85
  self.logger = logger
85
86
  self.hooks = hooks
86
- self.generator = get_dialect_generator(self.dialect)
87
+ self.generator = get_dialect_generator(self.dialect, rendering)
87
88
  self.connection = self.engine.connect()
88
89
  # TODO: make generic
89
90
  if self.dialect == Dialects.DATAFRAME:
@@ -18,9 +18,11 @@ from lark.tree import Meta
18
18
  from pydantic import ValidationError
19
19
 
20
20
  from trilogy.constants import (
21
+ CONFIG,
21
22
  DEFAULT_NAMESPACE,
22
23
  NULL_VALUE,
23
24
  MagicConstants,
25
+ Parsing,
24
26
  )
25
27
  from trilogy.core.enums import (
26
28
  BooleanOperator,
@@ -282,6 +284,7 @@ class ParseToObjects(Transformer):
282
284
  text_lookup: dict[Path | str, str] | None = None,
283
285
  environment_lookup: dict[str, Environment] | None = None,
284
286
  import_keys: list[str] | None = None,
287
+ parse_config: Parsing | None = None,
285
288
  ):
286
289
  Transformer.__init__(self, True)
287
290
  self.environment: Environment = environment
@@ -298,6 +301,7 @@ class ParseToObjects(Transformer):
298
301
  self.parse_pass = ParsePass.INITIAL
299
302
  self.function_factory = FunctionFactory(self.environment)
300
303
  self.import_keys: list[str] = import_keys or ["root"]
304
+ self.parse_config: Parsing = parse_config or CONFIG.parsing
301
305
 
302
306
  def set_text(self, text: str):
303
307
  self.text_lookup[self.token_address] = text
@@ -1014,6 +1018,7 @@ class ParseToObjects(Transformer):
1014
1018
  tokens=self.tokens,
1015
1019
  text_lookup=self.text_lookup,
1016
1020
  import_keys=self.import_keys + [cache_key],
1021
+ parse_config=self.parse_config,
1017
1022
  )
1018
1023
  nparser.transform(raw_tokens)
1019
1024
  self.parsed[cache_lookup] = nparser
@@ -1148,8 +1153,8 @@ class ParseToObjects(Transformer):
1148
1153
  elif isinstance(arg, HavingClause):
1149
1154
  having = arg
1150
1155
  if not select_items:
1151
- raise ValueError("Malformed select, missing select items")
1152
-
1156
+ raise ParseError("Malformed select, missing select items")
1157
+ pre_keys = set(self.environment.concepts.keys())
1153
1158
  base = SelectStatement.from_inputs(
1154
1159
  environment=self.environment,
1155
1160
  selection=select_items,
@@ -1159,6 +1164,15 @@ class ParseToObjects(Transformer):
1159
1164
  limit=limit,
1160
1165
  meta=Metadata(line_number=meta.line),
1161
1166
  )
1167
+ if (
1168
+ self.parse_pass == ParsePass.INITIAL
1169
+ and self.parse_config.strict_name_shadow_enforcement
1170
+ ):
1171
+ intersection = base.locally_derived.intersection(pre_keys)
1172
+ if intersection:
1173
+ raise ParseError(
1174
+ f"Select statement {base} has derived concepts {list(intersection)} that shadow existing environment concepts, which may cause unexpected behavior. Rename these."
1175
+ )
1162
1176
  return base
1163
1177
 
1164
1178
  @v_args(meta=True)
@@ -1740,7 +1754,10 @@ def parse_text_raw(text: str, environment: Optional[Environment] = None):
1740
1754
 
1741
1755
 
1742
1756
  def parse_text(
1743
- text: str, environment: Optional[Environment] = None, root: Path | None = None
1757
+ text: str,
1758
+ environment: Optional[Environment] = None,
1759
+ root: Path | None = None,
1760
+ parse_config: Parsing | None = None,
1744
1761
  ) -> Tuple[
1745
1762
  Environment,
1746
1763
  List[
@@ -1756,7 +1773,9 @@ def parse_text(
1756
1773
  environment = environment or (
1757
1774
  Environment(working_path=root) if root else Environment()
1758
1775
  )
1759
- parser = ParseToObjects(environment=environment, import_keys=["root"])
1776
+ parser = ParseToObjects(
1777
+ environment=environment, import_keys=["root"], parse_config=parse_config
1778
+ )
1760
1779
 
1761
1780
  try:
1762
1781
  parser.set_text(text)
trilogy/render.py CHANGED
@@ -1,38 +1,42 @@
1
+ from trilogy.constants import Rendering
2
+ from trilogy.dialect.base import BaseDialect
1
3
  from trilogy.dialect.enums import Dialects
2
4
 
3
5
 
4
- def get_dialect_generator(dialect: Dialects):
6
+ def get_dialect_generator(
7
+ dialect: Dialects, rendering: Rendering | None = None
8
+ ) -> BaseDialect:
5
9
  if dialect == Dialects.BIGQUERY:
6
10
  from trilogy.dialect.bigquery import BigqueryDialect
7
11
 
8
- return BigqueryDialect()
12
+ return BigqueryDialect(rendering=rendering)
9
13
  elif dialect == Dialects.SQL_SERVER:
10
14
  from trilogy.dialect.sql_server import SqlServerDialect
11
15
 
12
- return SqlServerDialect()
16
+ return SqlServerDialect(rendering=rendering)
13
17
  elif dialect == Dialects.DUCK_DB:
14
18
  from trilogy.dialect.duckdb import DuckDBDialect
15
19
 
16
- return DuckDBDialect()
20
+ return DuckDBDialect(rendering=rendering)
17
21
  elif dialect == Dialects.PRESTO:
18
22
  from trilogy.dialect.presto import PrestoDialect
19
23
 
20
- return PrestoDialect()
24
+ return PrestoDialect(rendering=rendering)
21
25
  elif dialect == Dialects.TRINO:
22
26
  from trilogy.dialect.presto import TrinoDialect
23
27
 
24
- return TrinoDialect()
28
+ return TrinoDialect(rendering=rendering)
25
29
  elif dialect == Dialects.POSTGRES:
26
30
  from trilogy.dialect.postgres import PostgresDialect
27
31
 
28
- return PostgresDialect()
32
+ return PostgresDialect(rendering=rendering)
29
33
  elif dialect == Dialects.SNOWFLAKE:
30
34
  from trilogy.dialect.snowflake import SnowflakeDialect
31
35
 
32
- return SnowflakeDialect()
36
+ return SnowflakeDialect(rendering=rendering)
33
37
  elif dialect == Dialects.DATAFRAME:
34
38
  from trilogy.dialect.dataframe import DataframeDialect
35
39
 
36
- return DataframeDialect()
40
+ return DataframeDialect(rendering=rendering)
37
41
  else:
38
42
  raise ValueError(f"Unsupported dialect {dialect}")
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- trilogy = preql.scripts.trilogy:cli