pytrilogy 0.0.3.45__py3-none-any.whl → 0.0.3.47__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.45
3
+ Version: 0.0.3.47
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,5 +1,5 @@
1
- pytrilogy-0.0.3.45.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=CExOPBg5voukcSp-h8dQa4Sf1R0iZ0oQBnfggESqllo,303
1
+ pytrilogy-0.0.3.47.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=SPFHWIHKOsEgt2qXg_bEpYrsQE1oX5pNjD1gj43FZW4,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
@@ -13,17 +13,17 @@ trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  trilogy/core/constants.py,sha256=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
14
14
  trilogy/core/enums.py,sha256=JwbWyAHOC2xRTZe2SeEvlIGPvmC1KjcJ4uh1Po5USzQ,7380
15
15
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
16
- trilogy/core/environment_helpers.py,sha256=UWtF5ZQqFyzHdrjUBEd7c2ZfASBhBWFoa9WkUHBbyHI,9700
16
+ trilogy/core/environment_helpers.py,sha256=VvPIiFemqaLLpIpLIqprfu63K7muZ1YzNg7UZIUph8w,8267
17
17
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
18
18
  trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,673
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
22
  trilogy/core/optimization.py,sha256=aihzx4-2-mSjx5td1TDTYGvc7e9Zvy-_xEyhPqLS-Ig,8314
23
- trilogy/core/query_processor.py,sha256=Vl-u0F0rbqI2liv82yJgiZCB255Kx_KiuzZVHL6aeTM,19459
23
+ trilogy/core/query_processor.py,sha256=FFZlTzF9DBDH7dvpTDgd5SxDkE4EkING-MVtUgqx9gQ,19459
24
24
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- trilogy/core/models/author.py,sha256=b5GQc79w-gFQfZhgBdHeRCJAtCYr4j_da6k3Dkx4YAA,76863
26
- trilogy/core/models/build.py,sha256=EsI7BLmFXdxj1an3NnKR_Qm79tcjlFKjmLjmt3_v2eA,61829
25
+ trilogy/core/models/author.py,sha256=NhTKuk1eYAuYBbpvaFUxr-LntIoVarFQlNuNJwZmMmw,76990
26
+ trilogy/core/models/build.py,sha256=fFPGyHIPzQU8DNOxkGQGC3_sbZt-MHP0o5ftSA67LtU,61962
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
@@ -41,7 +41,7 @@ trilogy/core/processing/utility.py,sha256=rfzdgl-vWkCyhLzXNNuWgPLK59eiYypQb6TdZK
41
41
  trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW6gjUJCsiXumdbJNozHUf-Y,800
42
42
  trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
43
43
  trilogy/core/processing/node_generators/common.py,sha256=nVeH_AdO58ygtNSO0wNgMR7_h2D0dFSGM_rh1fJd4Yc,9468
44
- trilogy/core/processing/node_generators/filter_node.py,sha256=lT167yBgy3P9sDBM1Cjj0PKSXro8dvGtBmc8nwsUjig,8366
44
+ trilogy/core/processing/node_generators/filter_node.py,sha256=JymSKzA-9oQAZ3ZtJRK9c3w5FXs8MjJBGWU9TYUqx4E,9099
45
45
  trilogy/core/processing/node_generators/group_node.py,sha256=kO-ersxIL04rZwX5-vFIFQQnp357PFo_7ZKXoGq3wyc,5989
46
46
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
47
47
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
@@ -55,7 +55,7 @@ trilogy/core/processing/node_generators/unnest_node.py,sha256=cOEKnMRzXUW3bwmiOl
55
55
  trilogy/core/processing/node_generators/window_node.py,sha256=RUHgpYovQObFod1xRIMWtDzMcxwlm4-1Fdrf_Cuw5W4,6346
56
56
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=GMW07bb6hXurhF0hZLYoMAKSIS65tat5hwBjvqqPeSA,6516
58
- trilogy/core/processing/nodes/__init__.py,sha256=MfookoG73Z9p7kYFDh033uZJpP4exqkwdKtheDRepAw,5893
58
+ trilogy/core/processing/nodes/__init__.py,sha256=xPFF7x3TFs1Z4IcfthCykZgrksb-UhN-pc_oIigfFSo,6014
59
59
  trilogy/core/processing/nodes/base_node.py,sha256=FHrY8GsTKPuMJklOjILbhGqCt5s1nmlj62Z-molARDA,16835
60
60
  trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
61
61
  trilogy/core/processing/nodes/group_node.py,sha256=MUvcOg9U5J6TnWBel8eht9PdI9BfAKjUxmfjP_ZXx9o,10484
