pytrilogy 0.0.3.47__py3-none-any.whl → 0.0.3.48__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.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.47
3
+ Version: 0.0.3.48
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -53,7 +53,7 @@ Installation: `pip install pytrilogy`
53
53
 
54
54
  You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
55
55
 
56
- Trilogy:
56
+ Trilogy looks like SQL:
57
57
  ```sql
58
58
  WHERE
59
59
  name like '%lvis%'
@@ -65,7 +65,7 @@ ORDER BY
65
65
  LIMIT 10;
66
66
  ```
67
67
  ## Goals
68
- vs SQL, the goals are:
68
+ And aims to:
69
69
 
70
70
  Preserve:
71
71
  - Correctness
@@ -85,6 +85,7 @@ Maintain:
85
85
  Save the following code in a file named `hello.preql`
86
86
 
87
87
  ```python
88
+ # semantic model is abstract from data
88
89
  key sentence_id int;
89
90
  property sentence_id.word_one string; # comments after a definition
90
91
  property sentence_id.word_two string; # are syntactic sugar for adding
@@ -92,7 +93,8 @@ property sentence_id.word_three string; # a description to it
92
93
 
93
94
  # comments in other places are just comments
94
95
 
95
- # define our datasources as queries in duckdb
96
+ # define our datasource to bind the model to data
97
+ # testing using query fixtures is a common pattern
96
98
  datasource word_one(
97
99
  sentence: sentence_id,
98
100
  word:word_one
@@ -126,25 +128,20 @@ union all
126
128
  select 2 as sentence, '!'
127
129
  ''';
128
130
 
131
+ def concat_with_space(x,y) -> x || ' ' || y;
132
+
129
133
  # an actual select statement
130
134
  # joins are automatically resolved between the 3 sources
131
135
  with sentences as
132
- select sentence_id, word_one || ' ' || word_two || word_three as text;
136
+ select sentence_id, @concat_with_space(word_one, word_two) || word_three as text;
133
137
 
134
- SELECT
135
- --sentences.sentence_id,
136
- sentences.text
137
138
  WHERE
138
- sentences.sentence_id = 1
139
- ;
140
-
139
+ sentences.sentence_id in (1,2)
141
140
  SELECT
142
- --sentences.sentence_id,
143
141
  sentences.text
144
- WHERE
145
- sentences.sentence_id = 2
146
142
  ;
147
- # semicolon termination for all statements
143
+
144
+
148
145
 
149
146
  ```
150
147
 
@@ -1,9 +1,9 @@
1
- pytrilogy-0.0.3.47.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=SPFHWIHKOsEgt2qXg_bEpYrsQE1oX5pNjD1gj43FZW4,303
1
+ pytrilogy-0.0.3.48.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=X7jtGsMd3bHz73UVPQxZzoipqeTD3gI4UEQZtAq3OUs,303
3
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  trilogy/constants.py,sha256=5eQxk1A0pv-TQk3CCvgZCFA9_K-6nxrOm7E5Lxd7KIY,1652
5
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
6
- trilogy/executor.py,sha256=_xihzIaUEbE5lzwHECsvQ75Dm5fdRPBdMCVz6gNBpV4,16091
6
+ trilogy/executor.py,sha256=GwNhP9UW4565dxnpHbw-VWNE2lX8uroQJQtSpC_j2pI,16298
7
7
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
8
8
  trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  trilogy/render.py,sha256=qQWwduymauOlB517UtM-VGbVe8Cswa4UJub5aGbSO6c,1512
@@ -19,19 +19,18 @@ trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,67
19
19
  trilogy/core/functions.py,sha256=4fEOGgXWDvgrJtCg_5m2Y9iWnHfLbvLQ82RkIMl_1K0,27722
20
20
  trilogy/core/graph_models.py,sha256=z17EoO8oky2QOuO6E2aMWoVNKEVJFhLdsQZOhC4fNLU,2079
21
21
  trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
