pytrilogy 0.0.3.33__py3-none-any.whl → 0.0.3.34__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.33.dist-info → pytrilogy-0.0.3.34.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.33.dist-info → pytrilogy-0.0.3.34.dist-info}/RECORD +19 -19
- trilogy/__init__.py +1 -1
- trilogy/authoring/__init__.py +6 -0
- trilogy/core/models/author.py +1 -1
- trilogy/core/models/build_environment.py +6 -13
- trilogy/core/models/environment.py +2 -1
- trilogy/core/models/execute.py +8 -0
- trilogy/core/processing/concept_strategies_v3.py +17 -1
- trilogy/core/processing/node_generators/select_merge_node.py +2 -0
- trilogy/core/processing/utility.py +11 -5
- trilogy/core/statements/author.py +5 -0
- trilogy/parsing/parse_engine.py +24 -0
- trilogy/parsing/render.py +6 -0
- trilogy/parsing/trilogy.lark +3 -0
- {pytrilogy-0.0.3.33.dist-info → pytrilogy-0.0.3.34.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.33.dist-info → pytrilogy-0.0.3.34.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.33.dist-info → pytrilogy-0.0.3.34.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.33.dist-info → pytrilogy-0.0.3.34.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.34.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=RkaJLhtJbPi75Dpq1wKhAgq3CaTPGqR2AIjV7Ot7NWA,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
|
|
@@ -8,7 +8,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
|
|
10
10
|
trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
|
|
11
|
-
trilogy/authoring/__init__.py,sha256=
|
|
11
|
+
trilogy/authoring/__init__.py,sha256=v9PRuZs4fTnxhpXAnwTxCDwlLasUax6g2FONidcujR4,2369
|
|
12
12
|
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=fWexUZtssfvP5TiD7eQ66Q_tPUCNCCTGNSzLbVXrnqQ,7358
|
|
@@ -22,22 +22,22 @@ trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
|
|
|
22
22
|
trilogy/core/optimization.py,sha256=aihzx4-2-mSjx5td1TDTYGvc7e9Zvy-_xEyhPqLS-Ig,8314
|
|
23
23
|
trilogy/core/query_processor.py,sha256=Do8YpdPBdsbKtl9n37hobzk8SORMGqH-e_zNNxd-BE4,19456
|
|
24
24
|
trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
trilogy/core/models/author.py,sha256=
|
|
25
|
+
trilogy/core/models/author.py,sha256=Z5RQr5vsPyxLSXR11w_b8z-tQ54bSM3ks1az9uV3d40,76579
|
|
26
26
|
trilogy/core/models/build.py,sha256=N3Zr47iN4y9gFWS72uYjecmiQfcm8Fl9wT3QS8xKBAA,58185
|
|
27
|
-
trilogy/core/models/build_environment.py,sha256=
|
|
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
|
-
trilogy/core/models/environment.py,sha256=
|
|
31
|
-
trilogy/core/models/execute.py,sha256=
|
|
30
|
+
trilogy/core/models/environment.py,sha256=axgk7W3STy5EIrG8fUwl2oh6WCqeBAr7PWy6EOe-_Dc,27002
|
|
31
|
+
trilogy/core/models/execute.py,sha256=mQm5Gydo2Ph0W7w9wm5dQEarS04PC-IKAgNVsdqOZsQ,34524
|
|
32
32
|
trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
|
|
33
33
|
trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
|
|
34
34
|
trilogy/core/optimizations/inline_constant.py,sha256=lvNTIXaLNkw3HseJyXyDNk5R52doLU9sIg3pmU2_S08,1332
|
|
35
35
|
trilogy/core/optimizations/inline_datasource.py,sha256=AHuTGh2x0GQ8usOe0NiFncfTFQ_KogdgDl4uucmhIbI,4241
|
|
36
36
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
|
|
37
37
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
38
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=m24X5FOOcLTCvY1MY1yUK5qFcWwokMWZ5cAFI2YN9G8,43352
|
|
39
39
|
trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
|
|
40
|
-
trilogy/core/processing/utility.py,sha256=
|
|
40
|
+
trilogy/core/processing/utility.py,sha256=3tC__aT76EzcnIexZfOqCH-3WzvPiCAYWs9TBoMvGjc,20903
|
|
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=ZsDzThjm_mAtdQpKAg8QIJiPVZ4KuUkKyilj4eOhSDs,9439
|
|
@@ -47,7 +47,7 @@ trilogy/core/processing/node_generators/group_to_node.py,sha256=E5bEjovSx422d_Ml
|
|
|
47
47
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
48
48
|
trilogy/core/processing/node_generators/node_merge_node.py,sha256=sv55oynfqgpHEpo1OEtVDri-5fywzPhDlR85qaWikvY,16195
|
|
49
49
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=YmBs6ZQ7azLXRFEmeoecpGjK4pMHsUCovuBxfb3UKZI,6848
|
|
50
|
-
trilogy/core/processing/node_generators/select_merge_node.py,sha256=
|
|
50
|
+
trilogy/core/processing/node_generators/select_merge_node.py,sha256=lxXhMhDKGbu67QFNbbAT-BO8gbWppIvjn_hAXpLEPe0,19953
|
|
51
51
|
trilogy/core/processing/node_generators/select_node.py,sha256=Y-zO0AFkTrpi2LyebjpyHU7WWANr7nKZSS9rY7DH4Wo,1888
|
|
52
52
|
trilogy/core/processing/node_generators/synonym_node.py,sha256=9LHK2XHDjbyTLjmDQieskG8fqbiSpRnFOkfrutDnOTE,2258
|
|
53
53
|
trilogy/core/processing/node_generators/union_node.py,sha256=zuMSmgF170vzlp2BBQEhKbqUMjVl2xQDqUB82Dhv-VU,2536
|
|
@@ -65,7 +65,7 @@ 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=
|
|
68
|
+
trilogy/core/statements/author.py,sha256=SWB755fSZo0rxc9MzN1E7-NwmTqA7BhFul0ENqU9P5k,14727
|
|
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
|
|
@@ -91,9 +91,9 @@ trilogy/parsing/common.py,sha256=bhZ_f520D2diRcXC3YeEToPSXBbUBBNq42zII_smdYc,235
|
|
|
91
91
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
92
92
|
trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
|
|
93
93
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
94
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
95
|
-
trilogy/parsing/render.py,sha256=
|
|
96
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
94
|
+
trilogy/parsing/parse_engine.py,sha256=2zUqtpEyTaxNUz4L3IkA9RKHEFif7VDjxF0Y7sFigts,63506
|
|
95
|
+
trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
|
|
96
|
+
trilogy/parsing/trilogy.lark,sha256=ErSKUy2sqpmc3OnflRbQnzTeE81y3ISfUJBD_wvypFM,13159
|
|
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/date.preql,sha256=0MHeGLp2mG4QBKtmozcBZ7qVjOAdWOtrliIKn6hz1Pc,95
|
|
|
102
102
|
trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
|
|
103
103
|
trilogy/std/geography.preql,sha256=-fqAGnBL6tR-UtT8DbSek3iMFg66ECR_B_41pODxv-k,504
|
|
104
104
|
trilogy/std/money.preql,sha256=ZHW-csTX-kYbOLmKSO-TcGGgQ-_DMrUXy0BjfuJSFxM,80
|
|
105
|
-
pytrilogy-0.0.3.
|
|
106
|
-
pytrilogy-0.0.3.
|
|
107
|
-
pytrilogy-0.0.3.
|
|
108
|
-
pytrilogy-0.0.3.
|
|
109
|
-
pytrilogy-0.0.3.
|
|
105
|
+
pytrilogy-0.0.3.34.dist-info/METADATA,sha256=GisPUfJ5BGF4EpuzvClSgxb6ITvhKhX3lv2Ht9brw2M,9100
|
|
106
|
+
pytrilogy-0.0.3.34.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
107
|
+
pytrilogy-0.0.3.34.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
108
|
+
pytrilogy-0.0.3.34.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
109
|
+
pytrilogy-0.0.3.34.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/authoring/__init__.py
CHANGED
|
@@ -19,12 +19,15 @@ from trilogy.core.models.author import (
|
|
|
19
19
|
Conditional,
|
|
20
20
|
FilterItem,
|
|
21
21
|
Function,
|
|
22
|
+
FunctionCallWrapper,
|
|
22
23
|
HavingClause,
|
|
23
24
|
MagicConstants,
|
|
24
25
|
Metadata,
|
|
26
|
+
MultiSelectLineage,
|
|
25
27
|
OrderBy,
|
|
26
28
|
OrderItem,
|
|
27
29
|
Parenthetical,
|
|
30
|
+
RowsetItem,
|
|
28
31
|
SubselectComparison,
|
|
29
32
|
WhereClause,
|
|
30
33
|
WindowItem,
|
|
@@ -103,4 +106,7 @@ __all__ = [
|
|
|
103
106
|
"RawSQLStatement",
|
|
104
107
|
"Datasource",
|
|
105
108
|
"DatasourceMetadata",
|
|
109
|
+
"MultiSelectLineage",
|
|
110
|
+
"RowsetItem",
|
|
111
|
+
"FunctionCallWrapper",
|
|
106
112
|
]
|
trilogy/core/models/author.py
CHANGED
|
@@ -1089,7 +1089,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1089
1089
|
pseudonyms=self.pseudonyms,
|
|
1090
1090
|
)
|
|
1091
1091
|
|
|
1092
|
-
@
|
|
1092
|
+
@cached_property
|
|
1093
1093
|
def sources(self) -> List["ConceptRef"]:
|
|
1094
1094
|
if self.lineage:
|
|
1095
1095
|
output: List[ConceptRef] = []
|
|
@@ -31,19 +31,12 @@ class BuildEnvironmentConceptDict(dict):
|
|
|
31
31
|
def raise_undefined(
|
|
32
32
|
self, key: str, line_no: int | None = None, file: Path | str | None = None
|
|
33
33
|
) -> Never:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
message
|
|
39
|
-
|
|
40
|
-
if line_no:
|
|
41
|
-
if file:
|
|
42
|
-
raise UndefinedConceptException(
|
|
43
|
-
f"{file}: {line_no}: " + message, matches
|
|
44
|
-
)
|
|
45
|
-
raise UndefinedConceptException(f"line: {line_no}: " + message, matches)
|
|
46
|
-
raise UndefinedConceptException(message, matches)
|
|
34
|
+
# build environment should never check for missing values.
|
|
35
|
+
if line_no is not None:
|
|
36
|
+
message = f"Concept '{key}' not found in environment at line {line_no}."
|
|
37
|
+
else:
|
|
38
|
+
message = f"Concept '{key}' not found in environment."
|
|
39
|
+
raise UndefinedConceptException(message, [])
|
|
47
40
|
|
|
48
41
|
def __getitem__(
|
|
49
42
|
self, key: str, line_no: int | None = None, file: Path | None = None
|
|
@@ -686,7 +686,8 @@ class Environment(BaseModel):
|
|
|
686
686
|
replacements[k] = target
|
|
687
687
|
# we need to update keys and grains of all concepts
|
|
688
688
|
else:
|
|
689
|
-
|
|
689
|
+
if source.address in v.sources or source.address in v.grain.components:
|
|
690
|
+
replacements[k] = v.with_merge(source, target, modifiers)
|
|
690
691
|
self.concepts.update(replacements)
|
|
691
692
|
for k, ds in self.datasources.items():
|
|
692
693
|
if source.address in ds.output_lcl:
|
trilogy/core/models/execute.py
CHANGED
|
@@ -282,6 +282,7 @@ class CTE(BaseModel):
|
|
|
282
282
|
**self.existence_source_map,
|
|
283
283
|
**other.existence_source_map,
|
|
284
284
|
}
|
|
285
|
+
|
|
285
286
|
return self
|
|
286
287
|
|
|
287
288
|
@property
|
|
@@ -764,8 +765,15 @@ class QueryDatasource(BaseModel):
|
|
|
764
765
|
def identifier(self) -> str:
|
|
765
766
|
filters = abs(hash(str(self.condition))) if self.condition else ""
|
|
766
767
|
grain = "_".join([str(c).replace(".", "_") for c in self.grain.components])
|
|
768
|
+
group = ""
|
|
769
|
+
if self.source_type == SourceType.GROUP:
|
|
770
|
+
keys = [
|
|
771
|
+
x.address for x in self.output_concepts if x.purpose != Purpose.METRIC
|
|
772
|
+
]
|
|
773
|
+
group = "_grouped_by_" + "_".join(keys)
|
|
767
774
|
return (
|
|
768
775
|
"_join_".join([d.identifier for d in self.datasources])
|
|
776
|
+
+ group
|
|
769
777
|
+ (f"_at_{grain}" if grain else "_at_abstract")
|
|
770
778
|
+ (f"_filtered_by_{filters}" if filters else "")
|
|
771
779
|
)
|
|
@@ -860,7 +860,7 @@ def _search_concepts(
|
|
|
860
860
|
and priority_concept.address not in conditions.row_arguments
|
|
861
861
|
):
|
|
862
862
|
logger.info(
|
|
863
|
-
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Force including conditions to push filtering above complex condition that is not condition member or parent"
|
|
863
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Force including conditions in {priority_concept.address} to push filtering above complex condition that is not condition member or parent"
|
|
864
864
|
)
|
|
865
865
|
local_conditions = conditions
|
|
866
866
|
|
|
@@ -936,7 +936,23 @@ def _search_concepts(
|
|
|
936
936
|
if complete == ValidationResult.COMPLETE and (
|
|
937
937
|
not accept_partial or (accept_partial and not partial)
|
|
938
938
|
):
|
|
939
|
+
logger.info(
|
|
940
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} breaking loop, complete"
|
|
941
|
+
)
|
|
939
942
|
break
|
|
943
|
+
elif complete == ValidationResult.COMPLETE and accept_partial and partial:
|
|
944
|
+
if len(attempted) == len(mandatory_list):
|
|
945
|
+
logger.info(
|
|
946
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Breaking as we have attempted all nodes"
|
|
947
|
+
)
|
|
948
|
+
break
|
|
949
|
+
logger.info(
|
|
950
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Found complete stack with partials {partial}, continuing search, attempted {attempted} all {len(mandatory_list)}"
|
|
951
|
+
)
|
|
952
|
+
else:
|
|
953
|
+
logger.info(
|
|
954
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Not complete, continuing search"
|
|
955
|
+
)
|
|
940
956
|
# if we have attempted on root node, we've tried them all.
|
|
941
957
|
# inject in another search with filter concepts
|
|
942
958
|
if priority_concept.derivation == Derivation.ROOT:
|
|
@@ -344,12 +344,14 @@ def create_datasource_node(
|
|
|
344
344
|
for c in datasource.columns
|
|
345
345
|
if not c.is_complete and c.concept.address in all_concepts
|
|
346
346
|
]
|
|
347
|
+
|
|
347
348
|
partial_lcl = LooseBuildConceptList(concepts=partial_concepts)
|
|
348
349
|
nullable_concepts = [
|
|
349
350
|
c.concept
|
|
350
351
|
for c in datasource.columns
|
|
351
352
|
if c.is_nullable and c.concept.address in all_concepts
|
|
352
353
|
]
|
|
354
|
+
|
|
353
355
|
nullable_lcl = LooseBuildConceptList(concepts=nullable_concepts)
|
|
354
356
|
partial_is_full = conditions and (conditions == datasource.non_partial_for)
|
|
355
357
|
|
|
@@ -81,7 +81,7 @@ class JoinOrderOutput:
|
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def resolve_join_order_v2(
|
|
84
|
-
g: nx.Graph, partials: dict[str, list[str]]
|
|
84
|
+
g: nx.Graph, partials: dict[str, list[str]], nullables: dict[str, list[str]]
|
|
85
85
|
) -> list[JoinOrderOutput]:
|
|
86
86
|
datasources = [x for x in g.nodes if x.startswith("ds~")]
|
|
87
87
|
concepts = [x for x in g.nodes if x.startswith("c~")]
|
|
@@ -108,7 +108,7 @@ def resolve_join_order_v2(
|
|
|
108
108
|
root = next_pivots[0]
|
|
109
109
|
pivots = [x for x in pivots if x != root]
|
|
110
110
|
else:
|
|
111
|
-
root = pivots.pop()
|
|
111
|
+
root = pivots.pop(0)
|
|
112
112
|
|
|
113
113
|
# sort so less partials is last and eligible lefts are
|
|
114
114
|
def score_key(x: str) -> tuple[int, int, str]:
|
|
@@ -119,6 +119,8 @@ def resolve_join_order_v2(
|
|
|
119
119
|
# if it has the concept as a partial, lower weight
|
|
120
120
|
if root in partials.get(x, []):
|
|
121
121
|
base -= 1
|
|
122
|
+
if root in nullables.get(x, []):
|
|
123
|
+
base -= 1
|
|
122
124
|
return (base, len(x), x)
|
|
123
125
|
|
|
124
126
|
# get remainig un-joined datasets
|
|
@@ -159,9 +161,11 @@ def resolve_join_order_v2(
|
|
|
159
161
|
)
|
|
160
162
|
right_is_partial = any(key in partials.get(right, []) for key in common)
|
|
161
163
|
# we don't care if left is nullable for join type (just keys), but if we did
|
|
162
|
-
#
|
|
164
|
+
# left_is_nullable = any(
|
|
165
|
+
# key in nullables.get(left_candidate, []) for key in common
|
|
166
|
+
# )
|
|
163
167
|
right_is_nullable = any(
|
|
164
|
-
key in
|
|
168
|
+
key in nullables.get(right, []) for key in common
|
|
165
169
|
)
|
|
166
170
|
if left_is_partial:
|
|
167
171
|
join_type = JoinType.FULL
|
|
@@ -356,6 +360,7 @@ def get_node_joins(
|
|
|
356
360
|
) -> List[BaseJoin]:
|
|
357
361
|
graph = nx.Graph()
|
|
358
362
|
partials: dict[str, list[str]] = {}
|
|
363
|
+
nullables: dict[str, list[str]] = {}
|
|
359
364
|
ds_node_map: dict[str, QueryDatasource | BuildDatasource] = {}
|
|
360
365
|
concept_map: dict[str, BuildConcept] = {}
|
|
361
366
|
for datasource in datasources:
|
|
@@ -363,6 +368,7 @@ def get_node_joins(
|
|
|
363
368
|
ds_node_map[ds_node] = datasource
|
|
364
369
|
graph.add_node(ds_node, type=NodeType.NODE)
|
|
365
370
|
partials[ds_node] = [f"c~{c.address}" for c in datasource.partial_concepts]
|
|
371
|
+
nullables[ds_node] = [f"c~{c.address}" for c in datasource.nullable_concepts]
|
|
366
372
|
for concept in datasource.output_concepts:
|
|
367
373
|
if concept.address in datasource.hidden_concepts:
|
|
368
374
|
continue
|
|
@@ -374,7 +380,7 @@ def get_node_joins(
|
|
|
374
380
|
environment=environment,
|
|
375
381
|
)
|
|
376
382
|
|
|
377
|
-
joins = resolve_join_order_v2(graph, partials=partials)
|
|
383
|
+
joins = resolve_join_order_v2(graph, partials=partials, nullables=nullables)
|
|
378
384
|
return [
|
|
379
385
|
BaseJoin(
|
|
380
386
|
left_datasource=ds_node_map[j.left] if j.left else None,
|
|
@@ -384,6 +384,11 @@ class MergeStatementV2(HasUUID, BaseModel):
|
|
|
384
384
|
modifiers: List[Modifier] = Field(default_factory=list)
|
|
385
385
|
|
|
386
386
|
|
|
387
|
+
class KeyMergeStatement(HasUUID, BaseModel):
|
|
388
|
+
keys: set[str]
|
|
389
|
+
target: ConceptRef
|
|
390
|
+
|
|
391
|
+
|
|
387
392
|
class ImportStatement(HasUUID, BaseModel):
|
|
388
393
|
# import abc.def as bar
|
|
389
394
|
# the bit after 'as', eg bar
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -116,6 +116,7 @@ from trilogy.core.statements.author import (
|
|
|
116
116
|
CopyStatement,
|
|
117
117
|
FunctionDeclaration,
|
|
118
118
|
ImportStatement,
|
|
119
|
+
KeyMergeStatement,
|
|
119
120
|
Limit,
|
|
120
121
|
MergeStatementV2,
|
|
121
122
|
MultiSelectStatement,
|
|
@@ -890,6 +891,29 @@ class ParseToObjects(Transformer):
|
|
|
890
891
|
def over_list(self, args):
|
|
891
892
|
return [x for x in args]
|
|
892
893
|
|
|
894
|
+
@v_args(meta=True)
|
|
895
|
+
def key_merge_statement(self, meta: Meta, args) -> KeyMergeStatement | None:
|
|
896
|
+
key_inputs = args[:-1]
|
|
897
|
+
target = args[-1]
|
|
898
|
+
keys = [self.environment.concepts[grain] for grain in key_inputs]
|
|
899
|
+
target_c = self.environment.concepts[target]
|
|
900
|
+
new = KeyMergeStatement(
|
|
901
|
+
keys=set([x.address for x in keys]),
|
|
902
|
+
target=target_c.reference,
|
|
903
|
+
)
|
|
904
|
+
internal = Concept(
|
|
905
|
+
name="_" + target_c.address.replace(".", "_"),
|
|
906
|
+
namespace=self.environment.namespace,
|
|
907
|
+
purpose=Purpose.PROPERTY,
|
|
908
|
+
keys=set([x.address for x in keys]),
|
|
909
|
+
datatype=target_c.datatype,
|
|
910
|
+
grain=Grain(components={x.address for x in keys}),
|
|
911
|
+
)
|
|
912
|
+
self.environment.add_concept(internal)
|
|
913
|
+
# always a full merge
|
|
914
|
+
self.environment.merge_concept(target_c, internal, [])
|
|
915
|
+
return new
|
|
916
|
+
|
|
893
917
|
@v_args(meta=True)
|
|
894
918
|
def merge_statement(self, meta: Meta, args) -> MergeStatementV2 | None:
|
|
895
919
|
modifiers = []
|
trilogy/parsing/render.py
CHANGED
|
@@ -51,6 +51,7 @@ from trilogy.core.statements.author import (
|
|
|
51
51
|
CopyStatement,
|
|
52
52
|
FunctionDeclaration,
|
|
53
53
|
ImportStatement,
|
|
54
|
+
KeyMergeStatement,
|
|
54
55
|
MergeStatementV2,
|
|
55
56
|
MultiSelectStatement,
|
|
56
57
|
PersistStatement,
|
|
@@ -525,6 +526,11 @@ class Renderer:
|
|
|
525
526
|
return f"MERGE {self.to_string(arg.sources[0])} into {''.join([self.to_string(modifier) for modifier in arg.modifiers])}{self.to_string(arg.targets[arg.sources[0].address])};"
|
|
526
527
|
return f"MERGE {arg.source_wildcard}.* into {''.join([self.to_string(modifier) for modifier in arg.modifiers])}{arg.target_wildcard}.*;"
|
|
527
528
|
|
|
529
|
+
@to_string.register
|
|
530
|
+
def _(self, arg: KeyMergeStatement):
|
|
531
|
+
keys = ", ".join(sorted(list(arg.keys)))
|
|
532
|
+
return f"MERGE PROPERTY <{keys}> from {arg.target.address};"
|
|
533
|
+
|
|
528
534
|
@to_string.register
|
|
529
535
|
def _(self, arg: Modifier):
|
|
530
536
|
if arg == Modifier.PARTIAL:
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
| rowset_derivation_statement
|
|
11
11
|
| import_statement
|
|
12
12
|
| copy_statement
|
|
13
|
+
| key_merge_statement
|
|
13
14
|
| merge_statement
|
|
14
15
|
| rawsql_statement
|
|
15
16
|
|
|
@@ -76,6 +77,8 @@
|
|
|
76
77
|
|
|
77
78
|
align_clause: align_item ("AND"i align_item)* "AND"i?
|
|
78
79
|
|
|
80
|
+
key_merge_statement: "merge"i "property"i "<" IDENTIFIER ("," IDENTIFIER )* ","? ">" "from"i IDENTIFIER
|
|
81
|
+
|
|
79
82
|
merge_statement: "merge"i WILDCARD_IDENTIFIER "into"i SHORTHAND_MODIFIER? WILDCARD_IDENTIFIER
|
|
80
83
|
|
|
81
84
|
// raw sql statement
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|