@@ -65,12 +65,12 @@ trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ
65
65
  trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
66
66
  trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
67
67
  trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
- trilogy/core/statements/author.py,sha256=rYDf9rCQ4YKEO9br1OWmOZd-51AiaDkaWYegvteJa8M,14728
68
+ trilogy/core/statements/author.py,sha256=2q0yvP_0AbExdXnASlLG7OaDcM7sBaRco6YALnrQwzg,15255
69
69
  trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
70
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
71
71
  trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
72
72
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- trilogy/dialect/base.py,sha256=vp6_9fUkblAWVpCXGBIcoAx6N7vof9M7s9t6-b_waUY,41409
73
+ trilogy/dialect/base.py,sha256=kIwkNWWhMEIWDJ-6nr5bb2CJgbpKvJ9CxbhH4tYOFEc,41482
74
74
  trilogy/dialect/bigquery.py,sha256=7LcgPLDkeNBk6YTfaE-RBBi7SjWFV-jjuvZM1VMIXqk,3350
75
75
  trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
76
76
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
@@ -87,13 +87,13 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
87
87
  trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
88
88
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
- trilogy/parsing/common.py,sha256=U9RNi1GyPTQaitZGwXy1QftdC5PWYArP7V8t-v3H8Po,27157
90
+ trilogy/parsing/common.py,sha256=CCCkoRkFG9nxBI6u0lWt81JZvtBvXbTdiblvexOaeSY,29250
91
91
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
92
- trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
92
+ trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
93
93
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
94
- trilogy/parsing/parse_engine.py,sha256=9SO2q8m5MlZo_Eho-_r6hmTSm5VH38k47C2iHTtYwjU,68224
94
+ trilogy/parsing/parse_engine.py,sha256=m8t6De4q-PhqthMl7iIJsB7JdSQ-YYnBlLumTkLzw1Q,68652
95
95
  trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
96
- trilogy/parsing/trilogy.lark,sha256=zbDAIG7gpsImxBtteD8E2pKwcJCGpM-rEQDRqpgzoSQ,13717
96
+ trilogy/parsing/trilogy.lark,sha256=q15J3P71yA_4lsWjC1vb7eDTemkJGLPKYvf5Hn9IBIk,13584
97
97
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
99
99
  trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -102,8 +102,8 @@ trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
102
102
  trilogy/std/geography.preql,sha256=-fqAGnBL6tR-UtT8DbSek3iMFg66ECR_B_41pODxv-k,504
103
103
  trilogy/std/money.preql,sha256=ZHW-csTX-kYbOLmKSO-TcGGgQ-_DMrUXy0BjfuJSFxM,80
104
104
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
105
- pytrilogy-0.0.3.45.dist-info/METADATA,sha256=7z2vOsBA8amZQl0YEkjnWFBqxh4amLBZznjHGwZJ6-E,9100
106
- pytrilogy-0.0.3.45.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
107
- pytrilogy-0.0.3.45.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
108
- pytrilogy-0.0.3.45.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
109
- pytrilogy-0.0.3.45.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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.45"
7
+ __version__ = "0.0.3.47"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -6,23 +6,8 @@ from trilogy.core.models.core import DataType, StructType, arg_to_datatype
6
6
  from trilogy.core.models.environment import Environment
7
7
  from trilogy.parsing.common import Meta
8
8
 
9
- FUNCTION_DESCRIPTION_MAPS = {
10
- FunctionType.DATE: "The date part of a timestamp/date. Integer, 0-31 depending on month.",
11
- FunctionType.MONTH: "The month part of a timestamp/date. Integer, 1-12.",
12
- FunctionType.YEAR: "The year part of a timestamp/date. Integer.",
13
- FunctionType.QUARTER: "The quarter part of a timestamp/date. Integer, 1-4.",
14
- FunctionType.DAY_OF_WEEK: "The day of the week part of a timestamp/date. Integer, 0-6.",
15
- FunctionType.HOUR: "The hour part of a timestamp. Integer, 0-23.",
16
- FunctionType.MINUTE: "The minute part of a timestamp. Integer, 0-59.",
17
- FunctionType.SECOND: "The second part of a timestamp. Integer, 0-59.",
18
- }
19
-
20
9
 
21
10
  def generate_date_concepts(concept: Concept, environment: Environment):
22
- if concept.metadata and concept.metadata.description:
23
- base_description = concept.metadata.description
24
- else:
25
- base_description = f"a {concept.address}"
26
11
  if concept.metadata and concept.metadata.line_number:
27
12
  base_line_number = concept.metadata.line_number