22
- trilogy/core/optimization.py,sha256=aihzx4-2-mSjx5td1TDTYGvc7e9Zvy-_xEyhPqLS-Ig,8314
23
- trilogy/core/query_processor.py,sha256=FFZlTzF9DBDH7dvpTDgd5SxDkE4EkING-MVtUgqx9gQ,19459
22
+ trilogy/core/optimization.py,sha256=O7ag0IVQlJyWdAXBi_hHeU3Df5DRyd75Vlz6pks2J10,8197
23
+ trilogy/core/query_processor.py,sha256=NNzYPKN5HzivQFXugSbJC_MaupkwOYii7A_vnXuBIK4,20063
24
24
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  trilogy/core/models/author.py,sha256=NhTKuk1eYAuYBbpvaFUxr-LntIoVarFQlNuNJwZmMmw,76990
26
- trilogy/core/models/build.py,sha256=fFPGyHIPzQU8DNOxkGQGC3_sbZt-MHP0o5ftSA67LtU,61962
26
+ trilogy/core/models/build.py,sha256=MPiHgyfOumZ8zF3iB61pzrAeDAlGV2F9R0Dw7mTTyqQ,62708
27
27
  trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
28
28
  trilogy/core/models/core.py,sha256=wx6hJcFECMG-Ij972ADNkr-3nFXkYESr82ObPiC46_U,10875
29
29
  trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
30
30
  trilogy/core/models/environment.py,sha256=AVSrvjNcNX535GhCPtYhCRY2Lp_Hj0tdY3VVt_kZb9Q,27260
31
- trilogy/core/models/execute.py,sha256=mQm5Gydo2Ph0W7w9wm5dQEarS04PC-IKAgNVsdqOZsQ,34524
32
- trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
31
+ trilogy/core/models/execute.py,sha256=m_GodtQkhuPo5kyBNlfC9c_jgprV7M64kE6x_12_ExQ,34616
32
+ trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
33
33
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
34
- trilogy/core/optimizations/inline_constant.py,sha256=lvNTIXaLNkw3HseJyXyDNk5R52doLU9sIg3pmU2_S08,1332
35
34
  trilogy/core/optimizations/inline_datasource.py,sha256=AHuTGh2x0GQ8usOe0NiFncfTFQ_KogdgDl4uucmhIbI,4241
36
35
  trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
37
36
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -42,7 +41,7 @@ trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW
42
41
  trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
43
42
  trilogy/core/processing/node_generators/common.py,sha256=nVeH_AdO58ygtNSO0wNgMR7_h2D0dFSGM_rh1fJd4Yc,9468
44
43
  trilogy/core/processing/node_generators/filter_node.py,sha256=JymSKzA-9oQAZ3ZtJRK9c3w5FXs8MjJBGWU9TYUqx4E,9099
45
- trilogy/core/processing/node_generators/group_node.py,sha256=kO-ersxIL04rZwX5-vFIFQQnp357PFo_7ZKXoGq3wyc,5989
44
+ trilogy/core/processing/node_generators/group_node.py,sha256=ISv2lLnr5m5nMpiXYJbgBqfUPQqeypjCAcaool9Kvnk,6109
46
45
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
47
46
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
48
47
  trilogy/core/processing/node_generators/node_merge_node.py,sha256=sv55oynfqgpHEpo1OEtVDri-5fywzPhDlR85qaWikvY,16195
@@ -70,9 +69,9 @@ trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
70
69
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
71
70
  trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
72
71
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- trilogy/dialect/base.py,sha256=kIwkNWWhMEIWDJ-6nr5bb2CJgbpKvJ9CxbhH4tYOFEc,41482
72
+ trilogy/dialect/base.py,sha256=RkfNoNSo46p-WCafAWC5tXqJ_FMZEXANLyZSqX7_Pxw,42082
74
73
  trilogy/dialect/bigquery.py,sha256=7LcgPLDkeNBk6YTfaE-RBBi7SjWFV-jjuvZM1VMIXqk,3350
75
- trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
74
+ trilogy/dialect/common.py,sha256=JQ8ONloalaWEXsTTWUhZcYyzMRaZ9HdUw7cN6QWtY5c,5295
76
75
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
77
76
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
78
77
  trilogy/dialect/duckdb.py,sha256=XTBK4RhE1_wF2_IA_7c2W5ih0uxZx0wZ1mfJ3YFIuso,3768
@@ -87,11 +86,11 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
87
86
  trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
88
87
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
88
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
- trilogy/parsing/common.py,sha256=CCCkoRkFG9nxBI6u0lWt81JZvtBvXbTdiblvexOaeSY,29250
89
+ trilogy/parsing/common.py,sha256=u7V8uc2mdBtszVujk-hzllfDAqM3j5pKd8B9UEj-uNc,29223
91
90
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
92
91
  trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
