pytrilogy 0.0.3.115__py3-none-any.whl → 0.0.3.116__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.115.dist-info → pytrilogy-0.0.3.116.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.115.dist-info → pytrilogy-0.0.3.116.dist-info}/RECORD +25 -25
- trilogy/__init__.py +1 -1
- trilogy/constants.py +1 -0
- trilogy/core/enums.py +1 -0
- trilogy/core/functions.py +8 -0
- trilogy/core/models/author.py +123 -0
- trilogy/core/models/build.py +53 -5
- trilogy/core/models/environment.py +2 -1
- trilogy/core/optimization.py +3 -2
- trilogy/core/optimizations/hide_unused_concept.py +1 -5
- trilogy/core/processing/concept_strategies_v3.py +2 -0
- trilogy/core/processing/node_generators/basic_node.py +26 -15
- trilogy/core/processing/node_generators/common.py +4 -1
- trilogy/core/processing/node_generators/multiselect_node.py +3 -3
- trilogy/core/statements/author.py +3 -0
- trilogy/dialect/base.py +30 -2
- trilogy/executor.py +1 -1
- trilogy/parsing/common.py +109 -17
- trilogy/parsing/parse_engine.py +58 -1
- trilogy/parsing/trilogy.lark +9 -2
- {pytrilogy-0.0.3.115.dist-info → pytrilogy-0.0.3.116.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.115.dist-info → pytrilogy-0.0.3.116.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.115.dist-info → pytrilogy-0.0.3.116.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.115.dist-info → pytrilogy-0.0.3.116.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
3
|
-
trilogy/constants.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.116.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=4zCLL3ve6Z14PQ3LocAoJnyiTflxDn90gE8yUR-T9-w,304
|
|
3
|
+
trilogy/constants.py,sha256=_Tm7YGaAZuxH77X5ve0TajU0dQD7RcGV6ECrTHRL3qQ,2678
|
|
4
4
|
trilogy/engine.py,sha256=v4TpNktM4zZ9OX7jZH2nde4dpX5uAH2U23ELfULTCSg,2280
|
|
5
|
-
trilogy/executor.py,sha256=
|
|
5
|
+
trilogy/executor.py,sha256=uKlCnPp4FHkgsa_dDcQJ4y-ObtvKat2KFx05c-z1mZo,17885
|
|
6
6
|
trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
|
|
7
7
|
trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
trilogy/render.py,sha256=qQWwduymauOlB517UtM-VGbVe8Cswa4UJub5aGbSO6c,1512
|
|
@@ -23,45 +23,45 @@ trilogy/ai/providers/utils.py,sha256=yttP6y2E_XzdytBCwhaKekfXfxM6gE6MRce4AtyLL60
|
|
|
23
23
|
trilogy/authoring/__init__.py,sha256=TABMOETSMERrWuyDLR0nK4ISlqR0yaqeXrmuOdrSvAY,3060
|
|
24
24
|
trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
|
|
26
|
-
trilogy/core/enums.py,sha256=
|
|
26
|
+
trilogy/core/enums.py,sha256=SkuyRrqqS59LO08CWwbFaZ5kYw_w0WWWcigd9MSXdaA,9082
|
|
27
27
|
trilogy/core/env_processor.py,sha256=H-rr2ALj31l5oh3FqeI47Qju6OOfiXBacXNJGNZ92zQ,4521
|
|
28
28
|
trilogy/core/environment_helpers.py,sha256=TRlqVctqIRBxzfjRBmpQsAVoiCcsEKBhG1B6PUE0l1M,12743
|
|
29
29
|
trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
|
|
30
30
|
trilogy/core/exceptions.py,sha256=axkVXYJYQXCCwMHwlyDA232g4tCOwdCZUt7eHeUMDMg,2829
|
|
31
|
-
trilogy/core/functions.py,sha256=
|
|
31
|
+
trilogy/core/functions.py,sha256=QidWH7dDjvLjXA0ujCkemUzYjkYmtMD2T78hnvk86K8,34876
|
|
32
32
|
trilogy/core/graph_models.py,sha256=4EWFTHGfYd72zvS2HYoV6hm7nMC_VEd7vWr6txY-ig0,3400
|
|
33
33
|
trilogy/core/internal.py,sha256=r9QagDB2GvpqlyD_I7VrsfbVfIk5mnok2znEbv72Aa4,2681
|
|
34
|
-
trilogy/core/optimization.py,sha256=
|
|
34
|
+
trilogy/core/optimization.py,sha256=eKieOaWXUtoNTVQbThGA5tqrI06ZR6SUFOqGe4Jw0k4,9262
|
|
35
35
|
trilogy/core/query_processor.py,sha256=rMrtLSQxVm7yeyh0nWjDNI9nnu4Xi0NgHvBJ14gvu4I,20384
|
|
36
36
|
trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
|
|
37
37
|
trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
trilogy/core/models/author.py,sha256=
|
|
39
|
-
trilogy/core/models/build.py,sha256=
|
|
38
|
+
trilogy/core/models/author.py,sha256=YnBwxanbn9uBAExz5POM0_RFw9GIHmjfLZRVzA10Gws,85862
|
|
39
|
+
trilogy/core/models/build.py,sha256=zVHD8jo8V8E0aGWtobzXG7hY3VjLChiW4s1QBx9sD5k,72938
|
|
40
40
|
trilogy/core/models/build_environment.py,sha256=mpx7MKGc60fnZLVdeLi2YSREy7eQbQYycCrP4zF-rHU,5258
|
|
41
41
|
trilogy/core/models/core.py,sha256=iT9WdZoiXeglmUHWn6bZyXCTBpkApTGPKtNm_Mhbu_g,12987
|
|
42
42
|
trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
|
|
43
|
-
trilogy/core/models/environment.py,sha256
|
|
43
|
+
trilogy/core/models/environment.py,sha256=-r6_imqkJ10iPTbWGrdfeF2Th480UKLsG8ihACyqO8w,28823
|
|
44
44
|
trilogy/core/models/execute.py,sha256=3fgEdho2e7S0outq91cCzb9jFwz6L1hTbsTrJwGvIFs,42311
|
|
45
45
|
trilogy/core/optimizations/__init__.py,sha256=yspWc25M5SgAuvXYoSt5J8atyPbDlOfsKjIo5yGD9s4,368
|
|
46
46
|
trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
|
|
47
|
-
trilogy/core/optimizations/hide_unused_concept.py,sha256=
|
|
47
|
+
trilogy/core/optimizations/hide_unused_concept.py,sha256=THp6byyh64dArSz6EuY7unik5WKgQQfWxNGToEqjqE0,1875
|
|
48
48
|
trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
|
|
49
49
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=5ubatgq1IwWQ4L2FDt4--y168YLuGP-vwqH0m8IeTIw,9786
|
|
50
50
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
51
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=MBQeJGBDW2w3xxwano43-5MVBqfbIU9M3K7RFbnSKNA,23367
|
|
52
52
|
trilogy/core/processing/discovery_node_factory.py,sha256=llnLxZo1NqBRIuuPz0GUohym6LZFhVkPT3xSiORi3k4,15446
|
|
53
53
|
trilogy/core/processing/discovery_utility.py,sha256=mO0npZMRlQSzxt3l4m8garKBAOrXFkzt3eiiUyUSoIU,13528
|
|
54
54
|
trilogy/core/processing/discovery_validation.py,sha256=eZ4HfHMpqZLI8MGG2jez8arS8THs6ceuVrQFIY6gXrU,5364
|
|
55
55
|
trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
|
|
56
56
|
trilogy/core/processing/utility.py,sha256=ESs6pKqVP2c9eMdfB2JNjw7D7YnoezVwbLFx1D6OUYA,26088
|
|
57
57
|
trilogy/core/processing/node_generators/__init__.py,sha256=iVJ-crowPxYeut-hFjyEjfibKIDq7PfB4LEuDAUCjGY,943
|
|
58
|
-
trilogy/core/processing/node_generators/basic_node.py,sha256=
|
|
59
|
-
trilogy/core/processing/node_generators/common.py,sha256=
|
|
58
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=IeR00MV6Ay1TDuebcbwNTp1WC9kT23_JIWYpeUqtn_M,5992
|
|
59
|
+
trilogy/core/processing/node_generators/common.py,sha256=oGUW4_RhSpfGu9PNUj0JsHB8iMeTz76FQHMcfVRHP30,9516
|
|
60
60
|
trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
|
|
61
61
|
trilogy/core/processing/node_generators/filter_node.py,sha256=EiP_tafx-X0gM-BIVRCy2rnp1_apt2cbhVfv8cg9dVg,11259
|
|
62
62
|
trilogy/core/processing/node_generators/group_node.py,sha256=sIm1QYrF4EY6sk56A48B6MieCZqvaJLSQebih_aiKnQ,8567
|
|
63
63
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
|
|
64
|
-
trilogy/core/processing/node_generators/multiselect_node.py,sha256=
|
|
64
|
+
trilogy/core/processing/node_generators/multiselect_node.py,sha256=qrgx8ofExbigipFBXKufu279vKWBLSBP91TyJW6g1qE,7135
|
|
65
65
|
trilogy/core/processing/node_generators/node_merge_node.py,sha256=hNcZxnDLTZyYJWfojg769zH9HB9PfZfESmpN1lcHWXg,23172
|
|
66
66
|
trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
|
|
67
67
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=MuVNIexXhqGONho_mewqMOwaYXNUnjjvyPvk_RDGNYE,5943
|
|
@@ -84,7 +84,7 @@ trilogy/core/processing/nodes/union_node.py,sha256=hLAXXVWqEgMWi7dlgSHfCF59fon64
|
|
|
84
84
|
trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
|
|
85
85
|
trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
|
|
86
86
|
trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
87
|
-
trilogy/core/statements/author.py,sha256=
|
|
87
|
+
trilogy/core/statements/author.py,sha256=uEYJ9Q39TdGP0cxQEv3Ue4pGp_dmZ-NiT6k2-rfZERQ,16525
|
|
88
88
|
trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
89
|
trilogy/core/statements/common.py,sha256=VnVLULQg1TJLNUFzJaROT1tsf2ewk3IpuhvZaP36R6A,535
|
|
90
90
|
trilogy/core/statements/execute.py,sha256=kiwJcVeMa4wZR-xLfM2oYOJ9DeyJkP8An38WFyJxktM,2413
|
|
@@ -95,7 +95,7 @@ trilogy/core/validation/datasource.py,sha256=nJeEFyb6iMBwlEVdYVy1vLzAbdRZwOsUjGx
|
|
|
95
95
|
trilogy/core/validation/environment.py,sha256=ymvhQyt7jLK641JAAIQkqjQaAmr9C5022ILzYvDgPP0,2835
|
|
96
96
|
trilogy/core/validation/fix.py,sha256=Z818UFNLxndMTLiyhB3doLxIfnOZ-16QGvVFWuD7UsA,3750
|
|
97
97
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
|
-
trilogy/dialect/base.py,sha256=
|
|
98
|
+
trilogy/dialect/base.py,sha256=i_rdrO3TmKYZK4K8k6ggpvRmzaCl1oR65n89_yHNEPo,52445
|
|
99
99
|
trilogy/dialect/bigquery.py,sha256=XS3hpybeowgfrOrkycAigAF3NX2YUzTzfgE6f__2fT4,4316
|
|
100
100
|
trilogy/dialect/common.py,sha256=I5Ku_TR5MwJTB3ZhcQenrtvXhH2RvTQ8wQe9w5lfkfA,5708
|
|
101
101
|
trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
|
|
@@ -113,13 +113,13 @@ trilogy/hooks/graph_hook.py,sha256=5BfR7Dt0bgEsCLgwjowgCsVkboGYfVJGOz8g9mqpnos,4
|
|
|
113
113
|
trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
|
|
114
114
|
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
115
115
|
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
116
|
-
trilogy/parsing/common.py,sha256=
|
|
116
|
+
trilogy/parsing/common.py,sha256=GijDRpysULL6vQWpFcjgxVASuTWXUVUi5fILHvjzkbg,35534
|
|
117
117
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
118
118
|
trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
|
|
119
119
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
120
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
120
|
+
trilogy/parsing/parse_engine.py,sha256=iHBRUatv4hXSdDgAjllgTe1OtD8WE7V0Gu-ZTT2YugY,86301
|
|
121
121
|
trilogy/parsing/render.py,sha256=k7MNp8EBTqVBSVqFlgTHSwIhfSKLyJfSeb2fSbt9dVA,24307
|
|
122
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
122
|
+
trilogy/parsing/trilogy.lark,sha256=6nQ4fLpiL0pNMAlmmIEwN8Am4DlIIYpBVajp4jHveA8,17185
|
|
123
123
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
124
124
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
125
125
|
trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -132,8 +132,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
|
|
|
132
132
|
trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
|
|
133
133
|
trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
|
|
134
134
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
135
|
-
pytrilogy-0.0.3.
|
|
136
|
-
pytrilogy-0.0.3.
|
|
137
|
-
pytrilogy-0.0.3.
|
|
138
|
-
pytrilogy-0.0.3.
|
|
139
|
-
pytrilogy-0.0.3.
|
|
135
|
+
pytrilogy-0.0.3.116.dist-info/METADATA,sha256=N3aeyGXHRF0etmB8YiAXUHOfidTmQgqDcxUm8enTumw,12911
|
|
136
|
+
pytrilogy-0.0.3.116.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
137
|
+
pytrilogy-0.0.3.116.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
138
|
+
pytrilogy-0.0.3.116.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
139
|
+
pytrilogy-0.0.3.116.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/constants.py
CHANGED
trilogy/core/enums.py
CHANGED
trilogy/core/functions.py
CHANGED
|
@@ -928,6 +928,14 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
928
928
|
output_type=DataType.TIMESTAMP,
|
|
929
929
|
arg_count=1,
|
|
930
930
|
),
|
|
931
|
+
FunctionType.HASH: FunctionConfig(
|
|
932
|
+
valid_inputs={
|
|
933
|
+
DataType.STRING,
|
|
934
|
+
},
|
|
935
|
+
output_purpose=Purpose.PROPERTY,
|
|
936
|
+
output_type=DataType.STRING,
|
|
937
|
+
arg_count=2,
|
|
938
|
+
),
|
|
931
939
|
}
|
|
932
940
|
|
|
933
941
|
EXCLUDED_FUNCTIONS = {
|
trilogy/core/models/author.py
CHANGED
|
@@ -865,6 +865,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
865
865
|
AggregateWrapper,
|
|
866
866
|
RowsetItem,
|
|
867
867
|
MultiSelectLineage,
|
|
868
|
+
Comparison,
|
|
868
869
|
]
|
|
869
870
|
] = None
|
|
870
871
|
namespace: str = Field(default=DEFAULT_NAMESPACE, validate_default=True)
|
|
@@ -1079,6 +1080,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1079
1080
|
| AggregateWrapper
|
|
1080
1081
|
| RowsetItem
|
|
1081
1082
|
| MultiSelectLineage
|
|
1083
|
+
| Comparison
|
|
1082
1084
|
| None,
|
|
1083
1085
|
Grain,
|
|
1084
1086
|
set[str] | None,
|
|
@@ -1178,6 +1180,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1178
1180
|
AggregateWrapper,
|
|
1179
1181
|
RowsetItem,
|
|
1180
1182
|
MultiSelectLineage,
|
|
1183
|
+
Comparison,
|
|
1181
1184
|
],
|
|
1182
1185
|
output: List[ConceptRef],
|
|
1183
1186
|
):
|
|
@@ -1204,6 +1207,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1204
1207
|
def calculate_derivation(self, lineage, purpose: Purpose) -> Derivation:
|
|
1205
1208
|
from trilogy.core.models.build import (
|
|
1206
1209
|
BuildAggregateWrapper,
|
|
1210
|
+
BuildComparison,
|
|
1207
1211
|
BuildFilterItem,
|
|
1208
1212
|
BuildFunction,
|
|
1209
1213
|
BuildMultiSelectLineage,
|
|
@@ -1221,6 +1225,8 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1221
1225
|
# return Derivation.PARENTHETICAL
|
|
1222
1226
|
elif lineage and isinstance(lineage, (BuildRowsetItem, RowsetItem)):
|
|
1223
1227
|
return Derivation.ROWSET
|
|
1228
|
+
elif lineage and isinstance(lineage, BuildComparison):
|
|
1229
|
+
return Derivation.BASIC
|
|
1224
1230
|
elif lineage and isinstance(
|
|
1225
1231
|
lineage, (BuildMultiSelectLineage, MultiSelectLineage)
|
|
1226
1232
|
):
|
|
@@ -2133,6 +2139,89 @@ class AlignClause(Namespaced, BaseModel):
|
|
|
2133
2139
|
)
|
|
2134
2140
|
|
|
2135
2141
|
|
|
2142
|
+
class DeriveItem(Namespaced, DataTyped, ConceptArgs, Mergeable, BaseModel):
|
|
2143
|
+
expr: Expr
|
|
2144
|
+
name: str
|
|
2145
|
+
namespace: str
|
|
2146
|
+
|
|
2147
|
+
@property
|
|
2148
|
+
def derived_concept(self) -> str:
|
|
2149
|
+
return f"{self.namespace}.{self.name}"
|
|
2150
|
+
# return ConceptRef(
|
|
2151
|
+
# address=f"{self.namespace}.{self.name}",
|
|
2152
|
+
# datatype=arg_to_datatype(self.expr),
|
|
2153
|
+
# )
|
|
2154
|
+
|
|
2155
|
+
def with_namespace(self, namespace):
|
|
2156
|
+
return DeriveItem.model_construct(
|
|
2157
|
+
expr=(self.expr.with_namespace(namespace) if self.expr else None),
|
|
2158
|
+
name=self.name,
|
|
2159
|
+
namespace=namespace,
|
|
2160
|
+
)
|
|
2161
|
+
|
|
2162
|
+
def with_merge(
|
|
2163
|
+
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
2164
|
+
) -> "DeriveItem":
|
|
2165
|
+
return DeriveItem.model_construct(
|
|
2166
|
+
expr=(
|
|
2167
|
+
self.expr.with_merge(source, target, modifiers)
|
|
2168
|
+
if isinstance(self.expr, Mergeable)
|
|
2169
|
+
else self.expr
|
|
2170
|
+
),
|
|
2171
|
+
name=self.name,
|
|
2172
|
+
namespace=self.namespace,
|
|
2173
|
+
)
|
|
2174
|
+
|
|
2175
|
+
def with_reference_replacement(self, source, target):
|
|
2176
|
+
return DeriveItem.model_construct(
|
|
2177
|
+
expr=(
|
|
2178
|
+
self.expr.with_reference_replacement(source, target)
|
|
2179
|
+
if isinstance(self.expr, Mergeable)
|
|
2180
|
+
else self.expr
|
|
2181
|
+
),
|
|
2182
|
+
name=self.name,
|
|
2183
|
+
namespace=self.namespace,
|
|
2184
|
+
)
|
|
2185
|
+
|
|
2186
|
+
|
|
2187
|
+
class DeriveClause(Mergeable, Namespaced, BaseModel):
|
|
2188
|
+
items: List[DeriveItem]
|
|
2189
|
+
|
|
2190
|
+
def with_namespace(self, namespace: str) -> "DeriveClause":
|
|
2191
|
+
return DeriveClause.model_construct(
|
|
2192
|
+
items=[
|
|
2193
|
+
x.with_namespace(namespace) if isinstance(x, Namespaced) else x
|
|
2194
|
+
for x in self.items
|
|
2195
|
+
]
|
|
2196
|
+
)
|
|
2197
|
+
|
|
2198
|
+
def with_merge(
|
|
2199
|
+
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
2200
|
+
) -> "DeriveClause":
|
|
2201
|
+
return DeriveClause.model_construct(
|
|
2202
|
+
items=[
|
|
2203
|
+
(
|
|
2204
|
+
x.with_merge(source, target, modifiers)
|
|
2205
|
+
if isinstance(x, Mergeable)
|
|
2206
|
+
else x
|
|
2207
|
+
)
|
|
2208
|
+
for x in self.items
|
|
2209
|
+
]
|
|
2210
|
+
)
|
|
2211
|
+
|
|
2212
|
+
def with_reference_replacement(self, source, target):
|
|
2213
|
+
return DeriveClause.model_construct(
|
|
2214
|
+
items=[
|
|
2215
|
+
(
|
|
2216
|
+
x.with_reference_replacement(source, target)
|
|
2217
|
+
if isinstance(x, Mergeable)
|
|
2218
|
+
else x
|
|
2219
|
+
)
|
|
2220
|
+
for x in self.items
|
|
2221
|
+
]
|
|
2222
|
+
)
|
|
2223
|
+
|
|
2224
|
+
|
|
2136
2225
|
class SelectLineage(Mergeable, Namespaced, BaseModel):
|
|
2137
2226
|
selection: List[ConceptRef]
|
|
2138
2227
|
hidden_components: set[str]
|
|
@@ -2177,15 +2266,40 @@ class SelectLineage(Mergeable, Namespaced, BaseModel):
|
|
|
2177
2266
|
),
|
|
2178
2267
|
)
|
|
2179
2268
|
|
|
2269
|
+
def with_namespace(self, namespace):
|
|
2270
|
+
return SelectLineage.model_construct(
|
|
2271
|
+
selection=[x.with_namespace(namespace) for x in self.selection],
|
|
2272
|
+
hidden_components=self.hidden_components,
|
|
2273
|
+
local_concepts={
|
|
2274
|
+
x: y.with_namespace(namespace) for x, y in self.local_concepts.items()
|
|
2275
|
+
},
|
|
2276
|
+
order_by=self.order_by.with_namespace(namespace) if self.order_by else None,
|
|
2277
|
+
limit=self.limit,
|
|
2278
|
+
meta=self.meta,
|
|
2279
|
+
grain=self.grain.with_namespace(namespace),
|
|
2280
|
+
where_clause=(
|
|
2281
|
+
self.where_clause.with_namespace(namespace)
|
|
2282
|
+
if self.where_clause
|
|
2283
|
+
else None
|
|
2284
|
+
),
|
|
2285
|
+
having_clause=(
|
|
2286
|
+
self.having_clause.with_namespace(namespace)
|
|
2287
|
+
if self.having_clause
|
|
2288
|
+
else None
|
|
2289
|
+
),
|
|
2290
|
+
)
|
|
2291
|
+
|
|
2180
2292
|
|
|
2181
2293
|
class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
2182
2294
|
selects: List[SelectLineage]
|
|
2183
2295
|
align: AlignClause
|
|
2296
|
+
|
|
2184
2297
|
namespace: str
|
|
2185
2298
|
order_by: Optional[OrderBy] = None
|
|
2186
2299
|
limit: Optional[int] = None
|
|
2187
2300
|
where_clause: Union["WhereClause", None] = Field(default=None)
|
|
2188
2301
|
having_clause: Union["HavingClause", None] = Field(default=None)
|
|
2302
|
+
derive: DeriveClause | None = None
|
|
2189
2303
|
hidden_components: set[str]
|
|
2190
2304
|
|
|
2191
2305
|
@property
|
|
@@ -2211,6 +2325,11 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
2211
2325
|
new = MultiSelectLineage.model_construct(
|
|
2212
2326
|
selects=[s.with_merge(source, target, modifiers) for s in self.selects],
|
|
2213
2327
|
align=self.align,
|
|
2328
|
+
derive=(
|
|
2329
|
+
self.derive.with_merge(source, target, modifiers)
|
|
2330
|
+
if self.derive
|
|
2331
|
+
else None
|
|
2332
|
+
),
|
|
2214
2333
|
namespace=self.namespace,
|
|
2215
2334
|
hidden_components=self.hidden_components,
|
|
2216
2335
|
order_by=(
|
|
@@ -2236,6 +2355,7 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
2236
2355
|
return MultiSelectLineage.model_construct(
|
|
2237
2356
|
selects=[c.with_namespace(namespace) for c in self.selects],
|
|
2238
2357
|
align=self.align.with_namespace(namespace),
|
|
2358
|
+
derive=self.derive.with_namespace(namespace) if self.derive else None,
|
|
2239
2359
|
namespace=namespace,
|
|
2240
2360
|
hidden_components=self.hidden_components,
|
|
2241
2361
|
order_by=self.order_by.with_namespace(namespace) if self.order_by else None,
|
|
@@ -2257,6 +2377,9 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
2257
2377
|
output = set()
|
|
2258
2378
|
for item in self.align.items:
|
|
2259
2379
|
output.add(item.aligned_concept)
|
|
2380
|
+
if self.derive:
|
|
2381
|
+
for ditem in self.derive.items:
|
|
2382
|
+
output.add(ditem.derived_concept)
|
|
2260
2383
|
return output
|
|
2261
2384
|
|
|
2262
2385
|
@property
|
trilogy/core/models/build.py
CHANGED
|
@@ -48,6 +48,8 @@ from trilogy.core.models.author import (
|
|
|
48
48
|
Concept,
|
|
49
49
|
ConceptRef,
|
|
50
50
|
Conditional,
|
|
51
|
+
DeriveClause,
|
|
52
|
+
DeriveItem,
|
|
51
53
|
FilterItem,
|
|
52
54
|
FuncArgs,
|
|
53
55
|
Function,
|
|
@@ -247,7 +249,7 @@ class BuildParamaterizedConceptReference(DataTyped):
|
|
|
247
249
|
concept: BuildConcept
|
|
248
250
|
|
|
249
251
|
def __str__(self):
|
|
250
|
-
return f":{self.concept.address}"
|
|
252
|
+
return f":{self.concept.address.replace('.', '_')}"
|
|
251
253
|
|
|
252
254
|
@property
|
|
253
255
|
def safe_address(self) -> str:
|
|
@@ -1268,6 +1270,22 @@ class BuildAlignClause:
|
|
|
1268
1270
|
items: List[BuildAlignItem]
|
|
1269
1271
|
|
|
1270
1272
|
|
|
1273
|
+
@dataclass
|
|
1274
|
+
class BuildDeriveClause:
|
|
1275
|
+
items: List[BuildDeriveItem]
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
@dataclass
|
|
1279
|
+
class BuildDeriveItem:
|
|
1280
|
+
expr: BuildExpr
|
|
1281
|
+
name: str
|
|
1282
|
+
namespace: str = field(default=DEFAULT_NAMESPACE)
|
|
1283
|
+
|
|
1284
|
+
@property
|
|
1285
|
+
def address(self) -> str:
|
|
1286
|
+
return f"{self.namespace}.{self.name}"
|
|
1287
|
+
|
|
1288
|
+
|
|
1271
1289
|
@dataclass
|
|
1272
1290
|
class BuildSelectLineage:
|
|
1273
1291
|
selection: List[BuildConcept]
|
|
@@ -1299,12 +1317,16 @@ class BuildMultiSelectLineage(BuildConceptArgs):
|
|
|
1299
1317
|
limit: Optional[int] = None
|
|
1300
1318
|
where_clause: Union["BuildWhereClause", None] = field(default=None)
|
|
1301
1319
|
having_clause: Union["BuildHavingClause", None] = field(default=None)
|
|
1320
|
+
derive: BuildDeriveClause | None = None
|
|
1302
1321
|
|
|
1303
1322
|
@property
|
|
1304
1323
|
def derived_concepts(self) -> set[str]:
|
|
1305
1324
|
output = set()
|
|
1306
1325
|
for item in self.align.items:
|
|
1307
1326
|
output.add(item.aligned_concept)
|
|
1327
|
+
if self.derive:
|
|
1328
|
+
for ditem in self.derive.items:
|
|
1329
|
+
output.add(ditem.address)
|
|
1308
1330
|
return output
|
|
1309
1331
|
|
|
1310
1332
|
@property
|
|
@@ -1312,10 +1334,12 @@ class BuildMultiSelectLineage(BuildConceptArgs):
|
|
|
1312
1334
|
return self.build_output_components
|
|
1313
1335
|
|
|
1314
1336
|
@property
|
|
1315
|
-
def
|
|
1316
|
-
output = set()
|
|
1317
|
-
|
|
1318
|
-
output
|
|
1337
|
+
def calculated_derivations(self) -> set[str]:
|
|
1338
|
+
output: set[str] = set()
|
|
1339
|
+
if not self.derive:
|
|
1340
|
+
return output
|
|
1341
|
+
for item in self.derive.items:
|
|
1342
|
+
output.add(item.address)
|
|
1319
1343
|
return output
|
|
1320
1344
|
|
|
1321
1345
|
@property
|
|
@@ -1335,6 +1359,7 @@ class BuildMultiSelectLineage(BuildConceptArgs):
|
|
|
1335
1359
|
for c in x.concepts:
|
|
1336
1360
|
if c.address in cte.output_lcl:
|
|
1337
1361
|
return c
|
|
1362
|
+
|
|
1338
1363
|
raise SyntaxError(
|
|
1339
1364
|
f"Could not find upstream map for multiselect {str(concept)} on cte ({cte})"
|
|
1340
1365
|
)
|
|
@@ -1961,6 +1986,28 @@ class Factory:
|
|
|
1961
1986
|
def _build_align_clause(self, base: AlignClause) -> BuildAlignClause:
|
|
1962
1987
|
return BuildAlignClause(items=[self._build_align_item(x) for x in base.items])
|
|
1963
1988
|
|
|
1989
|
+
@build.register
|
|
1990
|
+
def _(self, base: DeriveItem) -> BuildDeriveItem:
|
|
1991
|
+
return self._build_derive_item(base)
|
|
1992
|
+
|
|
1993
|
+
def _build_derive_item(self, base: DeriveItem) -> BuildDeriveItem:
|
|
1994
|
+
expr: Concept | FuncArgs = base.expr
|
|
1995
|
+
validation = requires_concept_nesting(expr)
|
|
1996
|
+
if validation:
|
|
1997
|
+
expr, _ = self.instantiate_concept(validation)
|
|
1998
|
+
return BuildDeriveItem(
|
|
1999
|
+
expr=self.build(expr),
|
|
2000
|
+
name=base.name,
|
|
2001
|
+
namespace=base.namespace,
|
|
2002
|
+
)
|
|
2003
|
+
|
|
2004
|
+
@build.register
|
|
2005
|
+
def _(self, base: DeriveClause) -> BuildDeriveClause:
|
|
2006
|
+
return self._build_derive_clause(base)
|
|
2007
|
+
|
|
2008
|
+
def _build_derive_clause(self, base: DeriveClause) -> BuildDeriveClause:
|
|
2009
|
+
return BuildDeriveClause(items=[self.build(x) for x in base.items])
|
|
2010
|
+
|
|
1964
2011
|
@build.register
|
|
1965
2012
|
def _(self, base: RowsetItem) -> BuildRowsetItem:
|
|
1966
2013
|
return self._build_rowset_item(base)
|
|
@@ -2162,6 +2209,7 @@ class Factory:
|
|
|
2162
2209
|
selects=base.selects,
|
|
2163
2210
|
grain=final_grain,
|
|
2164
2211
|
align=factory.build(base.align),
|
|
2212
|
+
derive=factory.build(base.derive) if base.derive else None,
|
|
2165
2213
|
# self.align.with_select_context(
|
|
2166
2214
|
# local_build_cache, self.grain, environment
|
|
2167
2215
|
# ),
|
|
@@ -413,7 +413,8 @@ class Environment(BaseModel):
|
|
|
413
413
|
self.imports[alias].append(imp_stm)
|
|
414
414
|
# we can't exit early
|
|
415
415
|
# as there may be new concepts
|
|
416
|
-
|
|
416
|
+
iteration: list[tuple[str, Concept]] = list(source.concepts.items())
|
|
417
|
+
for k, concept in iteration:
|
|
417
418
|
# skip internal namespace
|
|
418
419
|
if INTERNAL_NAMESPACE in concept.address:
|
|
419
420
|
continue
|
trilogy/core/optimization.py
CHANGED
|
@@ -228,7 +228,8 @@ def optimize_ctes(
|
|
|
228
228
|
REGISTERED_RULES.append(PredicatePushdown())
|
|
229
229
|
if CONFIG.optimizations.predicate_pushdown:
|
|
230
230
|
REGISTERED_RULES.append(PredicatePushdownRemove())
|
|
231
|
-
|
|
231
|
+
if CONFIG.optimizations.hide_unused_concepts:
|
|
232
|
+
REGISTERED_RULES.append(HideUnusedConcepts())
|
|
232
233
|
for rule in REGISTERED_RULES:
|
|
233
234
|
loops = 0
|
|
234
235
|
complete = False
|
|
@@ -242,7 +243,7 @@ def optimize_ctes(
|
|
|
242
243
|
actions_taken = actions_taken or opt
|
|
243
244
|
complete = not actions_taken
|
|
244
245
|
loops += 1
|
|
245
|
-
|
|
246
|
+
input = reorder_ctes(filter_irrelevant_ctes(input, root_cte))
|
|
246
247
|
logger.info(
|
|
247
248
|
f"[Optimization] Finished checking for {type(rule).__name__} after {loops} loop(s)"
|
|
248
249
|
)
|
|
@@ -39,11 +39,7 @@ class HideUnusedConcepts(OptimizationRule):
|
|
|
39
39
|
self.log(
|
|
40
40
|
f"Hiding unused concepts {[x.address for x in add_to_hidden]} from {cte.name} (used: {used}, all: {[x.address for x in cte.output_columns]})"
|
|
41
41
|
)
|
|
42
|
-
candidates = [
|
|
43
|
-
x.address
|
|
44
|
-
for x in cte.output_columns
|
|
45
|
-
if x.address not in used and x.address not in cte.hidden_concepts
|
|
46
|
-
]
|
|
42
|
+
candidates = [x.address for x in cte.output_columns if x.address not in used]
|
|
47
43
|
if len(candidates) == len(set([x.address for x in cte.output_columns])):
|
|
48
44
|
# pop one out
|
|
49
45
|
candidates.pop()
|
|
@@ -7,7 +7,7 @@ from trilogy.core.models.build_environment import BuildEnvironment
|
|
|
7
7
|
from trilogy.core.processing.node_generators.common import (
|
|
8
8
|
resolve_function_parent_concepts,
|
|
9
9
|
)
|
|
10
|
-
from trilogy.core.processing.nodes import History, StrategyNode
|
|
10
|
+
from trilogy.core.processing.nodes import ConstantNode, History, StrategyNode
|
|
11
11
|
from trilogy.utility import unique
|
|
12
12
|
|
|
13
13
|
LOGGER_PREFIX = "[GEN_BASIC_NODE]"
|
|
@@ -51,11 +51,14 @@ def gen_basic_node(
|
|
|
51
51
|
)
|
|
52
52
|
synonyms: list[BuildConcept] = []
|
|
53
53
|
ignored_optional: set[str] = set()
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
# when we are getting an attribute, if there is anything else
|
|
56
56
|
# that is an attribute of the same struct in local optional
|
|
57
57
|
# select that value for discovery as well
|
|
58
|
-
if
|
|
58
|
+
if (
|
|
59
|
+
isinstance(concept.lineage, BuildFunction)
|
|
60
|
+
and concept.lineage.operator == FunctionType.ATTR_ACCESS
|
|
61
|
+
):
|
|
59
62
|
logger.info(
|
|
60
63
|
f"{depth_prefix}{LOGGER_PREFIX} checking for synonyms for attribute access"
|
|
61
64
|
)
|
|
@@ -106,20 +109,28 @@ def gen_basic_node(
|
|
|
106
109
|
logger.info(
|
|
107
110
|
f"{depth_prefix}{LOGGER_PREFIX} Fetching parents {[x.address for x in all_parents]}"
|
|
108
111
|
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
if all_parents:
|
|
113
|
+
parent_node: StrategyNode | None = source_concepts(
|
|
114
|
+
mandatory_list=all_parents,
|
|
115
|
+
environment=environment,
|
|
116
|
+
g=g,
|
|
117
|
+
depth=depth + 1,
|
|
118
|
+
history=history,
|
|
119
|
+
conditions=conditions,
|
|
120
|
+
)
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
if not parent_node:
|
|
123
|
+
logger.info(
|
|
124
|
+
f"{depth_prefix}{LOGGER_PREFIX} No basic node could be generated for {concept}"
|
|
125
|
+
)
|
|
126
|
+
return None
|
|
127
|
+
else:
|
|
128
|
+
return ConstantNode(
|
|
129
|
+
input_concepts=[],
|
|
130
|
+
output_concepts=[concept],
|
|
131
|
+
environment=environment,
|
|
132
|
+
depth=depth,
|
|
121
133
|
)
|
|
122
|
-
return None
|
|
123
134
|
if parent_node.source_type != SourceType.CONSTANT:
|
|
124
135
|
parent_node.source_type = SourceType.BASIC
|
|
125
136
|
parent_node.add_output_concept(concept)
|
|
@@ -4,6 +4,7 @@ from typing import Callable, List, Tuple
|
|
|
4
4
|
from trilogy.core.enums import Derivation, Purpose
|
|
5
5
|
from trilogy.core.models.build import (
|
|
6
6
|
BuildAggregateWrapper,
|
|
7
|
+
BuildComparison,
|
|
7
8
|
BuildConcept,
|
|
8
9
|
BuildFilterItem,
|
|
9
10
|
BuildFunction,
|
|
@@ -26,7 +27,9 @@ FUNCTION_TYPES = (BuildFunction,)
|
|
|
26
27
|
def resolve_function_parent_concepts(
|
|
27
28
|
concept: BuildConcept, environment: BuildEnvironment
|
|
28
29
|
) -> List[BuildConcept]:
|
|
29
|
-
if not isinstance(
|
|
30
|
+
if not isinstance(
|
|
31
|
+
concept.lineage, (*FUNCTION_TYPES, *AGGREGATE_TYPES, BuildComparison)
|
|
32
|
+
):
|
|
30
33
|
raise ValueError(
|
|
31
34
|
f"Concept {concept} lineage is not function or aggregate, is {type(concept.lineage)}"
|
|
32
35
|
)
|
|
@@ -157,19 +157,19 @@ def gen_multiselect_node(
|
|
|
157
157
|
possible_joins = concept_to_relevant_joins(additional_relevant)
|
|
158
158
|
if not local_optional:
|
|
159
159
|
logger.info(
|
|
160
|
-
f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for
|
|
160
|
+
f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for multiselect node; exiting early"
|
|
161
161
|
)
|
|
162
162
|
return node
|
|
163
163
|
if not possible_joins:
|
|
164
164
|
logger.info(
|
|
165
|
-
f"{padding(depth)}{LOGGER_PREFIX} no possible joins for
|
|
165
|
+
f"{padding(depth)}{LOGGER_PREFIX} no possible joins for multiselect node; exiting early"
|
|
166
166
|
)
|
|
167
167
|
return node
|
|
168
168
|
if all(
|
|
169
169
|
[x.address in [y.address for y in node.output_concepts] for x in local_optional]
|
|
170
170
|
):
|
|
171
171
|
logger.info(
|
|
172
|
-
f"{padding(depth)}{LOGGER_PREFIX} all enriched concepts returned from base
|
|
172
|
+
f"{padding(depth)}{LOGGER_PREFIX} all enriched concepts returned from base multiselect node; exiting early"
|
|
173
173
|
)
|
|
174
174
|
return node
|
|
175
175
|
logger.info(
|
|
@@ -21,6 +21,7 @@ from trilogy.core.models.author import (
|
|
|
21
21
|
Concept,
|
|
22
22
|
ConceptRef,
|
|
23
23
|
CustomType,
|
|
24
|
+
DeriveClause,
|
|
24
25
|
Expr,
|
|
25
26
|
FilterItem,
|
|
26
27
|
Function,
|
|
@@ -352,11 +353,13 @@ class MultiSelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
352
353
|
local_concepts: Annotated[
|
|
353
354
|
EnvironmentConceptDict, PlainValidator(validate_concepts)
|
|
354
355
|
] = Field(default_factory=EnvironmentConceptDict)
|
|
356
|
+
derive: DeriveClause | None = None
|
|
355
357
|
|
|
356
358
|
def as_lineage(self, environment: Environment):
|
|
357
359
|
return MultiSelectLineage(
|
|
358
360
|
selects=[x.as_lineage(environment) for x in self.selects],
|
|
359
361
|
align=self.align,
|
|
362
|
+
derive=self.derive,
|
|
360
363
|
namespace=self.namespace,
|
|
361
364
|
# derived_concepts = self.derived_concepts,
|
|
362
365
|
limit=self.limit,
|
trilogy/dialect/base.py
CHANGED
|
@@ -176,6 +176,20 @@ def struct_arg(args):
|
|
|
176
176
|
return [f"{x[1]}: {x[0]}" for x in zip(args[::2], args[1::2])]
|
|
177
177
|
|
|
178
178
|
|
|
179
|
+
def hash_from_args(val, hash_type):
|
|
180
|
+
hash_type = hash_type[1:-1]
|
|
181
|
+
if hash_type.lower() == "md5":
|
|
182
|
+
return f"md5({val})"
|
|
183
|
+
elif hash_type.lower() == "sha1":
|
|
184
|
+
return f"sha1({val})"
|
|
185
|
+
elif hash_type.lower() == "sha256":
|
|
186
|
+
return f"sha256({val})"
|
|
187
|
+
elif hash_type.lower() == "sha512":
|
|
188
|
+
return f"sha512({val})"
|
|
189
|
+
else:
|
|
190
|
+
raise ValueError(f"Unsupported hash type: {hash_type}")
|
|
191
|
+
|
|
192
|
+
|
|
179
193
|
FUNCTION_MAP = {
|
|
180
194
|
# generic types
|
|
181
195
|
FunctionType.ALIAS: lambda x: f"{x[0]}",
|
|
@@ -260,6 +274,7 @@ FUNCTION_MAP = {
|
|
|
260
274
|
FunctionType.REGEXP_REPLACE: lambda x: f"REGEXP_REPLACE({x[0]},{x[1]}, {x[2]})",
|
|
261
275
|
FunctionType.TRIM: lambda x: f"TRIM({x[0]})",
|
|
262
276
|
FunctionType.REPLACE: lambda x: f"REPLACE({x[0]},{x[1]},{x[2]})",
|
|
277
|
+
FunctionType.HASH: lambda x: hash_from_args(x[0], x[1]),
|
|
263
278
|
# FunctionType.NOT_LIKE: lambda x: f" CASE WHEN {x[0]} like {x[1]} THEN 0 ELSE 1 END",
|
|
264
279
|
# date types
|
|
265
280
|
FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
|
|
@@ -484,7 +499,20 @@ class BaseDialect:
|
|
|
484
499
|
elif isinstance(c.lineage, BuildRowsetItem):
|
|
485
500
|
rval = f"{self.render_concept_sql(c.lineage.content, cte=cte, alias=False, raise_invalid=raise_invalid)}"
|
|
486
501
|
elif isinstance(c.lineage, BuildMultiSelectLineage):
|
|
487
|
-
|
|
502
|
+
if c.address in c.lineage.calculated_derivations:
|
|
503
|
+
assert c.lineage.derive is not None
|
|
504
|
+
for x in c.lineage.derive.items:
|
|
505
|
+
if x.address == c.address:
|
|
506
|
+
rval = self.render_expr(
|
|
507
|
+
x.expr,
|
|
508
|
+
cte=cte,
|
|
509
|
+
raise_invalid=raise_invalid,
|
|
510
|
+
)
|
|
511
|
+
break
|
|
512
|
+
else:
|
|
513
|
+
rval = f"{self.render_concept_sql(c.lineage.find_source(c, cte), cte=cte, alias=False, raise_invalid=raise_invalid)}"
|
|
514
|
+
elif isinstance(c.lineage, BuildComparison):
|
|
515
|
+
rval = f"{self.render_expr(c.lineage.left, cte=cte, raise_invalid=raise_invalid)} {c.lineage.operator.value} {self.render_expr(c.lineage.right, cte=cte, raise_invalid=raise_invalid)}"
|
|
488
516
|
elif isinstance(c.lineage, AGGREGATE_ITEMS):
|
|
489
517
|
args = [
|
|
490
518
|
self.render_expr(v, cte) # , alias=False)
|
|
@@ -816,7 +844,7 @@ class BaseDialect:
|
|
|
816
844
|
if self.rendering.parameters:
|
|
817
845
|
if e.concept.namespace == DEFAULT_NAMESPACE:
|
|
818
846
|
return f":{e.concept.name}"
|
|
819
|
-
return f":{e.concept.address}"
|
|
847
|
+
return f":{e.concept.address.replace('.', '_')}"
|
|
820
848
|
elif e.concept.lineage:
|
|
821
849
|
return self.render_expr(e.concept.lineage, cte=cte, cte_map=cte_map)
|
|
822
850
|
return f"{self.QUOTE_CHARACTER}{e.concept.address}{self.QUOTE_CHARACTER}"
|
trilogy/executor.py
CHANGED
|
@@ -397,7 +397,7 @@ class Executor(object):
|
|
|
397
397
|
if v.safe_address == param or v.address == param
|
|
398
398
|
]
|
|
399
399
|
if not matched:
|
|
400
|
-
raise SyntaxError(f"No concept found for parameter {param}")
|
|
400
|
+
raise SyntaxError(f"No concept found for parameter {param};")
|
|
401
401
|
|
|
402
402
|
concept: Concept = matched.pop()
|
|
403
403
|
return self._concept_to_value(concept, local_concepts=local_concepts)
|
trilogy/parsing/common.py
CHANGED
|
@@ -3,9 +3,7 @@ from typing import Iterable, List, Sequence, Tuple
|
|
|
3
3
|
|
|
4
4
|
from lark.tree import Meta
|
|
5
5
|
|
|
6
|
-
from trilogy.constants import
|
|
7
|
-
VIRTUAL_CONCEPT_PREFIX,
|
|
8
|
-
)
|
|
6
|
+
from trilogy.constants import DEFAULT_NAMESPACE, VIRTUAL_CONCEPT_PREFIX
|
|
9
7
|
from trilogy.core.constants import ALL_ROWS_CONCEPT
|
|
10
8
|
from trilogy.core.enums import (
|
|
11
9
|
ConceptSource,
|
|
@@ -64,10 +62,12 @@ ARBITRARY_INPUTS = (
|
|
|
64
62
|
| Parenthetical
|
|
65
63
|
| ListWrapper
|
|
66
64
|
| MapWrapper
|
|
65
|
+
| Comparison
|
|
67
66
|
| int
|
|
68
67
|
| float
|
|
69
68
|
| str
|
|
70
69
|
| date
|
|
70
|
+
| bool
|
|
71
71
|
)
|
|
72
72
|
|
|
73
73
|
|
|
@@ -399,6 +399,10 @@ def _get_relevant_parent_concepts(arg) -> tuple[list[ConceptRef], bool]:
|
|
|
399
399
|
return [x.reference for x in arg.by], True
|
|
400
400
|
elif isinstance(arg, FunctionCallWrapper):
|
|
401
401
|
return get_relevant_parent_concepts(arg.content)
|
|
402
|
+
elif isinstance(arg, Comparison):
|
|
403
|
+
left, lflag = get_relevant_parent_concepts(arg.left)
|
|
404
|
+
right, rflag = get_relevant_parent_concepts(arg.right)
|
|
405
|
+
return left + right, lflag or rflag
|
|
402
406
|
return get_concept_arguments(arg), False
|
|
403
407
|
|
|
404
408
|
|
|
@@ -775,6 +779,27 @@ def align_item_to_concept(
|
|
|
775
779
|
return new
|
|
776
780
|
|
|
777
781
|
|
|
782
|
+
def derive_item_to_concept(
|
|
783
|
+
parent: ARBITRARY_INPUTS,
|
|
784
|
+
name: str,
|
|
785
|
+
lineage: MultiSelectLineage,
|
|
786
|
+
namespace: str | None = None,
|
|
787
|
+
) -> Concept:
|
|
788
|
+
datatype = arg_to_datatype(parent)
|
|
789
|
+
grain = Grain()
|
|
790
|
+
new = Concept(
|
|
791
|
+
name=name,
|
|
792
|
+
datatype=datatype,
|
|
793
|
+
purpose=Purpose.PROPERTY,
|
|
794
|
+
lineage=lineage,
|
|
795
|
+
grain=grain,
|
|
796
|
+
namespace=namespace or DEFAULT_NAMESPACE,
|
|
797
|
+
granularity=Granularity.MULTI_ROW,
|
|
798
|
+
derivation=Derivation.MULTISELECT,
|
|
799
|
+
)
|
|
800
|
+
return new
|
|
801
|
+
|
|
802
|
+
|
|
778
803
|
def rowset_concept(
|
|
779
804
|
orig_address: ConceptRef,
|
|
780
805
|
environment: Environment,
|
|
@@ -856,20 +881,7 @@ def rowset_to_concepts(rowset: RowsetDerivationStatement, environment: Environme
|
|
|
856
881
|
|
|
857
882
|
|
|
858
883
|
def generate_concept_name(
|
|
859
|
-
parent:
|
|
860
|
-
AggregateWrapper
|
|
861
|
-
| FunctionCallWrapper
|
|
862
|
-
| WindowItem
|
|
863
|
-
| FilterItem
|
|
864
|
-
| Function
|
|
865
|
-
| ListWrapper
|
|
866
|
-
| MapWrapper
|
|
867
|
-
| Parenthetical
|
|
868
|
-
| int
|
|
869
|
-
| float
|
|
870
|
-
| str
|
|
871
|
-
| date
|
|
872
|
-
),
|
|
884
|
+
parent: ARBITRARY_INPUTS,
|
|
873
885
|
) -> str:
|
|
874
886
|
"""Generate a name for a concept based on its parent type and content."""
|
|
875
887
|
if isinstance(parent, AggregateWrapper):
|
|
@@ -890,6 +902,8 @@ def generate_concept_name(
|
|
|
890
902
|
return f"{VIRTUAL_CONCEPT_PREFIX}_paren_{string_to_hash(str(parent))}"
|
|
891
903
|
elif isinstance(parent, FunctionCallWrapper):
|
|
892
904
|
return f"{VIRTUAL_CONCEPT_PREFIX}_{parent.name}_{string_to_hash(str(parent))}"
|
|
905
|
+
elif isinstance(parent, Comparison):
|
|
906
|
+
return f"{VIRTUAL_CONCEPT_PREFIX}_comp_{string_to_hash(str(parent))}"
|
|
893
907
|
else: # ListWrapper, MapWrapper, or primitive types
|
|
894
908
|
return f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(parent))}"
|
|
895
909
|
|
|
@@ -914,6 +928,82 @@ def parenthetical_to_concept(
|
|
|
914
928
|
)
|
|
915
929
|
|
|
916
930
|
|
|
931
|
+
def comparison_to_concept(
|
|
932
|
+
parent: Comparison,
|
|
933
|
+
name: str,
|
|
934
|
+
namespace: str,
|
|
935
|
+
environment: Environment,
|
|
936
|
+
metadata: Metadata | None = None,
|
|
937
|
+
):
|
|
938
|
+
fmetadata = metadata or Metadata()
|
|
939
|
+
|
|
940
|
+
pkeys: List[Concept] = []
|
|
941
|
+
namespace = namespace or environment.namespace
|
|
942
|
+
is_metric = False
|
|
943
|
+
ref_args, is_metric = get_relevant_parent_concepts(parent)
|
|
944
|
+
concrete_args = [environment.concepts[c.address] for c in ref_args]
|
|
945
|
+
pkeys += [
|
|
946
|
+
x
|
|
947
|
+
for x in concrete_args
|
|
948
|
+
if not x.derivation == Derivation.CONSTANT
|
|
949
|
+
and not (x.derivation == Derivation.AGGREGATE and not x.grain.components)
|
|
950
|
+
]
|
|
951
|
+
grain: Grain | None = Grain()
|
|
952
|
+
for x in pkeys:
|
|
953
|
+
grain += x.grain
|
|
954
|
+
if parent.operator in FunctionClass.ONE_TO_MANY.value:
|
|
955
|
+
# if the function will create more rows, we don't know what grain this is at
|
|
956
|
+
grain = None
|
|
957
|
+
modifiers = get_upstream_modifiers(pkeys, environment)
|
|
958
|
+
key_grain: list[str] = []
|
|
959
|
+
for x in pkeys:
|
|
960
|
+
# metrics will group to keys, so do not do key traversal
|
|
961
|
+
if is_metric:
|
|
962
|
+
key_grain.append(x.address)
|
|
963
|
+
# otherwse, for row ops, assume keys are transitive
|
|
964
|
+
elif x.keys:
|
|
965
|
+
key_grain += [*x.keys]
|
|
966
|
+
else:
|
|
967
|
+
key_grain.append(x.address)
|
|
968
|
+
keys = set(key_grain)
|
|
969
|
+
if is_metric:
|
|
970
|
+
purpose = Purpose.METRIC
|
|
971
|
+
elif not pkeys:
|
|
972
|
+
purpose = Purpose.CONSTANT
|
|
973
|
+
else:
|
|
974
|
+
purpose = Purpose.PROPERTY
|
|
975
|
+
fmetadata = metadata or Metadata()
|
|
976
|
+
|
|
977
|
+
if grain is not None:
|
|
978
|
+
r = Concept(
|
|
979
|
+
name=name,
|
|
980
|
+
datatype=parent.output_datatype,
|
|
981
|
+
purpose=purpose,
|
|
982
|
+
lineage=parent,
|
|
983
|
+
namespace=namespace,
|
|
984
|
+
keys=keys,
|
|
985
|
+
modifiers=modifiers,
|
|
986
|
+
grain=grain,
|
|
987
|
+
metadata=fmetadata,
|
|
988
|
+
derivation=Derivation.BASIC,
|
|
989
|
+
granularity=Granularity.MULTI_ROW,
|
|
990
|
+
)
|
|
991
|
+
return r
|
|
992
|
+
|
|
993
|
+
return Concept(
|
|
994
|
+
name=name,
|
|
995
|
+
datatype=parent.output_datatype,
|
|
996
|
+
purpose=purpose,
|
|
997
|
+
lineage=parent,
|
|
998
|
+
namespace=namespace,
|
|
999
|
+
keys=keys,
|
|
1000
|
+
modifiers=modifiers,
|
|
1001
|
+
metadata=fmetadata,
|
|
1002
|
+
derivation=Derivation.BASIC,
|
|
1003
|
+
granularity=Granularity.MULTI_ROW,
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
|
|
917
1007
|
def arbitrary_to_concept(
|
|
918
1008
|
parent: ARBITRARY_INPUTS,
|
|
919
1009
|
environment: Environment,
|
|
@@ -973,5 +1063,7 @@ def arbitrary_to_concept(
|
|
|
973
1063
|
return constant_to_concept(parent, name, namespace, metadata)
|
|
974
1064
|
elif isinstance(parent, Parenthetical):
|
|
975
1065
|
return parenthetical_to_concept(parent, name, namespace, environment, metadata)
|
|
1066
|
+
elif isinstance(parent, Comparison):
|
|
1067
|
+
return comparison_to_concept(parent, name, namespace, environment, metadata)
|
|
976
1068
|
else:
|
|
977
1069
|
return constant_to_concept(parent, name, namespace, metadata)
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -62,6 +62,8 @@ from trilogy.core.models.author import (
|
|
|
62
62
|
Conditional,
|
|
63
63
|
CustomFunctionFactory,
|
|
64
64
|
CustomType,
|
|
65
|
+
DeriveClause,
|
|
66
|
+
DeriveItem,
|
|
65
67
|
Expr,
|
|
66
68
|
FilterItem,
|
|
67
69
|
Function,
|
|
@@ -69,6 +71,7 @@ from trilogy.core.models.author import (
|
|
|
69
71
|
Grain,
|
|
70
72
|
HavingClause,
|
|
71
73
|
Metadata,
|
|
74
|
+
MultiSelectLineage,
|
|
72
75
|
OrderBy,
|
|
73
76
|
OrderItem,
|
|
74
77
|
Parenthetical,
|
|
@@ -135,6 +138,7 @@ from trilogy.parsing.common import (
|
|
|
135
138
|
align_item_to_concept,
|
|
136
139
|
arbitrary_to_concept,
|
|
137
140
|
constant_to_concept,
|
|
141
|
+
derive_item_to_concept,
|
|
138
142
|
process_function_args,
|
|
139
143
|
rowset_to_concepts,
|
|
140
144
|
)
|
|
@@ -603,6 +607,9 @@ class ParseToObjects(Transformer):
|
|
|
603
607
|
def PROPERTY(self, args):
|
|
604
608
|
return Purpose.PROPERTY
|
|
605
609
|
|
|
610
|
+
def HASH_TYPE(self, args):
|
|
611
|
+
return args.value
|
|
612
|
+
|
|
606
613
|
@v_args(meta=True)
|
|
607
614
|
def prop_ident(self, meta: Meta, args) -> Tuple[List[Concept], str]:
|
|
608
615
|
return [self.environment.concepts[grain] for grain in args[:-1]], args[-1]
|
|
@@ -707,7 +714,14 @@ class ParseToObjects(Transformer):
|
|
|
707
714
|
|
|
708
715
|
if isinstance(
|
|
709
716
|
source_value,
|
|
710
|
-
(
|
|
717
|
+
(
|
|
718
|
+
FilterItem,
|
|
719
|
+
WindowItem,
|
|
720
|
+
AggregateWrapper,
|
|
721
|
+
Function,
|
|
722
|
+
FunctionCallWrapper,
|
|
723
|
+
Comparison,
|
|
724
|
+
),
|
|
711
725
|
):
|
|
712
726
|
concept = arbitrary_to_concept(
|
|
713
727
|
source_value,
|
|
@@ -1275,6 +1289,17 @@ class ParseToObjects(Transformer):
|
|
|
1275
1289
|
def align_clause(self, meta: Meta, args) -> AlignClause:
|
|
1276
1290
|
return AlignClause(items=args)
|
|
1277
1291
|
|
|
1292
|
+
@v_args(meta=True)
|
|
1293
|
+
def derive_item(self, meta: Meta, args) -> DeriveItem:
|
|
1294
|
+
return DeriveItem(
|
|
1295
|
+
expr=args[0], name=args[1], namespace=self.environment.namespace
|
|
1296
|
+
)
|
|
1297
|
+
|
|
1298
|
+
@v_args(meta=True)
|
|
1299
|
+
def derive_clause(self, meta: Meta, args) -> DeriveClause:
|
|
1300
|
+
|
|
1301
|
+
return DeriveClause(items=args)
|
|
1302
|
+
|
|
1278
1303
|
@v_args(meta=True)
|
|
1279
1304
|
def multi_select_statement(self, meta: Meta, args) -> MultiSelectStatement:
|
|
1280
1305
|
|
|
@@ -1284,6 +1309,7 @@ class ParseToObjects(Transformer):
|
|
|
1284
1309
|
order_by: OrderBy | None = None
|
|
1285
1310
|
where: WhereClause | None = None
|
|
1286
1311
|
having: HavingClause | None = None
|
|
1312
|
+
derive: DeriveClause | None = None
|
|
1287
1313
|
for arg in args:
|
|
1288
1314
|
if isinstance(arg, SelectStatement):
|
|
1289
1315
|
selects.append(arg)
|
|
@@ -1297,11 +1323,24 @@ class ParseToObjects(Transformer):
|
|
|
1297
1323
|
having = arg
|
|
1298
1324
|
elif isinstance(arg, AlignClause):
|
|
1299
1325
|
align = arg
|
|
1326
|
+
elif isinstance(arg, DeriveClause):
|
|
1327
|
+
derive = arg
|
|
1300
1328
|
|
|
1301
1329
|
assert align
|
|
1302
1330
|
assert align is not None
|
|
1303
1331
|
|
|
1304
1332
|
derived_concepts = []
|
|
1333
|
+
new_selects = [x.as_lineage(self.environment) for x in selects]
|
|
1334
|
+
lineage = MultiSelectLineage(
|
|
1335
|
+
selects=new_selects,
|
|
1336
|
+
align=align,
|
|
1337
|
+
derive=derive,
|
|
1338
|
+
namespace=self.environment.namespace,
|
|
1339
|
+
where_clause=where,
|
|
1340
|
+
having_clause=having,
|
|
1341
|
+
limit=limit,
|
|
1342
|
+
hidden_components=set(y for x in new_selects for y in x.hidden_components),
|
|
1343
|
+
)
|
|
1305
1344
|
for x in align.items:
|
|
1306
1345
|
concept = align_item_to_concept(
|
|
1307
1346
|
x,
|
|
@@ -1314,6 +1353,19 @@ class ParseToObjects(Transformer):
|
|
|
1314
1353
|
)
|
|
1315
1354
|
derived_concepts.append(concept)
|
|
1316
1355
|
self.environment.add_concept(concept, meta=meta)
|
|
1356
|
+
if derive:
|
|
1357
|
+
for derived in derive.items:
|
|
1358
|
+
derivation = derived.expr
|
|
1359
|
+
name = derived.name
|
|
1360
|
+
if not isinstance(derivation, (Function, Comparison, WindowItem)):
|
|
1361
|
+
raise SyntaxError(
|
|
1362
|
+
f"Invalid derive expression {derivation} in {meta.line}, must be a function or conditional"
|
|
1363
|
+
)
|
|
1364
|
+
concept = derive_item_to_concept(
|
|
1365
|
+
derivation, name, lineage, self.environment.namespace
|
|
1366
|
+
)
|
|
1367
|
+
derived_concepts.append(concept)
|
|
1368
|
+
self.environment.add_concept(concept, meta=meta)
|
|
1317
1369
|
multi = MultiSelectStatement(
|
|
1318
1370
|
selects=selects,
|
|
1319
1371
|
align=align,
|
|
@@ -1323,6 +1375,7 @@ class ParseToObjects(Transformer):
|
|
|
1323
1375
|
limit=limit,
|
|
1324
1376
|
meta=Metadata(line_number=meta.line),
|
|
1325
1377
|
derived_concepts=derived_concepts,
|
|
1378
|
+
derive=derive,
|
|
1326
1379
|
)
|
|
1327
1380
|
return multi
|
|
1328
1381
|
|
|
@@ -1883,6 +1936,10 @@ class ParseToObjects(Transformer):
|
|
|
1883
1936
|
def ftrim(self, meta, args):
|
|
1884
1937
|
return self.function_factory.create_function(args, FunctionType.TRIM, meta)
|
|
1885
1938
|
|
|
1939
|
+
@v_args(meta=True)
|
|
1940
|
+
def fhash(self, meta, args):
|
|
1941
|
+
return self.function_factory.create_function(args, FunctionType.HASH, meta)
|
|
1942
|
+
|
|
1886
1943
|
@v_args(meta=True)
|
|
1887
1944
|
def fsubstring(self, meta, args):
|
|
1888
1945
|
return self.function_factory.create_function(args, FunctionType.SUBSTRING, meta)
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -74,12 +74,16 @@
|
|
|
74
74
|
select_statement: where? "select"i select_list where? having? order_by? limit?
|
|
75
75
|
|
|
76
76
|
// multiple_selects
|
|
77
|
-
multi_select_statement: select_statement ("merge" select_statement)+ "align"i align_clause where? order_by? limit?
|
|
77
|
+
multi_select_statement: select_statement ("merge" select_statement)+ "align"i align_clause ("derive" derive_clause)? where? order_by? limit?
|
|
78
78
|
|
|
79
79
|
align_item: IDENTIFIER ":" IDENTIFIER ("," IDENTIFIER)* ","?
|
|
80
80
|
|
|
81
81
|
align_clause: align_item ("AND"i align_item)* "AND"i?
|
|
82
82
|
|
|
83
|
+
derive_item: expr "->" IDENTIFIER
|
|
84
|
+
|
|
85
|
+
derive_clause: derive_item ("," derive_item)* ","?
|
|
86
|
+
|
|
83
87
|
merge_statement: "merge"i WILDCARD_IDENTIFIER "into"i SHORTHAND_MODIFIER? WILDCARD_IDENTIFIER
|
|
84
88
|
|
|
85
89
|
// raw sql statement
|
|
@@ -308,8 +312,11 @@
|
|
|
308
312
|
fregexp_contains: _REGEXP_CONTAINS expr "," expr ")"
|
|
309
313
|
_REGEXP_REPLACE.1: "regexp_replace("
|
|
310
314
|
fregexp_replace: _REGEXP_REPLACE expr "," expr "," expr ")"
|
|
315
|
+
_HASH.1: "hash("
|
|
316
|
+
HASH_TYPE: "md5"i | "sha1"i | "sha256"i | "sha512"i
|
|
317
|
+
fhash: _HASH expr "," HASH_TYPE ")"
|
|
311
318
|
|
|
312
|
-
_string_functions: like | ilike | upper | flower | fsplit | fstrpos | fsubstring | fcontains | ftrim | freplace | fregexp_extract | fregexp_contains | fregexp_replace
|
|
319
|
+
_string_functions: like | ilike | upper | flower | fsplit | fstrpos | fsubstring | fcontains | ftrim | freplace | fregexp_extract | fregexp_contains | fregexp_replace | fhash
|
|
313
320
|
|
|
314
321
|
//array_functions
|
|
315
322
|
_ARRAY_SUM.1: "array_sum("i
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|