28
13
  else:
@@ -67,7 +52,6 @@ def generate_date_concepts(concept: Concept, environment: Environment):
67
52
  [concept.address],
68
53
  ),
69
54
  metadata=Metadata(
70
- description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}",
71
55
  line_number=base_line_number,
72
56
  concept_source=ConceptSource.AUTO_DERIVED,
73
57
  ),
@@ -95,7 +79,7 @@ def generate_date_concepts(concept: Concept, environment: Environment):
95
79
  [concept.address],
96
80
  ),
97
81
  metadata=Metadata(
98
- description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
82
+ # description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
99
83
  line_number=base_line_number,
100
84
  concept_source=ConceptSource.AUTO_DERIVED,
101
85
  ),
@@ -105,10 +89,6 @@ def generate_date_concepts(concept: Concept, environment: Environment):
105
89
 
106
90
 
107
91
  def generate_datetime_concepts(concept: Concept, environment: Environment):
108
- if concept.metadata and concept.metadata.description:
109
- base_description = concept.metadata.description
110
- else:
111
- base_description = concept.address
112
92
  if concept.metadata and concept.metadata.line_number:
113
93
  base_line_number = concept.metadata.line_number
114
94
  else:
@@ -146,7 +126,6 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
146
126
  [concept.address],
147
127
  ),
148
128
  metadata=Metadata(
149
- description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}",
150
129
  line_number=base_line_number,
151
130
  concept_source=ConceptSource.AUTO_DERIVED,
152
131
  ),
@@ -157,10 +136,6 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
157
136
 
158
137
 
159
138
  def generate_key_concepts(concept: Concept, environment: Environment):
160
- if concept.metadata and concept.metadata.description:
161
- base_description = concept.metadata.description
162
- else:
163
- base_description = f"a {concept.datatype.value}"
164
139
  if concept.metadata and concept.metadata.line_number:
165
140
  base_line_number = concept.metadata.line_number
166
141
  else:
@@ -186,7 +161,7 @@ def generate_key_concepts(concept: Concept, environment: Environment):
186
161
  namespace=concept.namespace,
187
162
  keys=set(),
188
163
  metadata=Metadata(
189
- description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
164
+ # description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
190
165
  line_number=base_line_number,
191
166
  concept_source=ConceptSource.AUTO_DERIVED,
192
167
  ),
@@ -445,13 +445,16 @@ class Grain(Namespaced, BaseModel):
445
445
  concepts: Iterable[Concept | ConceptRef | str],
446
446
  environment: Environment | None = None,
447
447
  where_clause: WhereClause | None = None,
448
+ local_concepts: dict[str, Concept] | None = None,
448
449
  ) -> Grain:
449
450
  from trilogy.parsing.common import concepts_to_grain_concepts
450
451
 
451
452
  x = Grain.model_construct(
452
453
  components={
453
454
  c.address
454
- for c in concepts_to_grain_concepts(concepts, environment=environment)
455
+ for c in concepts_to_grain_concepts(
456
+ concepts, environment=environment, local_concepts=local_concepts
457
+ )
455
458
  },
456
459
  where_clause=where_clause,
457
460
  )
@@ -126,9 +126,14 @@ def concept_is_relevant(
126
126
 
127
127
  return False
128
128
  if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
129
- if any([c in others for c in concept.keys]):
130
-
129
+ if all([c in others for c in concept.keys]):
131
130
  return False
131
+ if (
132
+ concept.purpose == Purpose.KEY
133
+ and concept.keys
134
+ and all([c in others for c in concept.keys])
135
+ ):
136
+ return False
132
137
  if concept.purpose in (Purpose.METRIC,):
133
138
  if all([c in others for c in concept.grain.components]):
134
139
  return False
@@ -1458,11 +1463,35 @@ class Factory:
1458
1463
  {} if local_concepts is None else local_concepts
1459
1464
  )
1460
1465
 
1466
+ def instantiate_concept(
1467
+ self,
1468
+ arg: (
1469
+ AggregateWrapper
1470
+ | FunctionCallWrapper
1471
+ | WindowItem
1472
+ | FilterItem
1473
+ | Function
1474
+ | ListWrapper[Any]
1475
+ | MapWrapper[Any, Any]
1476
+ | int
1477
+ | float
1478
+ | str
1479
+ ),
1480
+ ) -> tuple[Concept, BuildConcept]:
1481
+ from trilogy.parsing.common import arbitrary_to_concept
1482
+
1483
+ new = arbitrary_to_concept(
1484
+ arg,
1485
+ environment=self.environment,
1486
+ )
1487
+ built = self.build(new)
1488
+ self.local_concepts[new.address] = built
1489
+ return new, built
1490
+
1461
1491
  @singledispatchmethod