93
92
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
94
- trilogy/parsing/parse_engine.py,sha256=m8t6De4q-PhqthMl7iIJsB7JdSQ-YYnBlLumTkLzw1Q,68652
93
+ trilogy/parsing/parse_engine.py,sha256=K3TwjCiiZtG3UrICF9Alik56_KPusVNWfqE-oUaKfho,68664
95
94
  trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
96
95
  trilogy/parsing/trilogy.lark,sha256=q15J3P71yA_4lsWjC1vb7eDTemkJGLPKYvf5Hn9IBIk,13584
97
96
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -102,8 +101,8 @@ trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
102
101
  trilogy/std/geography.preql,sha256=-fqAGnBL6tR-UtT8DbSek3iMFg66ECR_B_41pODxv-k,504
103
102
  trilogy/std/money.preql,sha256=ZHW-csTX-kYbOLmKSO-TcGGgQ-_DMrUXy0BjfuJSFxM,80
104
103
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
105
- pytrilogy-0.0.3.47.dist-info/METADATA,sha256=pWnJM7LvUZHAV5x2DH41DNeZHCo3lqmuEwmSPeWCeJQ,9100
106
- pytrilogy-0.0.3.47.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
107
- pytrilogy-0.0.3.47.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
108
- pytrilogy-0.0.3.47.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
109
- pytrilogy-0.0.3.47.dist-info/RECORD,,
104
+ pytrilogy-0.0.3.48.dist-info/METADATA,sha256=xuGEKV1ZdJtS-uES50q5bj5x-_60E4Pb6VL5G_SKzNQ,9095
105
+ pytrilogy-0.0.3.48.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
106
+ pytrilogy-0.0.3.48.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
107
+ pytrilogy-0.0.3.48.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
108
+ pytrilogy-0.0.3.48.dist-info/RECORD,,
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.47"
7
+ __version__ = "0.0.3.48"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -247,6 +247,17 @@ def get_concept_arguments(expr) -> List["BuildConcept"]:
247
247
  return output
248
248
 
249
249
 
250
+ class BuildParamaterizedConceptReference(BaseModel):
251
+ concept: BuildConcept
252
+
253
+ def __str__(self):
254
+ return f":{self.concept.address}"
255
+
256
+ @property
257
+ def safe_address(self) -> str:
258
+ return self.concept.safe_address
259
+
260
+
250
261
  class BuildGrain(BaseModel):
251
262
  components: set[str] = Field(default_factory=set)
252
263
  where_clause: Optional[BuildWhereClause] = None
@@ -1578,7 +1589,7 @@ class Factory:
1578
1589
 
1579
1590
  new = BuildFunction.model_construct(
1580
1591
  operator=base.operator,
1581
- arguments=[self.build(c) for c in raw_args],
1592
+ arguments=[self.handle_constant(self.build(c)) for c in raw_args],
1582
1593
  output_datatype=base.output_datatype,
1583
1594
  output_purpose=base.output_purpose,
1584
1595
  valid_inputs=base.valid_inputs,
@@ -1615,6 +1626,8 @@ class Factory:
1615
1626
 
1616
1627
  @build.register
1617
1628
  def _(self, base: Concept) -> BuildConcept:
1629
+
1630
+ # TODO: if we are using parameters, wrap it in a new model and use that in rendering
1618
1631
  if base.address in self.local_concepts:
1619
1632
  return self.local_concepts[base.address]
1620
1633
  new_lineage, final_grain, _ = base.get_select_grain_and_keys(
@@ -1738,8 +1751,8 @@ class Factory:
1738
1751
  @build.register
1739
1752
  def _(self, base: Conditional) -> BuildConditional:
1740
1753
  return BuildConditional.model_construct(
1741
- left=(self.build(base.left)),
1742
- right=(self.build(base.right)),
1754
+ left=self.handle_constant(self.build(base.left)),
1755
+ right=self.handle_constant(self.build(base.right)),
1743
1756
  operator=base.operator,
1744
1757
  )
1745
1758
 
@@ -1767,8 +1780,8 @@ class Factory:
1767
1780
  right_c, _ = self.instantiate_concept(right)
1768
1781
  right = right_c # type: ignore
1769
1782
  return BuildComparison.model_construct(
1770
- left=(self.build(left)),
1771
- right=(self.build(right)),
1783
+ left=self.handle_constant(self.build(left)),
1784
+ right=self.handle_constant(self.build(right)),
1772
1785
  operator=base.operator,
1773
1786
  )
1774
1787
 
@@ -2015,3 +2028,12 @@ class Factory:
2015
2028
  factory.build(base.non_partial_for) if base.non_partial_for else None
2016
2029
  ),
2017
2030
  )
