pytrilogy 0.0.3.100__py3-none-any.whl → 0.0.3.102__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.
- {pytrilogy-0.0.3.100.dist-info → pytrilogy-0.0.3.102.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.100.dist-info → pytrilogy-0.0.3.102.dist-info}/RECORD +21 -21
- trilogy/__init__.py +1 -1
- trilogy/core/exceptions.py +1 -1
- trilogy/core/functions.py +5 -2
- trilogy/core/models/author.py +9 -0
- trilogy/core/models/build.py +11 -4
- trilogy/core/models/core.py +3 -0
- trilogy/core/processing/node_generators/group_node.py +1 -0
- trilogy/core/processing/node_generators/node_merge_node.py +0 -3
- trilogy/core/validation/datasource.py +30 -6
- trilogy/dialect/base.py +1 -1
- trilogy/parsing/common.py +11 -1
- trilogy/parsing/parse_engine.py +3 -0
- trilogy/parsing/render.py +130 -29
- trilogy/parsing/trilogy.lark +8 -3
- trilogy/std/display.preql +5 -2
- {pytrilogy-0.0.3.100.dist-info → pytrilogy-0.0.3.102.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.100.dist-info → pytrilogy-0.0.3.102.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.100.dist-info → pytrilogy-0.0.3.102.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.100.dist-info → pytrilogy-0.0.3.102.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.102.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=WZdbHlLqyuYo0xjcYkV5QDokunZLDlhGeibgoay48uc,304
|
|
3
3
|
trilogy/constants.py,sha256=ohmro6so7PPNp2ruWQKVc0ijjXYPOyRrxB9LI8dr3TU,1746
|
|
4
4
|
trilogy/engine.py,sha256=3MiADf5MKcmxqiHBuRqiYdsXiLj7oitDfVvXvHrfjkA,2178
|
|
5
5
|
trilogy/executor.py,sha256=KgCAQhHPT-j0rPkBbALX0f84W9-Q-bkjHayGuavg99w,16490
|
|
@@ -14,18 +14,18 @@ trilogy/core/enums.py,sha256=H8I2Dz4POHZ4ixYCGzNs4c3KDqxLQklGLVfmje1DSMo,8877
|
|
|
14
14
|
trilogy/core/env_processor.py,sha256=H-rr2ALj31l5oh3FqeI47Qju6OOfiXBacXNJGNZ92zQ,4521
|
|
15
15
|
trilogy/core/environment_helpers.py,sha256=TRlqVctqIRBxzfjRBmpQsAVoiCcsEKBhG1B6PUE0l1M,12743
|
|
16
16
|
trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
|
|
17
|
-
trilogy/core/exceptions.py,sha256=
|
|
18
|
-
trilogy/core/functions.py,sha256=
|
|
17
|
+
trilogy/core/exceptions.py,sha256=axkVXYJYQXCCwMHwlyDA232g4tCOwdCZUt7eHeUMDMg,2829
|
|
18
|
+
trilogy/core/functions.py,sha256=sdV6Z3NUVfwL1d18eNcaAXllVNqzLez23McsJ6xIp7M,33182
|
|
19
19
|
trilogy/core/graph_models.py,sha256=4EWFTHGfYd72zvS2HYoV6hm7nMC_VEd7vWr6txY-ig0,3400
|
|
20
20
|
trilogy/core/internal.py,sha256=r9QagDB2GvpqlyD_I7VrsfbVfIk5mnok2znEbv72Aa4,2681
|
|
21
21
|
trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
|
|
22
22
|
trilogy/core/query_processor.py,sha256=uqygDJqkjIH4vLP-lbGRgTN7rRcYEkr3KGqNimNw_80,20345
|
|
23
23
|
trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
|
|
24
24
|
trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
trilogy/core/models/author.py,sha256=
|
|
26
|
-
trilogy/core/models/build.py,sha256=
|
|
25
|
+
trilogy/core/models/author.py,sha256=3I7PFpJgoQT9RPOT3DfiqAjEtkcQPJnScs60I2UoyWo,81461
|
|
26
|
+
trilogy/core/models/build.py,sha256=iqk_-3plxX1BdxvUCTebqE9F3x62f40neKGf6Ld4VVU,70858
|
|
27
27
|
trilogy/core/models/build_environment.py,sha256=mpx7MKGc60fnZLVdeLi2YSREy7eQbQYycCrP4zF-rHU,5258
|
|
28
|
-
trilogy/core/models/core.py,sha256=
|
|
28
|
+
trilogy/core/models/core.py,sha256=iT9WdZoiXeglmUHWn6bZyXCTBpkApTGPKtNm_Mhbu_g,12987
|
|
29
29
|
trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
|
|
30
30
|
trilogy/core/models/environment.py,sha256=hwTIRnJgaHUdCYof7U5A9NPitGZ2s9yxqiW5O2SaJ9Y,28759
|
|
31
31
|
trilogy/core/models/execute.py,sha256=lsNzNjS3nZvoW5CHjYwxDTwBe502NZyytpK1eq8CwW4,42357
|
|
@@ -45,10 +45,10 @@ trilogy/core/processing/node_generators/basic_node.py,sha256=0Uhnf07056SBbRkt-wY
|
|
|
45
45
|
trilogy/core/processing/node_generators/common.py,sha256=PdysdroW9DUADP7f5Wv_GKPUyCTROZV1g3L45fawxi8,9443
|
|
46
46
|
trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
|
|
47
47
|
trilogy/core/processing/node_generators/filter_node.py,sha256=ArBsQJl-4fWBJWCE28CRQ7UT7ErnFfbcseoQQZrBodY,11220
|
|
48
|
-
trilogy/core/processing/node_generators/group_node.py,sha256=
|
|
48
|
+
trilogy/core/processing/node_generators/group_node.py,sha256=yqOWl5TCV4PrdJua4OJkPUIHkljaLoSW2Y8eRAmVddQ,6733
|
|
49
49
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
|
|
50
50
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
51
|
-
trilogy/core/processing/node_generators/node_merge_node.py,sha256=
|
|
51
|
+
trilogy/core/processing/node_generators/node_merge_node.py,sha256=1joMV7XpQ9Gpe-d5y7JUMBHIqakV5wFJi3Mtvs4UcL4,23415
|
|
52
52
|
trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
|
|
53
53
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=5L5u6xz1In8EaHQdcYgR2si-tz9WB9YLXURo4AkUT9A,6630
|
|
54
54
|
trilogy/core/processing/node_generators/select_merge_node.py,sha256=KQvGoNT5ZBWQ_caEomRTtG1PKZC7OPT4PKfY0QmwMGE,22270
|
|
@@ -77,11 +77,11 @@ trilogy/core/statements/execute.py,sha256=kiwJcVeMa4wZR-xLfM2oYOJ9DeyJkP8An38WFy
|
|
|
77
77
|
trilogy/core/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
78
78
|
trilogy/core/validation/common.py,sha256=Sd-towAX1uSDe3dK51FcVtIwVrMhayEwdHqhzeJHro0,4776
|
|
79
79
|
trilogy/core/validation/concept.py,sha256=PM2BxBxLvuBScSWZMPsDZVcOblDil5pNT0pHLcLhdPA,5242
|
|
80
|
-
trilogy/core/validation/datasource.py,sha256=
|
|
80
|
+
trilogy/core/validation/datasource.py,sha256=nJeEFyb6iMBwlEVdYVy1vLzAbdRZwOsUjGxgWKgY8oM,7636
|
|
81
81
|
trilogy/core/validation/environment.py,sha256=ymvhQyt7jLK641JAAIQkqjQaAmr9C5022ILzYvDgPP0,2835
|
|
82
82
|
trilogy/core/validation/fix.py,sha256=Z818UFNLxndMTLiyhB3doLxIfnOZ-16QGvVFWuD7UsA,3750
|
|
83
83
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
|
-
trilogy/dialect/base.py,sha256=
|
|
84
|
+
trilogy/dialect/base.py,sha256=d2gXfa5Jh3uyN9H9MxG53JT-xQQgntq2X7EprobJYUc,49698
|
|
85
85
|
trilogy/dialect/bigquery.py,sha256=XS3hpybeowgfrOrkycAigAF3NX2YUzTzfgE6f__2fT4,4316
|
|
86
86
|
trilogy/dialect/common.py,sha256=_MarnMWRBn3VcNt3k5VUdFrwH6oHzGdNQquSpHNLq4o,5644
|
|
87
87
|
trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
|
|
@@ -99,26 +99,26 @@ trilogy/hooks/graph_hook.py,sha256=5BfR7Dt0bgEsCLgwjowgCsVkboGYfVJGOz8g9mqpnos,4
|
|
|
99
99
|
trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
|
|
100
100
|
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
101
101
|
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
102
|
-
trilogy/parsing/common.py,sha256=
|
|
102
|
+
trilogy/parsing/common.py,sha256=NJLm31J3W9BLWq1ClhNvYE43jrF950698KJ3o0UfSCo,31340
|
|
103
103
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
104
104
|
trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
|
|
105
105
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
106
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
107
|
-
trilogy/parsing/render.py,sha256=
|
|
108
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
106
|
+
trilogy/parsing/parse_engine.py,sha256=T-3Q4UH256IB6cfX85crScZwZ6gAwslgv0fy3WKBdjc,81930
|
|
107
|
+
trilogy/parsing/render.py,sha256=IklKMdXiqQEB6D28PrU1BewlDwD88Hnmqn1xjA9h720,23863
|
|
108
|
+
trilogy/parsing/trilogy.lark,sha256=6eBDD6d4D9N1Nnn4CtmaoB-NpOpjHrEn5oi0JykAlbE,16509
|
|
109
109
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
110
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
111
111
|
trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
112
112
|
trilogy/std/date.preql,sha256=HWZm4t4HWyxr5geWRsY05RnHBVDMci8z8YA2cu0-OOw,188
|
|
113
|
-
trilogy/std/display.preql,sha256=
|
|
113
|
+
trilogy/std/display.preql,sha256=S20HW8qbShBc4OZPcHYiRlLdcaBp9dwruozWBoXKscs,293
|
|
114
114
|
trilogy/std/geography.preql,sha256=1A9Sq5PPMBnEPPf7f-rPVYxJfsnWpQ8oV_k4Fm3H2dU,675
|
|
115
115
|
trilogy/std/metric.preql,sha256=DRECGhkMyqfit5Fl4Ut9zbWrJuSMI1iO9HikuyoBpE0,421
|
|
116
116
|
trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
|
|
117
117
|
trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
|
|
118
118
|
trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
|
|
119
119
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
120
|
-
pytrilogy-0.0.3.
|
|
121
|
-
pytrilogy-0.0.3.
|
|
122
|
-
pytrilogy-0.0.3.
|
|
123
|
-
pytrilogy-0.0.3.
|
|
124
|
-
pytrilogy-0.0.3.
|
|
120
|
+
pytrilogy-0.0.3.102.dist-info/METADATA,sha256=fQKKWHDkY9Nhofow6RO22oMSXp91H-vOD5d3kk3S-V8,11811
|
|
121
|
+
pytrilogy-0.0.3.102.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
122
|
+
pytrilogy-0.0.3.102.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
123
|
+
pytrilogy-0.0.3.102.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
124
|
+
pytrilogy-0.0.3.102.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/core/exceptions.py
CHANGED
|
@@ -69,7 +69,7 @@ class DatasourceColumnBindingData:
|
|
|
69
69
|
actual_modifiers: List[Modifier]
|
|
70
70
|
|
|
71
71
|
def format_failure(self):
|
|
72
|
-
return f"Concept {self.address} value '{self.value}' with type {self.value_modifiers} does not conform to expected type {str(self.actual_type)} with modifiers {self.actual_modifiers}"
|
|
72
|
+
return f"Concept {self.address} value '{self.value}' with type {self.value_type} and {self.value_modifiers} does not conform to expected type {str(self.actual_type)} with modifiers {self.actual_modifiers}"
|
|
73
73
|
|
|
74
74
|
def is_modifier_issue(self) -> bool:
|
|
75
75
|
return len(self.value_modifiers) > 0 and any(
|
trilogy/core/functions.py
CHANGED
|
@@ -18,6 +18,7 @@ from trilogy.core.models.author import (
|
|
|
18
18
|
AggregateWrapper,
|
|
19
19
|
Concept,
|
|
20
20
|
ConceptRef,
|
|
21
|
+
Conditional,
|
|
21
22
|
Function,
|
|
22
23
|
Parenthetical,
|
|
23
24
|
UndefinedConcept,
|
|
@@ -129,8 +130,8 @@ def validate_case_output(
|
|
|
129
130
|
def create_struct_output(
|
|
130
131
|
args: list[Any],
|
|
131
132
|
) -> StructType:
|
|
132
|
-
zipped = dict(zip(args[::2], args[
|
|
133
|
-
types = [arg_to_datatype(x) for x in args[
|
|
133
|
+
zipped = dict(zip(args[1::2], args[::2]))
|
|
134
|
+
types = [arg_to_datatype(x) for x in args[::2]]
|
|
134
135
|
return StructType(fields=types, fields_map=zipped)
|
|
135
136
|
|
|
136
137
|
|
|
@@ -997,6 +998,8 @@ def argument_to_purpose(arg) -> Purpose:
|
|
|
997
998
|
return argument_to_purpose(arg.content)
|
|
998
999
|
elif isinstance(arg, WindowItem):
|
|
999
1000
|
return Purpose.PROPERTY
|
|
1001
|
+
elif isinstance(arg, Conditional):
|
|
1002
|
+
return Purpose.PROPERTY
|
|
1000
1003
|
elif isinstance(arg, Concept):
|
|
1001
1004
|
base = arg.purpose
|
|
1002
1005
|
if (
|
trilogy/core/models/author.py
CHANGED
|
@@ -259,6 +259,15 @@ class Parenthetical(
|
|
|
259
259
|
)
|
|
260
260
|
)
|
|
261
261
|
|
|
262
|
+
def with_reference_replacement(self, source, target):
|
|
263
|
+
return Parenthetical.model_construct(
|
|
264
|
+
content=(
|
|
265
|
+
self.content.with_reference_replacement(source, target)
|
|
266
|
+
if isinstance(self.content, Mergeable)
|
|
267
|
+
else self.content
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
|
|
262
271
|
@property
|
|
263
272
|
def concept_arguments(self) -> Sequence[ConceptRef]:
|
|
264
273
|
base: List[ConceptRef] = []
|
trilogy/core/models/build.py
CHANGED
|
@@ -1511,7 +1511,10 @@ def requires_concept_nesting(
|
|
|
1511
1511
|
) -> AggregateWrapper | WindowItem | FilterItem | Function | None:
|
|
1512
1512
|
if isinstance(expr, (AggregateWrapper, WindowItem, FilterItem)):
|
|
1513
1513
|
return expr
|
|
1514
|
-
if isinstance(expr, Function) and expr.operator
|
|
1514
|
+
if isinstance(expr, Function) and expr.operator in (
|
|
1515
|
+
FunctionType.GROUP,
|
|
1516
|
+
FunctionType.PARENTHETICAL,
|
|
1517
|
+
):
|
|
1515
1518
|
# group by requires nesting
|
|
1516
1519
|
return expr
|
|
1517
1520
|
return None
|
|
@@ -1696,13 +1699,12 @@ class Factory:
|
|
|
1696
1699
|
return self._build_case_when(base)
|
|
1697
1700
|
|
|
1698
1701
|
def _build_case_when(self, base: CaseWhen) -> BuildCaseWhen:
|
|
1699
|
-
comparison = base.comparison
|
|
1700
1702
|
expr: Concept | FuncArgs = base.expr
|
|
1701
1703
|
validation = requires_concept_nesting(expr)
|
|
1702
1704
|
if validation:
|
|
1703
1705
|
expr, _ = self.instantiate_concept(validation)
|
|
1704
1706
|
return BuildCaseWhen(
|
|
1705
|
-
comparison=self.build(comparison),
|
|
1707
|
+
comparison=self.build(base.comparison),
|
|
1706
1708
|
expr=self.build(expr),
|
|
1707
1709
|
)
|
|
1708
1710
|
|
|
@@ -2019,7 +2021,12 @@ class Factory:
|
|
|
2019
2021
|
return self._build_parenthetical(base)
|
|
2020
2022
|
|
|
2021
2023
|
def _build_parenthetical(self, base: Parenthetical) -> BuildParenthetical:
|
|
2022
|
-
|
|
2024
|
+
validate = requires_concept_nesting(base.content)
|
|
2025
|
+
if validate:
|
|
2026
|
+
content, _ = self.instantiate_concept(validate)
|
|
2027
|
+
return BuildParenthetical(content=self.build(content))
|
|
2028
|
+
else:
|
|
2029
|
+
return BuildParenthetical(content=self.build(base.content))
|
|
2023
2030
|
|
|
2024
2031
|
@build.register
|
|
2025
2032
|
def _(self, base: SelectLineage) -> BuildSelectLineage:
|
trilogy/core/models/core.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from abc import ABC
|
|
4
4
|
from collections import UserDict, UserList
|
|
5
5
|
from datetime import date, datetime
|
|
6
|
+
from decimal import Decimal
|
|
6
7
|
from enum import Enum
|
|
7
8
|
from typing import (
|
|
8
9
|
Any,
|
|
@@ -448,6 +449,8 @@ def arg_to_datatype(arg) -> CONCRETE_TYPES:
|
|
|
448
449
|
return DataType.STRING
|
|
449
450
|
elif isinstance(arg, float):
|
|
450
451
|
return DataType.FLOAT
|
|
452
|
+
elif isinstance(arg, Decimal):
|
|
453
|
+
return DataType.NUMERIC
|
|
451
454
|
elif isinstance(arg, DataType):
|
|
452
455
|
return arg
|
|
453
456
|
elif isinstance(arg, NumericType):
|
|
@@ -164,9 +164,6 @@ def reinject_common_join_keys_v2(
|
|
|
164
164
|
reduced = BuildGrain.from_concepts(concrete_concepts).components
|
|
165
165
|
existing_addresses = set()
|
|
166
166
|
for concrete in concrete_concepts:
|
|
167
|
-
logger.debug(
|
|
168
|
-
f"looking at column {concrete.address} with pseudonyms {concrete.pseudonyms}"
|
|
169
|
-
)
|
|
170
167
|
cnode = concept_to_node(concrete.with_default_grain())
|
|
171
168
|
if cnode in final.nodes:
|
|
172
169
|
existing_addresses.add(concrete.address)
|
|
@@ -36,31 +36,51 @@ def type_check(
|
|
|
36
36
|
) -> bool:
|
|
37
37
|
if input is None and nullable:
|
|
38
38
|
return True
|
|
39
|
+
|
|
39
40
|
target_type = expected_type
|
|
40
41
|
while isinstance(target_type, TraitDataType):
|
|
41
42
|
return type_check(input, target_type.data_type, nullable)
|
|
43
|
+
|
|
42
44
|
if target_type == DataType.STRING:
|
|
43
45
|
return isinstance(input, str)
|
|
44
46
|
if target_type == DataType.INTEGER:
|
|
45
47
|
return isinstance(input, int)
|
|
48
|
+
if target_type == DataType.BIGINT:
|
|
49
|
+
return isinstance(input, int) # or check for larger int if needed
|
|
46
50
|
if target_type == DataType.FLOAT or isinstance(target_type, NumericType):
|
|
47
51
|
return (
|
|
48
52
|
isinstance(input, float)
|
|
49
53
|
or isinstance(input, int)
|
|
50
54
|
or isinstance(input, Decimal)
|
|
51
55
|
)
|
|
56
|
+
if target_type == DataType.NUMBER:
|
|
57
|
+
return isinstance(input, (int, float, Decimal))
|
|
58
|
+
if target_type == DataType.NUMERIC:
|
|
59
|
+
return isinstance(input, (int, float, Decimal))
|
|
52
60
|
if target_type == DataType.BOOL:
|
|
53
61
|
return isinstance(input, bool)
|
|
54
62
|
if target_type == DataType.DATE:
|
|
55
|
-
return isinstance(input, date)
|
|
63
|
+
return isinstance(input, date) and not isinstance(input, datetime)
|
|
56
64
|
if target_type == DataType.DATETIME:
|
|
57
65
|
return isinstance(input, datetime)
|
|
66
|
+
if target_type == DataType.TIMESTAMP:
|
|
67
|
+
return isinstance(input, datetime) # or timestamp type if you have one
|
|
68
|
+
if target_type == DataType.UNIX_SECONDS:
|
|
69
|
+
return isinstance(input, (int, float)) # Unix timestamps are numeric
|
|
70
|
+
if target_type == DataType.DATE_PART:
|
|
71
|
+
return isinstance(
|
|
72
|
+
input, str
|
|
73
|
+
) # assuming date parts are strings like "year", "month"
|
|
58
74
|
if target_type == DataType.ARRAY or isinstance(target_type, ArrayType):
|
|
59
75
|
return isinstance(input, list)
|
|
60
76
|
if target_type == DataType.MAP or isinstance(target_type, MapType):
|
|
61
77
|
return isinstance(input, dict)
|
|
62
78
|
if target_type == DataType.STRUCT or isinstance(target_type, StructType):
|
|
63
79
|
return isinstance(input, dict)
|
|
80
|
+
if target_type == DataType.NULL:
|
|
81
|
+
return input is None
|
|
82
|
+
if target_type == DataType.UNKNOWN:
|
|
83
|
+
return True
|
|
64
84
|
return False
|
|
65
85
|
|
|
66
86
|
|
|
@@ -125,15 +145,19 @@ def validate_datasource(
|
|
|
125
145
|
rval = row[actual_address]
|
|
126
146
|
passed = type_check(rval, col.concept.datatype, col.is_nullable)
|
|
127
147
|
if not passed:
|
|
148
|
+
value_type = (
|
|
149
|
+
arg_to_datatype(rval) if rval is not None else col.concept.datatype
|
|
150
|
+
)
|
|
151
|
+
traits = None
|
|
152
|
+
if isinstance(col.concept.datatype, TraitDataType):
|
|
153
|
+
traits = col.concept.datatype.traits
|
|
154
|
+
if traits and not isinstance(value_type, TraitDataType):
|
|
155
|
+
value_type = TraitDataType(type=value_type, traits=traits)
|
|
128
156
|
failures.append(
|
|
129
157
|
DatasourceColumnBindingData(
|
|
130
158
|
address=col.concept.address,
|
|
131
159
|
value=rval,
|
|
132
|
-
value_type=
|
|
133
|
-
arg_to_datatype(rval)
|
|
134
|
-
if rval is not None
|
|
135
|
-
else col.concept.datatype
|
|
136
|
-
),
|
|
160
|
+
value_type=value_type,
|
|
137
161
|
value_modifiers=[Modifier.NULLABLE] if rval is None else [],
|
|
138
162
|
actual_type=col.concept.datatype,
|
|
139
163
|
actual_modifiers=col.concept.modifiers,
|
trilogy/dialect/base.py
CHANGED
trilogy/parsing/common.py
CHANGED
|
@@ -249,11 +249,14 @@ def atom_is_relevant(
|
|
|
249
249
|
return atom_is_relevant(atom.left, others, environment) or atom_is_relevant(
|
|
250
250
|
atom.right, others, environment
|
|
251
251
|
)
|
|
252
|
+
elif isinstance(atom, Parenthetical):
|
|
253
|
+
return atom_is_relevant(atom.content, others, environment)
|
|
252
254
|
elif isinstance(atom, ConceptArgs):
|
|
253
255
|
# use atom is relevant here to trigger the early exit behavior for concepts in set
|
|
254
256
|
return any(
|
|
255
257
|
[atom_is_relevant(x, others, environment) for x in atom.concept_arguments]
|
|
256
258
|
)
|
|
259
|
+
|
|
257
260
|
return False
|
|
258
261
|
|
|
259
262
|
|
|
@@ -294,12 +297,18 @@ def concept_is_relevant(
|
|
|
294
297
|
if all([c in others for c in concept.grain.components]):
|
|
295
298
|
return False
|
|
296
299
|
if concept.derivation in (Derivation.BASIC,) and isinstance(
|
|
297
|
-
concept.lineage, Function
|
|
300
|
+
concept.lineage, (Function, CaseWhen)
|
|
298
301
|
):
|
|
299
302
|
relevant = False
|
|
300
303
|
for arg in concept.lineage.arguments:
|
|
301
304
|
relevant = atom_is_relevant(arg, others, environment) or relevant
|
|
305
|
+
|
|
302
306
|
return relevant
|
|
307
|
+
if concept.derivation in (Derivation.BASIC,) and isinstance(
|
|
308
|
+
concept.lineage, Parenthetical
|
|
309
|
+
):
|
|
310
|
+
return atom_is_relevant(concept.lineage.content, others, environment)
|
|
311
|
+
|
|
303
312
|
if concept.granularity == Granularity.SINGLE_ROW:
|
|
304
313
|
return False
|
|
305
314
|
return True
|
|
@@ -346,6 +355,7 @@ def concepts_to_grain_concepts(
|
|
|
346
355
|
if sub.address in seen:
|
|
347
356
|
continue
|
|
348
357
|
if not concept_is_relevant(sub, pconcepts, environment): # type: ignore
|
|
358
|
+
|
|
349
359
|
continue
|
|
350
360
|
seen.add(sub.address)
|
|
351
361
|
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -992,6 +992,9 @@ class ParseToObjects(Transformer):
|
|
|
992
992
|
def order_by(self, args):
|
|
993
993
|
return OrderBy(items=args[0])
|
|
994
994
|
|
|
995
|
+
def over_component(self, args):
|
|
996
|
+
return ConceptRef(address=args[0].value.lstrip(",").strip())
|
|
997
|
+
|
|
995
998
|
def over_list(self, args):
|
|
996
999
|
return [x for x in args]
|
|
997
1000
|
|
trilogy/parsing/render.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from dataclasses import dataclass
|
|
2
4
|
from datetime import date, datetime
|
|
3
5
|
from functools import singledispatchmethod
|
|
4
6
|
from typing import Any
|
|
@@ -23,6 +25,7 @@ from trilogy.core.models.author import (
|
|
|
23
25
|
FunctionCallWrapper,
|
|
24
26
|
Grain,
|
|
25
27
|
OrderBy,
|
|
28
|
+
Ordering,
|
|
26
29
|
OrderItem,
|
|
27
30
|
Parenthetical,
|
|
28
31
|
SubselectComparison,
|
|
@@ -67,23 +70,72 @@ from trilogy.core.statements.author import (
|
|
|
67
70
|
|
|
68
71
|
QUERY_TEMPLATE = Template(
|
|
69
72
|
"""{% if where %}WHERE
|
|
70
|
-
|
|
73
|
+
{{ where }}
|
|
71
74
|
{% endif %}SELECT{%- for select in select_columns %}
|
|
72
|
-
|
|
75
|
+
{{ select }},{% endfor %}{% if having %}
|
|
73
76
|
HAVING
|
|
74
|
-
|
|
77
|
+
{{ having }}
|
|
75
78
|
{% endif %}{%- if order_by %}
|
|
76
79
|
ORDER BY{% for order in order_by %}
|
|
77
|
-
|
|
80
|
+
{{ order }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{%- if limit is not none %}
|
|
78
81
|
LIMIT {{ limit }}{% endif %}
|
|
79
82
|
;"""
|
|
80
83
|
)
|
|
81
84
|
|
|
82
85
|
|
|
86
|
+
@dataclass
|
|
87
|
+
class IndentationContext:
|
|
88
|
+
"""Tracks indentation state during rendering"""
|
|
89
|
+
|
|
90
|
+
depth: int = 0
|
|
91
|
+
indent_string: str = " " # 4 spaces by default
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def current_indent(self) -> str:
|
|
95
|
+
return self.indent_string * self.depth
|
|
96
|
+
|
|
97
|
+
def increase_depth(self, extra_levels: int = 1) -> "IndentationContext":
|
|
98
|
+
return IndentationContext(
|
|
99
|
+
depth=self.depth + extra_levels, indent_string=self.indent_string
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
83
103
|
class Renderer:
|
|
84
104
|
|
|
85
|
-
def __init__(
|
|
105
|
+
def __init__(
|
|
106
|
+
self, environment: Environment | None = None, indent_string: str = " "
|
|
107
|
+
):
|
|
86
108
|
self.environment = environment
|
|
109
|
+
self.indent_context = IndentationContext(indent_string=indent_string)
|
|
110
|
+
|
|
111
|
+
@contextmanager
|
|
112
|
+
def indented(self, levels: int = 1):
|
|
113
|
+
"""Context manager for temporarily increasing indentation"""
|
|
114
|
+
old_context = self.indent_context
|
|
115
|
+
self.indent_context = self.indent_context.increase_depth(levels)
|
|
116
|
+
try:
|
|
117
|
+
yield
|
|
118
|
+
finally:
|
|
119
|
+
self.indent_context = old_context
|
|
120
|
+
|
|
121
|
+
def indent_lines(self, text: str, extra_levels: int = 0) -> str:
|
|
122
|
+
"""Apply current indentation to all lines in text"""
|
|
123
|
+
if not text:
|
|
124
|
+
return text
|
|
125
|
+
|
|
126
|
+
indent = self.indent_context.indent_string * (
|
|
127
|
+
self.indent_context.depth + extra_levels
|
|
128
|
+
)
|
|
129
|
+
lines = text.split("\n")
|
|
130
|
+
indented_lines = []
|
|
131
|
+
|
|
132
|
+
for line in lines:
|
|
133
|
+
if line.strip(): # Only indent non-empty lines
|
|
134
|
+
indented_lines.append(indent + line)
|
|
135
|
+
else:
|
|
136
|
+
indented_lines.append(line) # Keep empty lines as-is
|
|
137
|
+
|
|
138
|
+
return "\n".join(indented_lines)
|
|
87
139
|
|
|
88
140
|
def render_statement_string(self, list_of_statements: list[Any]) -> str:
|
|
89
141
|
new = []
|
|
@@ -98,7 +150,7 @@ class Renderer:
|
|
|
98
150
|
new.append("\n\n")
|
|
99
151
|
else:
|
|
100
152
|
new.append("\n")
|
|
101
|
-
new.append(
|
|
153
|
+
new.append(self.to_string(stmt))
|
|
102
154
|
last_statement_type = stmt_type
|
|
103
155
|
return "".join(new)
|
|
104
156
|
|
|
@@ -192,14 +244,19 @@ class Renderer:
|
|
|
192
244
|
|
|
193
245
|
@to_string.register
|
|
194
246
|
def _(self, arg: Datasource):
|
|
195
|
-
|
|
247
|
+
with self.indented():
|
|
248
|
+
assignments = ",\n".join(
|
|
249
|
+
[self.indent_lines(self.to_string(x)) for x in arg.columns]
|
|
250
|
+
)
|
|
251
|
+
|
|
196
252
|
if arg.non_partial_for:
|
|
197
253
|
non_partial = f"\ncomplete where {self.to_string(arg.non_partial_for)}"
|
|
198
254
|
else:
|
|
199
255
|
non_partial = ""
|
|
256
|
+
|
|
200
257
|
base = f"""datasource {arg.name} (
|
|
201
|
-
|
|
202
|
-
|
|
258
|
+
{assignments}
|
|
259
|
+
)
|
|
203
260
|
{self.to_string(arg.grain) if arg.grain.components else ''}{non_partial}
|
|
204
261
|
{self.to_string(arg.address)}"""
|
|
205
262
|
|
|
@@ -349,7 +406,8 @@ class Renderer:
|
|
|
349
406
|
else:
|
|
350
407
|
output = f"{concept.purpose.value} {namespace}{concept.name} <- {self.to_string(concept.lineage)};"
|
|
351
408
|
if base_description:
|
|
352
|
-
|
|
409
|
+
lines = "\n#".join(base_description.split("\n"))
|
|
410
|
+
output += f" #{lines}"
|
|
353
411
|
return output
|
|
354
412
|
|
|
355
413
|
@to_string.register
|
|
@@ -389,26 +447,45 @@ class Renderer:
|
|
|
389
447
|
|
|
390
448
|
@to_string.register
|
|
391
449
|
def _(self, arg: SelectStatement):
|
|
450
|
+
with self.indented():
|
|
451
|
+
select_columns = [
|
|
452
|
+
self.indent_lines(self.to_string(c)) for c in arg.selection
|
|
453
|
+
]
|
|
454
|
+
where_clause = None
|
|
455
|
+
if arg.where_clause:
|
|
456
|
+
where_clause = self.indent_lines(self.to_string(arg.where_clause))
|
|
457
|
+
having_clause = None
|
|
458
|
+
if arg.having_clause:
|
|
459
|
+
having_clause = self.indent_lines(self.to_string(arg.having_clause))
|
|
460
|
+
order_by = None
|
|
461
|
+
if arg.order_by:
|
|
462
|
+
order_by = [
|
|
463
|
+
self.indent_lines(self.to_string(c)) for c in arg.order_by.items
|
|
464
|
+
]
|
|
465
|
+
|
|
392
466
|
return QUERY_TEMPLATE.render(
|
|
393
|
-
select_columns=
|
|
394
|
-
where=
|
|
395
|
-
having=
|
|
396
|
-
order_by=
|
|
397
|
-
[self.to_string(c) for c in arg.order_by.items]
|
|
398
|
-
if arg.order_by
|
|
399
|
-
else None
|
|
400
|
-
),
|
|
467
|
+
select_columns=select_columns,
|
|
468
|
+
where=where_clause,
|
|
469
|
+
having=having_clause,
|
|
470
|
+
order_by=order_by,
|
|
401
471
|
limit=arg.limit,
|
|
402
472
|
)
|
|
403
473
|
|
|
404
474
|
@to_string.register
|
|
405
475
|
def _(self, arg: MultiSelectStatement):
|
|
406
|
-
|
|
476
|
+
# Each select gets its own indentation
|
|
477
|
+
select_parts = []
|
|
478
|
+
for select in arg.selects:
|
|
479
|
+
select_parts.append(
|
|
480
|
+
self.to_string(select)[:-2]
|
|
481
|
+
) # Remove the trailing ";\n"
|
|
482
|
+
|
|
483
|
+
base = "\nMERGE\n".join(select_parts)
|
|
407
484
|
base += self.to_string(arg.align)
|
|
408
485
|
if arg.where_clause:
|
|
409
486
|
base += f"\nWHERE\n{self.to_string(arg.where_clause)}"
|
|
410
487
|
if arg.order_by:
|
|
411
|
-
base += f"\nORDER BY\n
|
|
488
|
+
base += f"\nORDER BY\n{self.to_string(arg.order_by)}"
|
|
412
489
|
if arg.limit:
|
|
413
490
|
base += f"\nLIMIT {arg.limit}"
|
|
414
491
|
base += "\n;"
|
|
@@ -420,7 +497,9 @@ class Renderer:
|
|
|
420
497
|
|
|
421
498
|
@to_string.register
|
|
422
499
|
def _(self, arg: AlignClause):
|
|
423
|
-
|
|
500
|
+
with self.indented():
|
|
501
|
+
align_items = [self.indent_lines(self.to_string(c)) for c in arg.items]
|
|
502
|
+
return "\nALIGN\n" + ",\n".join(align_items)
|
|
424
503
|
|
|
425
504
|
@to_string.register
|
|
426
505
|
def _(self, arg: AlignItem):
|
|
@@ -428,7 +507,13 @@ class Renderer:
|
|
|
428
507
|
|
|
429
508
|
@to_string.register
|
|
430
509
|
def _(self, arg: OrderBy):
|
|
431
|
-
|
|
510
|
+
with self.indented():
|
|
511
|
+
order_items = [self.indent_lines(self.to_string(c)) for c in arg.items]
|
|
512
|
+
return ",\n".join(order_items)
|
|
513
|
+
|
|
514
|
+
@to_string.register
|
|
515
|
+
def _(self, arg: Ordering):
|
|
516
|
+
return arg.value
|
|
432
517
|
|
|
433
518
|
@to_string.register
|
|
434
519
|
def _(self, arg: "WhereClause"):
|
|
@@ -439,7 +524,7 @@ class Renderer:
|
|
|
439
524
|
|
|
440
525
|
@to_string.register
|
|
441
526
|
def _(self, arg: "Conditional"):
|
|
442
|
-
return f"
|
|
527
|
+
return f"{self.to_string(arg.left)} {arg.operator.value} {self.to_string(arg.right)}"
|
|
443
528
|
|
|
444
529
|
@to_string.register
|
|
445
530
|
def _(self, arg: "SubselectComparison"):
|
|
@@ -451,7 +536,8 @@ class Renderer:
|
|
|
451
536
|
|
|
452
537
|
@to_string.register
|
|
453
538
|
def _(self, arg: "Comment"):
|
|
454
|
-
|
|
539
|
+
lines = "\n#".join(arg.text.split("\n"))
|
|
540
|
+
return f"{lines}"
|
|
455
541
|
|
|
456
542
|
@to_string.register
|
|
457
543
|
def _(self, arg: "WindowItem"):
|
|
@@ -467,7 +553,6 @@ class Renderer:
|
|
|
467
553
|
|
|
468
554
|
@to_string.register
|
|
469
555
|
def _(self, arg: "FilterItem"):
|
|
470
|
-
|
|
471
556
|
return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
|
|
472
557
|
|
|
473
558
|
@to_string.register
|
|
@@ -536,18 +621,34 @@ class Renderer:
|
|
|
536
621
|
if len(args) == 1:
|
|
537
622
|
return f"group({args[0]})"
|
|
538
623
|
return f"group({args[0]}) by {arg_string}"
|
|
539
|
-
inputs = ",".join(args)
|
|
540
624
|
|
|
541
625
|
if arg.operator == FunctionType.CONSTANT:
|
|
542
|
-
return f"{
|
|
626
|
+
return f"{', '.join(args)}"
|
|
543
627
|
if arg.operator == FunctionType.CAST:
|
|
544
628
|
return f"CAST({self.to_string(arg.arguments[0])} AS {self.to_string(arg.arguments[1])})"
|
|
545
629
|
if arg.operator == FunctionType.INDEX_ACCESS:
|
|
546
630
|
return f"{self.to_string(arg.arguments[0])}[{self.to_string(arg.arguments[1])}]"
|
|
547
631
|
|
|
548
632
|
if arg.operator == FunctionType.CASE:
|
|
549
|
-
|
|
550
|
-
|
|
633
|
+
with self.indented():
|
|
634
|
+
indented_args = [
|
|
635
|
+
self.indent_lines(self.to_string(a)) for a in arg.arguments
|
|
636
|
+
]
|
|
637
|
+
inputs = "\n".join(indented_args)
|
|
638
|
+
return f"CASE\n{inputs}\n{self.indent_context.current_indent}END"
|
|
639
|
+
|
|
640
|
+
if arg.operator == FunctionType.STRUCT:
|
|
641
|
+
# zip arguments to pairs
|
|
642
|
+
input_pairs = zip(arg.arguments[0::2], arg.arguments[1::2])
|
|
643
|
+
with self.indented():
|
|
644
|
+
pair_strings = []
|
|
645
|
+
for k, v in input_pairs:
|
|
646
|
+
pair_line = f"{self.to_string(k)}-> {v}"
|
|
647
|
+
pair_strings.append(self.indent_lines(pair_line))
|
|
648
|
+
inputs = ",\n".join(pair_strings)
|
|
649
|
+
return f"struct(\n{inputs}\n{self.indent_context.current_indent})"
|
|
650
|
+
|
|
651
|
+
inputs = ",".join(args)
|
|
551
652
|
return f"{arg.operator.value}({inputs})"
|
|
552
653
|
|
|
553
654
|
@to_string.register
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -134,10 +134,14 @@
|
|
|
134
134
|
metadata: "metadata" "(" IDENTIFIER "=" string_lit ")"
|
|
135
135
|
|
|
136
136
|
limit: "LIMIT"i /[0-9]+/
|
|
137
|
+
|
|
138
|
+
_order_atom: expr ordering
|
|
139
|
+
|
|
140
|
+
order_list: _order_atom ("," _order_atom)* ","?
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
over_component: /,\s*[a-zA-Z\_][a-zA-Z0-9\_\.]*/ "END"?
|
|
139
143
|
|
|
140
|
-
over_list: concept_lit
|
|
144
|
+
over_list: concept_lit over_component*
|
|
141
145
|
|
|
142
146
|
ORDERING_DIRECTION: /ASC|DESC/i
|
|
143
147
|
|
|
@@ -433,7 +437,8 @@
|
|
|
433
437
|
map_lit: "{" (literal ":" literal ",")* literal ":" literal ","? "}"
|
|
434
438
|
|
|
435
439
|
_STRUCT.1: "struct("i
|
|
436
|
-
|
|
440
|
+
_BINDING.1: "->"
|
|
441
|
+
struct_lit: _STRUCT expr _BINDING IDENTIFIER ( "," expr _BINDING IDENTIFIER )* ","? ")"
|
|
437
442
|
|
|
438
443
|
!bool_lit: "True"i | "False"i
|
|
439
444
|
|
trilogy/std/display.preql
CHANGED
|
@@ -2,5 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
type percent float; # Percentage value
|
|
4
4
|
|
|
5
|
-
def calc_percent(a, b, digits=-1) -> case when digits =-1 then
|
|
6
|
-
|
|
5
|
+
def calc_percent(a, b, digits=-1) -> case when digits =-1 then
|
|
6
|
+
case when b = 0 then 0.0::float::percent else
|
|
7
|
+
(a/b)::float::percent end
|
|
8
|
+
else round((case when b = 0 then 0.0::float::percent else
|
|
9
|
+
(a/b)::float::percent end):: float::percent, digits) end;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|