1462
1492
  def build(self, base):
1463
1493
  raise NotImplementedError("Cannot build {}".format(type(base)))
1464
1494
 
1465
- @build.register
1466
1495
  @build.register
1467
1496
  def _(
1468
1497
  self,
@@ -1496,31 +1525,6 @@ class Factory:
1496
1525
  ):
1497
1526
  return base
1498
1527
 
1499
- def instantiate_concept(
1500
- self,
1501
- arg: (
1502
- AggregateWrapper
1503
- | FunctionCallWrapper
1504
- | WindowItem
1505
- | FilterItem
1506
- | Function
1507
- | ListWrapper[Any]
1508
- | MapWrapper[Any, Any]
1509
- | int
1510
- | float
1511
- | str
1512
- ),
1513
- ) -> tuple[Concept, BuildConcept]:
1514
- from trilogy.parsing.common import arbitrary_to_concept
1515
-
1516
- new = arbitrary_to_concept(
1517
- arg,
1518
- environment=self.environment,
1519
- )
1520
- built = self.build(new)
1521
- self.local_concepts[new.address] = built
1522
- return new, built
1523
-
1524
1528
  @build.register
1525
1529
  def _(self, base: None) -> None:
1526
1530
  return base
@@ -1626,6 +1630,7 @@ class Factory:
1626
1630
  derivation, final_grain, build_lineage
1627
1631
  )
1628
1632
  is_aggregate = Concept.calculate_is_aggregate(build_lineage)