2031
+
2032
+ def handle_constant(self, base):
2033
+ if (
2034
+ isinstance(base, BuildConcept)
2035
+ and isinstance(base.lineage, BuildFunction)
2036
+ and base.lineage.operator == FunctionType.CONSTANT
2037
+ ):
2038
+ return BuildParamaterizedConceptReference(concept=base)
2039
+ return base
@@ -24,6 +24,7 @@ from trilogy.core.models.build import (
24
24
  BuildFunction,
25
25
  BuildGrain,
26
26
  BuildOrderBy,
27
+ BuildParamaterizedConceptReference,
27
28
  BuildParenthetical,
28
29
  BuildRowsetItem,
29
30
  LooseBuildConceptList,
@@ -447,7 +448,7 @@ class CTEConceptPair(ConceptPair):
447
448
 
448
449
 
449
450
  class InstantiatedUnnestJoin(BaseModel):
450
- concept_to_unnest: BuildConcept
451
+ object_to_unnest: BuildConcept | BuildParamaterizedConceptReference | BuildFunction
451
452
  alias: str = "unnest"
452
453
 
453
454
 
@@ -5,7 +5,6 @@ from trilogy.core.models.build import (
5
5
  )
6
6
  from trilogy.core.models.execute import CTE, UnionCTE
7
7
  from trilogy.core.optimizations import (
8
- InlineConstant,
9
8
  InlineDatasource,
10
9
  OptimizationRule,
11
10
  PredicatePushdown,
@@ -206,8 +205,6 @@ def optimize_ctes(
206
205
  REGISTERED_RULES.append(PredicatePushdown())
207
206
  if CONFIG.optimizations.predicate_pushdown:
208
207
  REGISTERED_RULES.append(PredicatePushdownRemove())
209
- if CONFIG.optimizations.constant_inlining:
210
- REGISTERED_RULES.append(InlineConstant())
211
208
  for rule in REGISTERED_RULES:
212
209
  loops = 0
213
210
  complete = False
@@ -1,11 +1,9 @@
1
1
  from .base_optimization import OptimizationRule
2
- from .inline_constant import InlineConstant
3
2
  from .inline_datasource import InlineDatasource
4
3
  from .predicate_pushdown import PredicatePushdown, PredicatePushdownRemove
5
4
 
6
5
  __all__ = [
7
6
  "OptimizationRule",
8
- "InlineConstant",
9
7
  "InlineDatasource",
10
8
  "PredicatePushdown",
11
9
  "PredicatePushdownRemove",
@@ -1,6 +1,7 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
+ from trilogy.core.internal import ALL_ROWS_CONCEPT
4
5
  from trilogy.core.models.build import (
5
6
  BuildAggregateWrapper,
6
7
  BuildConcept,
@@ -92,7 +93,9 @@ def gen_group_node(
92
93
  logger.info(
93
94
  f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseBuildConceptList(concepts=parent_concepts)}"
94
95
  )
95
- parent_concepts = unique(parent_concepts, "address")
96
+ parent_concepts = unique(
97
+ [x for x in parent_concepts if not x.name == ALL_ROWS_CONCEPT], "address"
98
+ )
96
99
  parent = source_concepts(
97
100
  mandatory_list=parent_concepts,
98
101
  environment=environment,
@@ -12,7 +12,9 @@ from trilogy.core.models.build import (
12
12
  BuildConcept,
13
13
  BuildConditional,
14
14
  BuildDatasource,
15
+ BuildFunction,
15
16
  BuildMultiSelectLineage,
17
+ BuildParamaterizedConceptReference,
16
18
  BuildSelectLineage,
17
19
  Factory,
18
20
  )
@@ -55,8 +57,14 @@ def base_join_to_join(
55
57
  """This function converts joins at the datasource level
56
58
  to joins at the CTE level"""
57
59
  if isinstance(base_join, UnnestJoin):
60
+ object_to_unnest = base_join.parent.arguments[0]
61
+ if not isinstance(
62
+ object_to_unnest,
63
+ (BuildConcept | BuildParamaterizedConceptReference | BuildFunction),
64
+ ):
65
+ raise ValueError(f"Unnest join must be a concept; got {object_to_unnest}")
58
66
  return InstantiatedUnnestJoin(
59
- concept_to_unnest=base_join.parent.concept_arguments[0],
67
+ object_to_unnest=object_to_unnest,
60
68
  alias=base_join.alias,
61
69
  )
62
70
 
@@ -220,6 +228,8 @@ def resolve_cte_base_name_and_alias_v2(
220
228
  source_map: Dict[str, list[str]],
221
229
  raw_joins: List[Join | InstantiatedUnnestJoin],
222
230
  ) -> Tuple[str | None, str | None]:
231
+ if not source.datasources:
232
+ return None, None
223
233
  if (
224
234
  isinstance(source.datasources[0], BuildDatasource)
225
235
  and not source.datasources[0].name == CONSTANT_DATASET
@@ -301,18 +311,23 @@ def datasource_to_cte(
301
311
 
302
312
  else:
303
313
  # source is the first datasource of the query datasource
304
- source = query_datasource.datasources[0]
305
- # this is required to ensure that constant datasets
306
- # render properly on initial access; since they have
307
- # no actual source
308
- if source.name == CONSTANT_DATASET:
309
- source_map = {k: [] for k in query_datasource.source_map}
310
- existence_map = source_map
314
+ if query_datasource.datasources:
315
+
316
+ source = query_datasource.datasources[0]
317
+ # this is required to ensure that constant datasets
318
+ # render properly on initial access; since they have
319
+ # no actual source
320
+ if source.name == CONSTANT_DATASET:
321
+ source_map = {k: [] for k in query_datasource.source_map}
322
+ existence_map = source_map
323
+ else:
324
+ source_map = {
325
+ k: [] if not v else [source.safe_identifier]
326
+ for k, v in query_datasource.source_map.items()
327
+ }
328
+ existence_map = source_map
311
329
  else:
312
- source_map = {
313
- k: [] if not v else [source.safe_identifier]
314
- for k, v in query_datasource.source_map.items()
315
- }
330
+ source_map = {k: [] for k in query_datasource.source_map}
316
331
  existence_map = source_map
317
332
 
318
333
  human_id = generate_cte_name(query_datasource.identifier, name_map)
trilogy/dialect/base.py CHANGED
@@ -3,7 +3,13 @@ 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, Rendering, logger
6
+ from trilogy.constants import (
7
+ CONFIG,
8
+ DEFAULT_NAMESPACE,
9
+ MagicConstants,
10
+ Rendering,
11
+ logger,
12
+ )
7
13
  from trilogy.core.enums import (
8
14
  DatePart,
9
15
  FunctionType,
@@ -22,6 +28,7 @@ from trilogy.core.models.build import (
22
28
  BuildFunction,
23
29
  BuildMultiSelectLineage,
24
30
  BuildOrderItem,
31
+ BuildParamaterizedConceptReference,
25
32
  BuildParenthetical,
26
33
  BuildRowsetItem,
27
34
  BuildSubselectComparison,
@@ -513,6 +520,9 @@ class BaseDialect:
513
520
  BuildWindowItem,
514
521
  BuildFilterItem,
515
522
  BuildParenthetical,
523
+ BuildParamaterizedConceptReference,
524
+ BuildMultiSelectLineage,
525
+ BuildRowsetItem,
516
526
  str,
517
527
  int,
518
528
  list,
@@ -693,6 +703,14 @@ class BaseDialect:
693
703
  return self.render_expr(e.type, cte=cte, cte_map=cte_map)
694
704
  elif isinstance(e, ListType):
695
705
  return f"{self.COMPLEX_DATATYPE_MAP[DataType.LIST](self.render_expr(e.value_data_type, cte=cte, cte_map=cte_map))}"
706
+ elif isinstance(e, BuildParamaterizedConceptReference):
707
+ if self.rendering.parameters:
708
+ if e.concept.namespace == DEFAULT_NAMESPACE:
709
+ return f":{e.concept.name}"
710
+ return f":{e.concept.address}"
711
+ elif e.concept.lineage:
712
+ return self.render_expr(e.concept.lineage, cte=cte, cte_map=cte_map)
713
+ return f"{self.QUOTE_CHARACTER}{e.concept.address}{self.QUOTE_CHARACTER}"
696
714
  else:
697
715
  raise ValueError(f"Unable to render type {type(e)} {e}")
698
716
 
@@ -744,12 +762,12 @@ class BaseDialect:
744
762
  UnnestMode.CROSS_APPLY,
745
763
  ):
746
764
 
747
- source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_concept_sql, cte)}"
765
+ source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_expr, cte)}"
748
766
  elif (
749
767
  cte.join_derived_concepts
750
768
  and self.UNNEST_MODE == UnnestMode.SNOWFLAKE
751
769
  ):
752
- source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_concept_sql, cte)}"
770
+ source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_expr, cte)}"
753
771
  # direct - eg DUCK DB - can be directly selected inline
754
772
  elif (
755
773
  cte.join_derived_concepts and self.UNNEST_MODE == UnnestMode.DIRECT
@@ -803,7 +821,6 @@ class BaseDialect:
803
821
  render_join(
804
822
  join,
805
823
  self.QUOTE_CHARACTER,
806
- self.render_concept_sql,
807
824
  self.render_expr,
808
825
  cte,
809
826
  self.UNNEST_MODE,
trilogy/dialect/common.py CHANGED
@@ -1,7 +1,11 @@
1
1
  from typing import Callable
2
2
 
3
3
  from trilogy.core.enums import Modifier, UnnestMode
4
- from trilogy.core.models.build import BuildConcept, BuildFunction
4
+ from trilogy.core.models.build import (
5
+ BuildConcept,
6
+ BuildFunction,
7
+ BuildParamaterizedConceptReference,
8
+ )
5
9
  from trilogy.core.models.datasource import RawColumnExpr
6
10
  from trilogy.core.models.execute import (
7
11
  CTE,
@@ -19,21 +23,27 @@ def null_wrapper(lval: str, rval: str, modifiers: list[Modifier]) -> str:
19
23
  def render_unnest(
20
24
  unnest_mode: UnnestMode,
21
25
  quote_character: str,
22
- concept: BuildConcept,
23
- render_func: Callable[[BuildConcept, CTE, bool], str],
26
+ concept: BuildConcept | BuildParamaterizedConceptReference | BuildFunction,
27
+ render_func: Callable[
28
+ [BuildConcept | BuildParamaterizedConceptReference | BuildFunction, CTE], str
29
+ ],
24
30
  cte: CTE,
25
31
  ):
32
+ if not isinstance(concept, (BuildConcept, BuildParamaterizedConceptReference)):
33
+ address = "anon_function"
34
+ else:
35
+ address = concept.safe_address
26
36
  if unnest_mode == UnnestMode.CROSS_JOIN:
27
- return f"{render_func(concept, cte, False)} as {quote_character}{concept.safe_address}{quote_character}"
37
+ return f"{render_func(concept, cte)} as {quote_character}{address}{quote_character}"
28
38
  elif unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
29
- return f"{render_func(concept, cte, False)} as unnest_wrapper ({quote_character}{concept.safe_address}{quote_character})"
39
+ return f"{render_func(concept, cte)} as unnest_wrapper ({quote_character}{address}{quote_character})"
30
40
  elif unnest_mode == UnnestMode.SNOWFLAKE:
31
41
  # if we don't actually have a join, we're directly unnesting a concept, and we can skip the flatten
32
42
  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})"
43
+ return f"{render_func(concept, cte)} as unnest_wrapper ( unnest1, unnest2, unnest3, unnest4, {quote_character}{cte.join_derived_concepts[0].safe_address}{quote_character})"
34
44
  # otherwise, flatten the concept for the join
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})"
36
- return f"{render_func(concept, cte, False)} as {quote_character}{concept.safe_address}{quote_character}"
45
+ return f"flatten({render_func(concept, cte)}) as unnest_wrapper ( unnest1, unnest2, unnest3, unnest4, {quote_character}{cte.join_derived_concepts[0].safe_address}{quote_character})"
46
+ return f"{render_func(concept, cte)} as {quote_character}{address}{quote_character}"
37
47
 
38
48
 
39
49
  def render_join_concept(
@@ -60,8 +70,9 @@ def render_join_concept(
60
70
  def render_join(
61
71
  join: Join | InstantiatedUnnestJoin,
62
72
  quote_character: str,
63
- render_func: Callable[[BuildConcept, CTE, bool], str],
64
- render_expr_func: Callable[[BuildConcept, CTE], str],
73
+ render_expr_func: Callable[
74
+ [BuildConcept | BuildParamaterizedConceptReference | BuildFunction, CTE], str
75
+ ],
65
76
  cte: CTE,
66
77
  unnest_mode: UnnestMode = UnnestMode.CROSS_APPLY,
67
78
  ) -> str | None:
@@ -72,12 +83,12 @@ def render_join(
72
83
  if not cte:
73
84
  raise ValueError("must provide a cte to build an unnest joins")
74
85
  if unnest_mode == UnnestMode.CROSS_JOIN:
75
- return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
86
+ return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
76
87
  if unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
77
- return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
88
+ return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
78
89
  if unnest_mode == UnnestMode.SNOWFLAKE:
79
- return f"LEFT JOIN LATERAL {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
80
- return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
90
+ return f"LEFT JOIN LATERAL {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
91
+ return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
81
92
  # left_name = join.left_name
82
93
  right_name = join.right_name
83
94
  if cte.quote_address.get(join.right_name, False):
trilogy/executor.py CHANGED
@@ -394,6 +394,9 @@ class Executor(object):
394
394
  if isinstance(rval, ListWrapper):
395
395
  return [x for x in rval]
396
396
  if isinstance(rval, MapWrapper):
397
+ # duckdb expects maps in this format as variables
398
+ if self.dialect == Dialects.DUCK_DB:
399
+ return {"key": [x for x in rval], "value": [rval[x] for x in rval]}
397
400
  return {k: v for k, v in rval.items()}
398
401
  # if isinstance(rval, ConceptRef):
399
402
  # return self._concept_to_value(self.environment.concepts[rval.address], local_concepts=local_concepts)
trilogy/parsing/common.py CHANGED
@@ -211,7 +211,6 @@ def atom_is_relevant(
211
211
 
212
212
  if isinstance(atom, Function):
213
213
  relevant = False
214
- print("atom args")
215
214
  for arg in atom.arguments:
216
215
  relevant = relevant or atom_is_relevant(arg, others, environment)
217
216
  return relevant
@@ -1795,7 +1795,7 @@ class ParseToObjects(Transformer):
1795
1795
  def fyear(self, meta, args):
1796
1796
  return self.function_factory.create_function(args, FunctionType.YEAR, meta)
1797
1797
 
1798
- def internal_fcast(self, meta, args):
1798
+ def internal_fcast(self, meta, args) -> Function:
1799
1799
  args = process_function_args(args, meta=meta, environment=self.environment)
1800
1800
  if isinstance(args[0], str):
1801
1801
  processed: date | datetime | int | float | bool | str
@@ -1,35 +0,0 @@
1
- from trilogy.core.enums import Derivation
2
- from trilogy.core.models.build import BuildConcept
3
- from trilogy.core.models.execute import (
4
- CTE,
5
- UnionCTE,
6
- )
7
- from trilogy.core.optimizations.base_optimization import OptimizationRule
8
-
9
-
10
- class InlineConstant(OptimizationRule):
11
- def optimize(
12
- self, cte: CTE | UnionCTE, inverse_map: dict[str, list[CTE | UnionCTE]]
13
- ) -> bool:
14
- if isinstance(cte, UnionCTE):
15
- return any(self.optimize(x, inverse_map) for x in cte.internal_ctes)
16
-
17
- to_inline: list[BuildConcept] = []
18
- for x in cte.source.input_concepts:
19
- if x.address not in cte.source_map:
20
- continue
21
- if x.derivation == Derivation.CONSTANT:
22
- self.log(f"Found constant {x.address} on {cte.name}")
23
- to_inline.append(x)
24
- if to_inline:
25
- inlined = False
26
- for c in to_inline:
27
- self.log(f"Attempting to inline constant {c.address} on {cte.name}")
28
- test = cte.inline_constant(c)
29
- if test:
30
- self.log(f"Successfully inlined constant {c.address} to {cte.name}")
31
- inlined = True
32
- else:
33
- self.log(f"Could not inline constant to {cte.name}")
34
- return inlined
35
- return False