1633
+
1629
1634
  rval = BuildConcept.model_construct(
1630
1635
  name=base.name,
1631
1636
  datatype=base.datatype,
@@ -1646,7 +1651,6 @@ class Factory:
1646
1651
 
1647
1652
  @build.register
1648
1653
  def _(self, base: AggregateWrapper) -> BuildAggregateWrapper:
1649
-
1650
1654
  if not base.by:
1651
1655
  by = [
1652
1656
  self.build(self.environment.concepts[c]) for c in self.grain.components
@@ -56,12 +56,40 @@ def gen_filter_node(
56
56
  continue
57
57
  if conditions and conditions == where:
58
58
  optional_included.append(x)
59
+
60
+ # sometimes, it's okay to include other local optional above the filter
61
+ # in case it is, prep our list
62
+ extra_row_level_optional: list[BuildConcept] = []
63
+ for x in local_optional:
64
+ if x.address in optional_included:
65
+ continue
66
+ extra_row_level_optional.append(x)
67
+
68
+ # this flag controls whether we materialize the filter as a where on the prior CTE
69
+ # or do the filtering inline as a case statement
70
+ optimized_pushdown = False
71
+ if not is_scalar_condition(where.conditional):
72
+ optimized_pushdown = False
73
+ elif not local_optional:
74
+ optimized_pushdown = True
75
+ elif conditions and conditions == where:
76
+ logger.info(
77
+ f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
78
+ )
79
+ optimized_pushdown = True
80
+ elif optional_included == local_optional:
81
+ logger.info(
82
+ f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
83
+ )
84
+ optimized_pushdown = True
59
85
  logger.info(
60
86
  f"{padding(depth)}{LOGGER_PREFIX} filter `{concept}` condition `{concept.lineage.where}` derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
61
87
  )
62
88
  # we'll populate this with the row parent
63
89
  # and the existence parent(s)
64
90
  core_parents = []
91
+ if not optimized_pushdown:
92
+ parent_row_concepts += extra_row_level_optional
65
93
 
66
94
  row_parent: StrategyNode = source_concepts(
67
95
  mandatory_list=parent_row_concepts,
@@ -99,21 +127,6 @@ def gen_filter_node(
99
127
  )
100
128
  return None
101
129
 
102
- optimized_pushdown = False
103
- if not is_scalar_condition(where.conditional):
104
- optimized_pushdown = False
105
- elif not local_optional:
106
- optimized_pushdown = True
107
- elif conditions and conditions == where:
108
- logger.info(
109
- f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
110
- )
111
- optimized_pushdown = True
112
- elif optional_included == local_optional:
113
- logger.info(
114
- f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
115
- )
116
- optimized_pushdown = True
117
130
  if optimized_pushdown:
118
131
  logger.info(
119
132
  f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional}"
@@ -193,7 +206,9 @@ def gen_filter_node(
193
206
  + optional_outputs
194
207
  )
195
208
  return filter_node
196
-
209
+ logger.info(
210
+ f"{padding(depth)}{LOGGER_PREFIX} need to enrich filter node with additional concepts {[x.address for x in local_optional if x.address not in filter_node.output_concepts]}"
211
+ )
197
212
  enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
198
213
  # to then connect to the rest of the query
199
214
  mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
@@ -1,6 +1,7 @@
1
1
  from pydantic import BaseModel, ConfigDict, Field
2
2
 
3
3
  from trilogy.core.exceptions import UnresolvableQueryException
4
+ from trilogy.core.models.author import Concept
4
5
  from trilogy.core.models.build import BuildConcept, BuildWhereClause
5
6
  from trilogy.core.models.build_environment import BuildEnvironment
6
7
  from trilogy.core.models.environment import Environment
@@ -17,6 +18,7 @@ from .window_node import WindowNode
17
18
 
18
19
  class History(BaseModel):
19
20
  base_environment: Environment
21
+ local_base_concepts: dict[str, Concept] = Field(default_factory=dict)
20
22
  history: dict[str, StrategyNode | None] = Field(default_factory=dict)
21
23
  select_history: dict[str, StrategyNode | None] = Field(default_factory=dict)
22
24
  started: dict[str, int] = Field(default_factory=dict)
@@ -373,10 +373,9 @@ def get_query_node(
373
373
  ) -> StrategyNode:
374
374
  if not statement.output_components:
375
375
  raise ValueError(f"Statement has no output components {statement}")
376
-
377
376
  history = history or History(base_environment=environment)
378
377
  build_statement: BuildSelectLineage | BuildMultiSelectLineage = Factory(
379
- environment=environment
378
+ environment=environment,
380
379
  ).build(statement)
381
380
 
382
381
  # build_statement = statement
@@ -7,6 +7,7 @@ from pydantic.functional_validators import PlainValidator
7
7
 
8
8
  from trilogy.constants import CONFIG
9
9
  from trilogy.core.enums import (
10
+ ConceptSource,
10
11
  FunctionClass,
11
12
  IOType,
12
13
  Modifier,
@@ -134,7 +135,7 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
134
135
  meta=meta or Metadata(),
135
136
  )
136
137
 
137
- output.grain = output.calculate_grain(environment)
138
+ output.grain = output.calculate_grain(environment, output.local_concepts)
138
139
 
139
140
  for x in selection:
140
141
  if x.is_undefined and environment.concepts.fail_on_missing:
@@ -144,12 +145,13 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
144
145
  elif isinstance(x.content, ConceptTransform):
145
146
  if isinstance(x.content.output, UndefinedConcept):
146
147
  continue
147
- if (
148
- CONFIG.parsing.select_as_definition
149
- and not environment.frozen
150
- and x.concept.address not in environment.concepts
151
- ):
152
- environment.add_concept(x.content.output)
148
+ if CONFIG.parsing.select_as_definition and not environment.frozen:
149
+ if x.concept.address not in environment.concepts:
150
+ environment.add_concept(x.content.output)
151
+ elif x.concept.address in environment.concepts:
152
+ version = environment.concepts[x.concept.address]
153
+ if version.metadata.concept_source == ConceptSource.SELECT:
154
+ environment.add_concept(x.content.output, force=True)
153
155
  x.content.output = x.content.output.set_select_grain(
154
156
  output.grain, environment
155
157
  )
@@ -160,16 +162,26 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
160
162
  output.local_concepts[x.content.address] = environment.concepts[
161
163
  x.content.address
162
164
  ]
165
+
166
+ output.grain = output.calculate_grain(environment, output.local_concepts)
167
+
163
168
  output.validate_syntax(environment)
164
169
  return output
165
170
 
166
- def calculate_grain(self, environment: Environment | None = None) -> Grain:
171
+ def calculate_grain(
172
+ self,
173
+ environment: Environment | None = None,
174
+ local_concepts: dict[str, Concept] | None = None,
175
+ ) -> Grain:
167
176
  targets = []
168
177
  for x in self.selection:
169
178
  targets.append(x.concept)
170
179
 
171
180
  result = Grain.from_concepts(
172
- targets, where_clause=self.where_clause, environment=environment
181
+ targets,
182
+ where_clause=self.where_clause,
183
+ environment=environment,
184
+ local_concepts=local_concepts,
173
185
  )
174
186
  return result
175
187
 
trilogy/dialect/base.py CHANGED
@@ -199,6 +199,7 @@ FUNCTION_MAP = {
199
199
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
200
200
  FunctionType.DATE_PART: lambda x: f"date_part({x[0]},{x[1]})",
201
201
  FunctionType.DATE_ADD: lambda x: f"date_add({x[0]},{x[1]}, {x[2]})",
202
+ FunctionType.DATE_SUB: lambda x: f"date_sub({x[0]},{x[1]}, {x[2]})",
202
203
  FunctionType.DATE_DIFF: lambda x: f"date_diff({x[0]},{x[1]}, {x[2]})",
203
204
  FunctionType.DATE: lambda x: f"date({x[0]})",
204
205
  FunctionType.DATETIME: lambda x: f"datetime({x[0]})",
trilogy/parsing/common.py CHANGED
@@ -24,6 +24,7 @@ from trilogy.core.models.author import (
24
24
  AlignClause,
25
25
  AlignItem,
26
26
  Concept,
27
+ ConceptArgs,
27
28
  ConceptRef,
28
29
  FilterItem,
29
30
  Function,
@@ -191,16 +192,53 @@ def constant_to_concept(
191
192
  )
192
193
 
193
194
 
195
+ def atom_is_relevant(
196
+ atom,
197
+ others: list[Concept | ConceptRef],
198
+ environment: Environment | None = None,
199
+ ):
200
+ if isinstance(atom, (ConceptRef, Concept)):
201
+ # when we are looking at atoms, if there is a concept that is in others
202
+ # return directly
203
+ if atom.address in others:
204
+ return False
205
+ return concept_is_relevant(atom, others, environment)
206
+
207
+ if isinstance(atom, AggregateWrapper) and not atom.by:
208
+ return False
209
+ elif isinstance(atom, AggregateWrapper):
210
+ return any(atom_is_relevant(x, others, environment) for x in atom.by)
211
+
212
+ if isinstance(atom, Function):
213
+ relevant = False
214
+ print("atom args")
215
+ for arg in atom.arguments:
216
+ relevant = relevant or atom_is_relevant(arg, others, environment)
217
+ return relevant
218
+ elif isinstance(atom, FunctionCallWrapper):
219
+ return any(
220
+ [atom_is_relevant(atom.content, others, environment)]
221
+ + [atom_is_relevant(x, others, environment) for x in atom.args]
222
+ )
223
+ elif isinstance(atom, ConceptArgs):
224
+ # use atom is relevant here to trigger the early exit behavior for concpets in set
225
+ return any(
226
+ [atom_is_relevant(x, others, environment) for x in atom.concept_arguments]
227
+ )
228
+ return False
229
+
230
+
194
231
  def concept_is_relevant(
195
232
  concept: Concept | ConceptRef,
196
233
  others: list[Concept | ConceptRef],
197
234
  environment: Environment | None = None,
198
235
  ) -> bool:
199
- if isinstance(concept, UndefinedConcept):
200
236
 
237
+ if isinstance(concept, UndefinedConcept):
201
238
  return False
202
239
  if concept.datatype == DataType.UNKNOWN:
203
240
  return False
241
+
204
242
  if isinstance(concept, ConceptRef):
205
243
  if environment:
206
244
  concept = environment.concepts[concept.address]
@@ -208,41 +246,56 @@ def concept_is_relevant(
208
246
  raise SyntaxError(
209
247
  "Require environment to determine relevance of ConceptRef"
210
248
  )
211
-
249
+ if concept.derivation == Derivation.CONSTANT:
250
+ return False
212
251
  if concept.is_aggregate and not (
213
252
  isinstance(concept.lineage, AggregateWrapper) and concept.lineage.by
214
253
  ):
215
254
 
216
255
  return False
217
256
  if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
218
- if any([c in others for c in concept.keys]):
219
-
257
+ if all([c in others for c in concept.keys]):
220
258
  return False
259
+ if (
260
+ concept.purpose == Purpose.KEY
261
+ and concept.keys
262
+ and all([c in others for c in concept.keys])
263
+ ):
264
+ return False
221
265
  if concept.purpose in (Purpose.METRIC,):
222
266
  if all([c in others for c in concept.grain.components]):
223
267
  return False
224
- if concept.derivation in (Derivation.BASIC,):
225
- return any(
226
- concept_is_relevant(c, others, environment)
227
- for c in concept.concept_arguments
228
- )
268
+ if concept.derivation in (Derivation.BASIC,) and isinstance(
269
+ concept.lineage, Function
270
+ ):
271
+ relevant = False
272
+ for arg in concept.lineage.arguments:
273
+ relevant = atom_is_relevant(arg, others, environment) or relevant
274
+ return relevant
229
275
  if concept.granularity == Granularity.SINGLE_ROW:
230
276
  return False
231
277
  return True
232
278
 
233
279
 
234
280
  def concepts_to_grain_concepts(
235
- concepts: Iterable[Concept | ConceptRef | str], environment: Environment | None
281
+ concepts: Iterable[Concept | ConceptRef | str],
282
+ environment: Environment | None,
283
+ local_concepts: dict[str, Concept] | None = None,
236
284
  ) -> list[Concept]:
237
285
  pconcepts: list[Concept] = []
238
286
  for c in concepts:
239
-
240
287
  if isinstance(c, Concept):
241
288
  pconcepts.append(c)
242
289
  elif isinstance(c, ConceptRef) and environment:
243
- pconcepts.append(environment.concepts[c.address])
290
+ if local_concepts and c.address in local_concepts:
291
+ pconcepts.append(local_concepts[c.address])
292
+ else:
293
+ pconcepts.append(environment.concepts[c.address])
244
294
  elif isinstance(c, str) and environment:
245
- pconcepts.append(environment.concepts[c])
295
+ if local_concepts and c in local_concepts:
296
+ pconcepts.append(local_concepts[c])
297
+ else:
298
+ pconcepts.append(environment.concepts[c])
246
299
  else:
247
300
  raise ValueError(
248
301
  f"Unable to resolve input {c} without environment provided to concepts_to_grain call"
@@ -250,6 +303,7 @@ def concepts_to_grain_concepts(
250
303
 
251
304
  final: List[Concept] = []
252
305
  for sub in pconcepts:
306
+
253
307
  if not concept_is_relevant(sub, pconcepts, environment): # type: ignore
254
308
  continue
255
309
  final.append(sub)
@@ -366,7 +420,12 @@ def function_to_concept(
366
420
  is_metric = False
367
421
  ref_args, is_metric = get_relevant_parent_concepts(parent)
368
422
  concrete_args = [environment.concepts[c.address] for c in ref_args]
369
- pkeys += [x for x in concrete_args if not x.derivation == Derivation.CONSTANT]
423
+ pkeys += [
424
+ x
425
+ for x in concrete_args
426
+ if not x.derivation == Derivation.CONSTANT
427
+ and not (x.derivation == Derivation.AGGREGATE and not x.grain.components)
428
+ ]
370
429
  grain: Grain | None = Grain()
371
430
  for x in pkeys:
372
431
  grain += x.grain
@@ -376,7 +435,7 @@ def function_to_concept(
376
435
  modifiers = get_upstream_modifiers(pkeys, environment)
377
436
  key_grain: list[str] = []
378
437
  for x in pkeys:
379
- # metrics will group to keys, so do no do key traversal
438
+ # metrics will group to keys, so do not do key traversal
380
439
  if is_metric:
381
440
  key_grain.append(x.address)
382
441
  # otherwse, for row ops, assume keys are transitive
@@ -419,7 +478,6 @@ def function_to_concept(
419
478
  else:
420
479
  derivation = Derivation.BASIC
421
480
  granularity = Granularity.MULTI_ROW
422
-
423
481
  if grain is not None:
424
482
  r = Concept(
425
483
  name=name,
@@ -1,2 +1,8 @@
1
1
  class ParseError(Exception):
2
2
  pass
3
+
4
+
5
+ class NameShadowError(ParseError):
6
+ """
7
+ Raised when a name shadows another name in the same scope.
8
+ """
@@ -117,7 +117,6 @@ from trilogy.core.statements.author import (
117
117
  CopyStatement,
118
118
  FunctionDeclaration,
119
119
  ImportStatement,
120
- KeyMergeStatement,
121
120
  Limit,
122
121
  MergeStatementV2,
123
122
  MultiSelectStatement,
@@ -136,7 +135,7 @@ from trilogy.parsing.common import (
136
135
  process_function_args,
137
136
  rowset_to_concepts,
138
137
  )
139
- from trilogy.parsing.exceptions import ParseError
138
+ from trilogy.parsing.exceptions import NameShadowError, ParseError
140
139
 
141
140
  perf_logger = getLogger("trilogy.parse.performance")
142
141
 
@@ -814,6 +813,24 @@ class ParseToObjects(Transformer):
814
813
  )
815
814
  if self.parse_pass == ParsePass.VALIDATION:
816
815
  self.environment.add_datasource(datasource, meta=meta)
816
+ # if we have any foreign keys on the datasource, we can
817
+ # at this point optimize them to properties if they do not have other usage.
818
+ for column in columns:
819
+ # skip partial for now
820
+ if not grain:
821
+ continue
822
+ if column.concept.address in grain.components:
823
+ continue
824
+ target_c = self.environment.concepts[column.concept.address]
825
+ if target_c.purpose != Purpose.KEY:
826
+ continue
827
+
828
+ key_inputs = grain.components
829
+ keys = [self.environment.concepts[grain] for grain in key_inputs]
830
+ # target_c.purpose = Purpose.PROPERTY
831
+ target_c.keys = set([x.address for x in keys])
832
+ # target_c.grain = Grain(components={x.address for x in keys})
833
+
817
834
  return datasource
818
835
 
819
836
  @v_args(meta=True)
@@ -903,29 +920,6 @@ class ParseToObjects(Transformer):
903
920
  def over_list(self, args):
904
921
  return [x for x in args]
905
922
 
906
- @v_args(meta=True)
907
- def key_merge_statement(self, meta: Meta, args) -> KeyMergeStatement | None:
908
- key_inputs = args[:-1]
909
- target = args[-1]
910
- keys = [self.environment.concepts[grain] for grain in key_inputs]
911
- target_c = self.environment.concepts[target]
912
- new = KeyMergeStatement(
913
- keys=set([x.address for x in keys]),
914
- target=target_c.reference,
915
- )
916
- internal = Concept(
917
- name="_" + target_c.address.replace(".", "_"),
918
- namespace=self.environment.namespace,
919
- purpose=Purpose.PROPERTY,
920
- keys=set([x.address for x in keys]),
921
- datatype=target_c.datatype,
922
- grain=Grain(components={x.address for x in keys}),
923
- )
924
- self.environment.add_concept(internal)
925
- # always a full merge
926
- self.environment.merge_concept(target_c, internal, [])
927
- return new
928
-
929
923
  @v_args(meta=True)
930
924
  def merge_statement(self, meta: Meta, args) -> MergeStatementV2 | None:
931
925
  modifiers = []
@@ -1253,9 +1247,18 @@ class ParseToObjects(Transformer):
1253
1247
  ):
1254
1248
  intersection = base.locally_derived.intersection(pre_keys)
1255
1249
  if intersection:
1256
- raise ParseError(
1257
- f"Select statement {base} creates new derived concepts {list(intersection)} from transformations with identical name(s) to existing concept(s). Do you mean to drop the calculation and directly use the existing concept? If not, alias these concept(s) under new names."
1258
- )
1250
+ for x in intersection:
1251
+ if (
1252
+ base.local_concepts[x].derivation
1253
+ == self.environment.concepts[x].derivation
1254
+ ):
1255
+ raise NameShadowError(
1256
+ f"Select statement {base} derives concept {x} with identical derivation as named concept. Use the named concept directly."
1257
+ )
1258
+ else:
1259
+ raise NameShadowError(
1260
+ f"Select statement {base} creates new derived concepts {list(intersection)} with identical name(s) to existing concept(s). If these are identical, reference the concept directly. Otherwise alias your column as a new name."
1261
+ )
1259
1262
  return base
1260
1263
 
1261
1264
  @v_args(meta=True)
@@ -1854,6 +1857,8 @@ class ParseToObjects(Transformer):
1854
1857
 
1855
1858
  @v_args(meta=True)
1856
1859
  def fround(self, meta, args) -> Function:
1860
+ if len(args) == 1:
1861
+ args.append(0)
1857
1862
  return self.function_factory.create_function(args, FunctionType.ROUND, meta)
1858
1863
 
1859
1864
  @v_args(meta=True)
@@ -10,7 +10,6 @@
10
10
  | rowset_derivation_statement
11
11
  | import_statement
12
12
  | copy_statement
13
- | key_merge_statement
14
13
  | merge_statement
15
14
  | rawsql_statement
16
15
 
@@ -77,8 +76,6 @@
77
76
 
78
77
  align_clause: align_item ("AND"i align_item)* "AND"i?
79
78
 
80
- key_merge_statement: "merge"i "property"i "<" IDENTIFIER ("," IDENTIFIER )* ","? ">" "from"i IDENTIFIER
81
-
82
79
  merge_statement: "merge"i WILDCARD_IDENTIFIER "into"i SHORTHAND_MODIFIER? WILDCARD_IDENTIFIER
83
80
 
84
81
  // raw sql statement
@@ -223,7 +220,7 @@
223
220
  fdiv: ( "divide"i "(" expr "," expr ")")
224
221
  fmod: ( "mod"i "(" expr "," (int_lit | concept_lit ) ")")
225
222
  _ROUND.1: "round"i "("
226
- fround: _ROUND expr "," expr ")"
223
+ fround: _ROUND expr ("," expr)? ")"
227
224
  fabs: "abs"i "(" expr ")"
228
225
  _SQRT.1: "sqrt("
229
226
  fsqrt: _SQRT expr ")"