pytrilogy 0.0.3.99__py3-none-any.whl → 0.0.3.100__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.99.dist-info → pytrilogy-0.0.3.100.dist-info}/METADATA +15 -7
- {pytrilogy-0.0.3.99.dist-info → pytrilogy-0.0.3.100.dist-info}/RECORD +18 -19
- trilogy/__init__.py +1 -1
- trilogy/constants.py +1 -0
- trilogy/core/models/execute.py +14 -7
- trilogy/core/processing/concept_strategies_v3.py +7 -37
- trilogy/core/processing/discovery_utility.py +30 -0
- trilogy/core/processing/node_generators/basic_node.py +12 -18
- trilogy/core/processing/node_generators/group_node.py +3 -2
- trilogy/core/processing/node_generators/node_merge_node.py +16 -2
- trilogy/core/processing/node_generators/unnest_node.py +30 -3
- trilogy/core/processing/nodes/group_node.py +6 -4
- trilogy/core/processing/nodes/merge_node.py +9 -1
- trilogy/core/processing/nodes/select_node_v2.py +1 -0
- trilogy/core/processing/discovery_loop.py +0 -0
- {pytrilogy-0.0.3.99.dist-info → pytrilogy-0.0.3.100.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.99.dist-info → pytrilogy-0.0.3.100.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.99.dist-info → pytrilogy-0.0.3.100.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.99.dist-info → pytrilogy-0.0.3.100.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.3.
|
|
3
|
+
Version: 0.0.3.100
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -123,15 +123,12 @@ Versus SQL, Trilogy aims to:
|
|
|
123
123
|
**Improve:**
|
|
124
124
|
- Simplicity
|
|
125
125
|
- Refactoring/maintainability
|
|
126
|
-
- Reusability
|
|
126
|
+
- Reusability/composability
|
|
127
|
+
- Expressivness
|
|
127
128
|
|
|
128
129
|
**Maintain:**
|
|
129
130
|
- Acceptable performance
|
|
130
131
|
|
|
131
|
-
**Remove:**
|
|
132
|
-
- Lower-level procedural features
|
|
133
|
-
- Transactional optimizations/non-analytics features
|
|
134
|
-
|
|
135
132
|
## Backend Support
|
|
136
133
|
|
|
137
134
|
| Backend | Status | Notes |
|
|
@@ -161,6 +158,7 @@ property sentence_id.word_three string::word; # a description to it
|
|
|
161
158
|
# comments in other places are just comments
|
|
162
159
|
|
|
163
160
|
# define our datasource to bind the model to data
|
|
161
|
+
# for most work, you can import something already defined
|
|
164
162
|
# testing using query fixtures is a common pattern
|
|
165
163
|
datasource word_one(
|
|
166
164
|
sentence: sentence_id,
|
|
@@ -323,7 +321,7 @@ from pytrilogy import Executor, Dialect
|
|
|
323
321
|
|
|
324
322
|
### Authoring Imports
|
|
325
323
|
|
|
326
|
-
Are also stable, and should be used for cases which programatically generate Trilogy statements without
|
|
324
|
+
Are also stable, and should be used for cases which programatically generate Trilogy statements without text inputs
|
|
327
325
|
or need to process/transform parsed code in more complicated ways.
|
|
328
326
|
|
|
329
327
|
```python
|
|
@@ -391,6 +389,16 @@ datasource <name>(
|
|
|
391
389
|
)
|
|
392
390
|
grain(<concept>, <concept>)
|
|
393
391
|
address <table>;
|
|
392
|
+
|
|
393
|
+
datasource orders(
|
|
394
|
+
order_id,
|
|
395
|
+
order_date,
|
|
396
|
+
total_rev: point_of_sale_rev,
|
|
397
|
+
customomer_id: customer.id
|
|
398
|
+
)
|
|
399
|
+
grain orders
|
|
400
|
+
address orders;
|
|
401
|
+
|
|
394
402
|
```
|
|
395
403
|
|
|
396
404
|
### Queries
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
3
|
-
trilogy/constants.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.100.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=g9zY8214LmrIQyjxs_q4DVvbSXccQFQwFe0o0_PZcD8,304
|
|
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
|
|
6
6
|
trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
|
|
@@ -28,45 +28,44 @@ trilogy/core/models/build_environment.py,sha256=mpx7MKGc60fnZLVdeLi2YSREy7eQbQYy
|
|
|
28
28
|
trilogy/core/models/core.py,sha256=EofJ8-kltNr_7oFhyCPqauVX1bSJzJI5xOp0eMP_vlA,12892
|
|
29
29
|
trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
|
|
30
30
|
trilogy/core/models/environment.py,sha256=hwTIRnJgaHUdCYof7U5A9NPitGZ2s9yxqiW5O2SaJ9Y,28759
|
|
31
|
-
trilogy/core/models/execute.py,sha256=
|
|
31
|
+
trilogy/core/models/execute.py,sha256=lsNzNjS3nZvoW5CHjYwxDTwBe502NZyytpK1eq8CwW4,42357
|
|
32
32
|
trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
|
|
33
33
|
trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
|
|
34
34
|
trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
|
|
35
35
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
|
|
36
36
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
38
|
-
trilogy/core/processing/discovery_loop.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=tvSN_aiqb1H7LkTl96vj7YK_DKcq_1nDdRJ69wZCLc8,22158
|
|
39
38
|
trilogy/core/processing/discovery_node_factory.py,sha256=5QVYUsci_h6iYWhS0GCoDow2tSAipiBW1OyTRX-g_L8,15581
|
|
40
|
-
trilogy/core/processing/discovery_utility.py,sha256=
|
|
39
|
+
trilogy/core/processing/discovery_utility.py,sha256=Xntgug6VnEF96uw5Zwen1qMEUwKjqrm_ZDUr4i4tc1U,5595
|
|
41
40
|
trilogy/core/processing/discovery_validation.py,sha256=eZ4HfHMpqZLI8MGG2jez8arS8THs6ceuVrQFIY6gXrU,5364
|
|
42
41
|
trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
|
|
43
42
|
trilogy/core/processing/utility.py,sha256=PGQKZgX58kI3gG4nprY8HRGayc2D8fF5RmbvBhQj8ok,23319
|
|
44
43
|
trilogy/core/processing/node_generators/__init__.py,sha256=iVJ-crowPxYeut-hFjyEjfibKIDq7PfB4LEuDAUCjGY,943
|
|
45
|
-
trilogy/core/processing/node_generators/basic_node.py,sha256=
|
|
44
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=0Uhnf07056SBbRkt-wYLw4DZqsFR6jztGLUaE9ebPZs,5577
|
|
46
45
|
trilogy/core/processing/node_generators/common.py,sha256=PdysdroW9DUADP7f5Wv_GKPUyCTROZV1g3L45fawxi8,9443
|
|
47
46
|
trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
|
|
48
47
|
trilogy/core/processing/node_generators/filter_node.py,sha256=ArBsQJl-4fWBJWCE28CRQ7UT7ErnFfbcseoQQZrBodY,11220
|
|
49
|
-
trilogy/core/processing/node_generators/group_node.py,sha256=
|
|
48
|
+
trilogy/core/processing/node_generators/group_node.py,sha256=pq8aqKe4hCtkzFtpHvE15BJoYvpveoe50_2IM1pqjIQ,6732
|
|
50
49
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
|
|
51
50
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
52
|
-
trilogy/core/processing/node_generators/node_merge_node.py,sha256=
|
|
51
|
+
trilogy/core/processing/node_generators/node_merge_node.py,sha256=531ptEAZIczc7PR-kfuNe_KBaDToyIMUMKq4bkoZkgw,23561
|
|
53
52
|
trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
|
|
54
53
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=5L5u6xz1In8EaHQdcYgR2si-tz9WB9YLXURo4AkUT9A,6630
|
|
55
54
|
trilogy/core/processing/node_generators/select_merge_node.py,sha256=KQvGoNT5ZBWQ_caEomRTtG1PKZC7OPT4PKfY0QmwMGE,22270
|
|
56
55
|
trilogy/core/processing/node_generators/select_node.py,sha256=Ta1G39V94gjX_AgyZDz9OqnwLz4BjY3D6Drx9YpziMQ,3555
|
|
57
56
|
trilogy/core/processing/node_generators/synonym_node.py,sha256=AnAsa_Wj50NJ_IK0HSgab_7klYmKVrv0WI1uUe-GvEY,3766
|
|
58
57
|
trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
|
|
59
|
-
trilogy/core/processing/node_generators/unnest_node.py,sha256=
|
|
58
|
+
trilogy/core/processing/node_generators/unnest_node.py,sha256=w9vhPzASz53QPASLqFcLDdR9eY132tgVUcp3QolD5Jw,3726
|
|
60
59
|
trilogy/core/processing/node_generators/window_node.py,sha256=A90linr4pkZtTNfn9k2YNLqrJ_SFII3lbHxB-BC6mI8,6688
|
|
61
60
|
trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
61
|
trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=m2YQ4OmG0N2O61a7NEq1ZzbTa7JsCC00lxB2ymjcYRI,8224
|
|
63
62
|
trilogy/core/processing/nodes/__init__.py,sha256=zTge1EzwzEydlcMliIFO_TT7h7lS8l37lyZuQDir1h0,5487
|
|
64
63
|
trilogy/core/processing/nodes/base_node.py,sha256=C_CjlOzlGMXckyV0b_PJZerpopNesRCKfambMq7Asvc,18221
|
|
65
64
|
trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
|
|
66
|
-
trilogy/core/processing/nodes/group_node.py,sha256=
|
|
67
|
-
trilogy/core/processing/nodes/merge_node.py,sha256=
|
|
65
|
+
trilogy/core/processing/nodes/group_node.py,sha256=njz-5T7OJ3-kaBC7EhdtPra3G77HnI7apjUwMGhUeXo,10569
|
|
66
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=daJywBxh44Gqk-7eTiXbYtY7xo6O6fNvqX-DagTOTmE,16231
|
|
68
67
|
trilogy/core/processing/nodes/recursive_node.py,sha256=k0rizxR8KE64ievfHx_GPfQmU8QAP118Laeyq5BLUOk,1526
|
|
69
|
-
trilogy/core/processing/nodes/select_node_v2.py,sha256=
|
|
68
|
+
trilogy/core/processing/nodes/select_node_v2.py,sha256=IWyKyNgFlV8A2S1FUTPdIaogg6PzaHh-HmQo6v24sbg,8862
|
|
70
69
|
trilogy/core/processing/nodes/union_node.py,sha256=hLAXXVWqEgMWi7dlgSHfCF59fon64av14-uPgJzoKzM,1870
|
|
71
70
|
trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
|
|
72
71
|
trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
|
|
@@ -118,8 +117,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
|
|
|
118
117
|
trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
|
|
119
118
|
trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
|
|
120
119
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
121
|
-
pytrilogy-0.0.3.
|
|
122
|
-
pytrilogy-0.0.3.
|
|
123
|
-
pytrilogy-0.0.3.
|
|
124
|
-
pytrilogy-0.0.3.
|
|
125
|
-
pytrilogy-0.0.3.
|
|
120
|
+
pytrilogy-0.0.3.100.dist-info/METADATA,sha256=T9p4b_yjL4_HtEwChltpxh5mlP8pCoRQPn28Ucu_1gI,11811
|
|
121
|
+
pytrilogy-0.0.3.100.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
122
|
+
pytrilogy-0.0.3.100.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
123
|
+
pytrilogy-0.0.3.100.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
124
|
+
pytrilogy-0.0.3.100.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/constants.py
CHANGED
trilogy/core/models/execute.py
CHANGED
|
@@ -118,19 +118,20 @@ class CTE(BaseModel):
|
|
|
118
118
|
base += f" Source: {self.source.source_type}."
|
|
119
119
|
if self.parent_ctes:
|
|
120
120
|
base += f" References: {', '.join([x.name for x in self.parent_ctes])}."
|
|
121
|
-
if self.joins:
|
|
121
|
+
if self.joins and CONFIG.comments.joins:
|
|
122
122
|
base += f"\n-- Joins: {', '.join([str(x) for x in self.joins])}."
|
|
123
|
-
if self.partial_concepts:
|
|
123
|
+
if self.partial_concepts and CONFIG.comments.partial:
|
|
124
124
|
base += (
|
|
125
125
|
f"\n-- Partials: {', '.join([str(x) for x in self.partial_concepts])}."
|
|
126
126
|
)
|
|
127
|
-
|
|
127
|
+
if CONFIG.comments.source_map:
|
|
128
|
+
base += f"\n-- Source Map: {self.source_map}."
|
|
128
129
|
base += f"\n-- Output: {', '.join([str(x) for x in self.output_columns])}."
|
|
129
130
|
if self.source.input_concepts:
|
|
130
131
|
base += f"\n-- Inputs: {', '.join([str(x) for x in self.source.input_concepts])}."
|
|
131
132
|
if self.hidden_concepts:
|
|
132
133
|
base += f"\n-- Hidden: {', '.join([str(x) for x in self.hidden_concepts])}."
|
|
133
|
-
if self.nullable_concepts:
|
|
134
|
+
if self.nullable_concepts and CONFIG.comments.nullable:
|
|
134
135
|
base += (
|
|
135
136
|
f"\n-- Nullable: {', '.join([str(x) for x in self.nullable_concepts])}."
|
|
136
137
|
)
|
|
@@ -368,6 +369,7 @@ class CTE(BaseModel):
|
|
|
368
369
|
@property
|
|
369
370
|
def group_concepts(self) -> List[BuildConcept]:
|
|
370
371
|
def check_is_not_in_group(c: BuildConcept):
|
|
372
|
+
|
|
371
373
|
if len(self.source_map.get(c.address, [])) > 0:
|
|
372
374
|
return False
|
|
373
375
|
if c.derivation == Derivation.ROWSET:
|
|
@@ -381,8 +383,6 @@ class CTE(BaseModel):
|
|
|
381
383
|
and c.lineage.operator in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
382
384
|
):
|
|
383
385
|
return True
|
|
384
|
-
if c.purpose == Purpose.METRIC:
|
|
385
|
-
return True
|
|
386
386
|
|
|
387
387
|
if c.derivation == Derivation.BASIC and c.lineage:
|
|
388
388
|
if all([check_is_not_in_group(x) for x in c.lineage.concept_arguments]):
|
|
@@ -392,6 +392,10 @@ class CTE(BaseModel):
|
|
|
392
392
|
and c.lineage.operator == FunctionType.GROUP
|
|
393
393
|
):
|
|
394
394
|
return check_is_not_in_group(c.lineage.concept_arguments[0])
|
|
395
|
+
return False
|
|
396
|
+
if c.purpose == Purpose.METRIC:
|
|
397
|
+
return True
|
|
398
|
+
|
|
395
399
|
return False
|
|
396
400
|
|
|
397
401
|
return (
|
|
@@ -707,6 +711,8 @@ class QueryDatasource(BaseModel):
|
|
|
707
711
|
f" {[c.address for c in self.output_concepts]} concepts and"
|
|
708
712
|
f" {other.name} with {[c.address for c in other.output_concepts]} concepts"
|
|
709
713
|
)
|
|
714
|
+
logger.info(self.source_map)
|
|
715
|
+
logger.info(other.source_map)
|
|
710
716
|
|
|
711
717
|
merged_datasources: dict[str, Union[BuildDatasource, "QueryDatasource"]] = {}
|
|
712
718
|
|
|
@@ -770,6 +776,7 @@ class QueryDatasource(BaseModel):
|
|
|
770
776
|
logger.debug(
|
|
771
777
|
f"[Query Datasource] merged with {[c.address for c in qds.output_concepts]} concepts"
|
|
772
778
|
)
|
|
779
|
+
logger.debug(qds.source_map)
|
|
773
780
|
return qds
|
|
774
781
|
|
|
775
782
|
@property
|
|
@@ -777,7 +784,7 @@ class QueryDatasource(BaseModel):
|
|
|
777
784
|
filters = abs(hash(str(self.condition))) if self.condition else ""
|
|
778
785
|
grain = "_".join([str(c).replace(".", "_") for c in self.grain.components])
|
|
779
786
|
group = ""
|
|
780
|
-
if self.
|
|
787
|
+
if self.group_required:
|
|
781
788
|
keys = [
|
|
782
789
|
x.address for x in self.output_concepts if x.purpose != Purpose.METRIC
|
|
783
790
|
]
|
|
@@ -19,13 +19,13 @@ from trilogy.core.processing.discovery_utility import (
|
|
|
19
19
|
LOGGER_PREFIX,
|
|
20
20
|
depth_to_prefix,
|
|
21
21
|
get_priority_concept,
|
|
22
|
+
group_if_required,
|
|
22
23
|
)
|
|
23
24
|
from trilogy.core.processing.discovery_validation import (
|
|
24
25
|
ValidationResult,
|
|
25
26
|
validate_stack,
|
|
26
27
|
)
|
|
27
28
|
from trilogy.core.processing.nodes import (
|
|
28
|
-
GroupNode,
|
|
29
29
|
History,
|
|
30
30
|
MergeNode,
|
|
31
31
|
StrategyNode,
|
|
@@ -218,6 +218,7 @@ def initialize_loop_context(
|
|
|
218
218
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} derived condition row inputs {[x.address for x in required_filters]} present in mandatory list, forcing condition evaluation at this level. "
|
|
219
219
|
)
|
|
220
220
|
mandatory_list = completion_mandatory
|
|
221
|
+
all_mandatory = set(c.address for c in completion_mandatory)
|
|
221
222
|
must_evaluate_condition_on_this_level_not_push_down = True
|
|
222
223
|
else:
|
|
223
224
|
logger.info(
|
|
@@ -263,7 +264,7 @@ def evaluate_loop_conditions(
|
|
|
263
264
|
]
|
|
264
265
|
) and not any(
|
|
265
266
|
[
|
|
266
|
-
x.derivation not in ROOT_DERIVATIONS
|
|
267
|
+
x.derivation not in ROOT_DERIVATIONS + [Derivation.BASIC]
|
|
267
268
|
for x in context.mandatory_list
|
|
268
269
|
if x.address not in context.conditions.row_arguments
|
|
269
270
|
]
|
|
@@ -282,7 +283,7 @@ def evaluate_loop_conditions(
|
|
|
282
283
|
# to ensure filtering happens before something like a SUM
|
|
283
284
|
if (
|
|
284
285
|
context.conditions
|
|
285
|
-
and priority_concept.derivation not in ROOT_DERIVATIONS
|
|
286
|
+
and priority_concept.derivation not in ROOT_DERIVATIONS + [Derivation.BASIC]
|
|
286
287
|
and priority_concept.address not in context.conditions.row_arguments
|
|
287
288
|
):
|
|
288
289
|
logger.info(
|
|
@@ -419,26 +420,9 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
|
|
|
419
420
|
logger.info(
|
|
420
421
|
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Conditions {context.conditions} were injected, checking if we need a group to restore grain"
|
|
421
422
|
)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
downstream_concepts=output.usable_outputs,
|
|
425
|
-
parents=[output.resolve()],
|
|
426
|
-
environment=context.environment,
|
|
427
|
-
depth=context.depth,
|
|
423
|
+
return group_if_required(
|
|
424
|
+
output, context.original_mandatory, context.environment
|
|
428
425
|
)
|
|
429
|
-
if result.required:
|
|
430
|
-
logger.info(
|
|
431
|
-
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Adding group node with outputs {[x.address for x in context.original_mandatory]}"
|
|
432
|
-
)
|
|
433
|
-
return GroupNode(
|
|
434
|
-
output_concepts=context.original_mandatory,
|
|
435
|
-
input_concepts=output.usable_outputs,
|
|
436
|
-
environment=context.environment,
|
|
437
|
-
parents=[output],
|
|
438
|
-
partial_concepts=output.partial_concepts,
|
|
439
|
-
preexisting_conditions=context.conditions.conditional,
|
|
440
|
-
depth=context.depth,
|
|
441
|
-
)
|
|
442
426
|
return output
|
|
443
427
|
|
|
444
428
|
|
|
@@ -604,18 +588,4 @@ def source_query_concepts(
|
|
|
604
588
|
logger.info(
|
|
605
589
|
f"{depth_to_prefix(0)}{LOGGER_PREFIX} final concepts are {[x.address for x in final]}"
|
|
606
590
|
)
|
|
607
|
-
|
|
608
|
-
downstream_concepts=final,
|
|
609
|
-
parents=[root.resolve()],
|
|
610
|
-
environment=environment,
|
|
611
|
-
).required:
|
|
612
|
-
candidate: StrategyNode = GroupNode(
|
|
613
|
-
output_concepts=final,
|
|
614
|
-
input_concepts=final,
|
|
615
|
-
environment=environment,
|
|
616
|
-
parents=[root],
|
|
617
|
-
partial_concepts=root.partial_concepts,
|
|
618
|
-
)
|
|
619
|
-
else:
|
|
620
|
-
candidate = root
|
|
621
|
-
return candidate
|
|
591
|
+
return group_if_required(root, output_concepts, environment)
|
|
@@ -6,6 +6,8 @@ from trilogy.core.models.build import (
|
|
|
6
6
|
BuildConcept,
|
|
7
7
|
BuildRowsetItem,
|
|
8
8
|
)
|
|
9
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
10
|
+
from trilogy.core.processing.nodes import GroupNode, MergeNode, StrategyNode
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
def depth_to_prefix(depth: int) -> str:
|
|
@@ -15,6 +17,34 @@ def depth_to_prefix(depth: int) -> str:
|
|
|
15
17
|
LOGGER_PREFIX = "[DISCOVERY LOOP]"
|
|
16
18
|
|
|
17
19
|
|
|
20
|
+
def group_if_required(
|
|
21
|
+
root: StrategyNode, final: List[BuildConcept], environment: BuildEnvironment
|
|
22
|
+
):
|
|
23
|
+
if isinstance(root, MergeNode) and root.force_group is True:
|
|
24
|
+
return root
|
|
25
|
+
elif isinstance(root, GroupNode):
|
|
26
|
+
return root
|
|
27
|
+
elif GroupNode.check_if_required(
|
|
28
|
+
downstream_concepts=final,
|
|
29
|
+
parents=[root.resolve()],
|
|
30
|
+
environment=environment,
|
|
31
|
+
).required:
|
|
32
|
+
if isinstance(root, MergeNode):
|
|
33
|
+
root.force_group = True
|
|
34
|
+
root.set_output_concepts(final, rebuild=False)
|
|
35
|
+
root.rebuild_cache()
|
|
36
|
+
return root
|
|
37
|
+
return GroupNode(
|
|
38
|
+
output_concepts=final,
|
|
39
|
+
input_concepts=final,
|
|
40
|
+
environment=environment,
|
|
41
|
+
parents=[root],
|
|
42
|
+
partial_concepts=root.partial_concepts,
|
|
43
|
+
preexisting_conditions=root.preexisting_conditions,
|
|
44
|
+
)
|
|
45
|
+
return root
|
|
46
|
+
|
|
47
|
+
|
|
18
48
|
def get_upstream_concepts(base: BuildConcept, nested: bool = False) -> set[str]:
|
|
19
49
|
upstream = set()
|
|
20
50
|
if nested:
|
|
@@ -120,8 +120,8 @@ def gen_basic_node(
|
|
|
120
120
|
f"{depth_prefix}{LOGGER_PREFIX} No basic node could be generated for {concept}"
|
|
121
121
|
)
|
|
122
122
|
return None
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
if parent_node.source_type != SourceType.CONSTANT:
|
|
124
|
+
parent_node.source_type = SourceType.BASIC
|
|
125
125
|
parent_node.add_output_concept(concept)
|
|
126
126
|
for x in equivalent_optional:
|
|
127
127
|
parent_node.add_output_concept(x)
|
|
@@ -129,24 +129,18 @@ def gen_basic_node(
|
|
|
129
129
|
logger.info(
|
|
130
130
|
f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
|
|
131
131
|
)
|
|
132
|
+
# if it's a constant, don't prune outputs
|
|
133
|
+
if parent_node.source_type == SourceType.CONSTANT:
|
|
134
|
+
return parent_node
|
|
132
135
|
targets = [concept] + local_optional + equivalent_optional
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
for
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
]
|
|
141
|
-
parent_node.hide_output_concepts(should_hide)
|
|
142
|
-
should_not_hide = [
|
|
143
|
-
x
|
|
144
|
-
for x in parent_node.output_concepts
|
|
145
|
-
if x.address in targets or any(x.address in y.pseudonyms for y in targets)
|
|
146
|
-
]
|
|
147
|
-
parent_node.unhide_output_concepts(should_not_hide)
|
|
136
|
+
targets = [
|
|
137
|
+
s
|
|
138
|
+
for s in parent_node.output_concepts
|
|
139
|
+
if any(s.address in y.pseudonyms for y in targets)
|
|
140
|
+
] + targets
|
|
141
|
+
parent_node.set_output_concepts(targets)
|
|
148
142
|
|
|
149
143
|
logger.info(
|
|
150
|
-
f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]} hidden {[x for x in parent_node.hidden_concepts]}"
|
|
144
|
+
f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: input: {[x.address for x in parent_node.input_concepts]} output {[x.address for x in parent_node.output_concepts]} hidden {[x for x in parent_node.hidden_concepts]}"
|
|
151
145
|
)
|
|
152
146
|
return parent_node
|
|
@@ -108,13 +108,14 @@ def gen_group_node(
|
|
|
108
108
|
f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg {possible_agg.address}; it has mismatched parent grain {comp_grain } vs local parent {build_grain_parents}"
|
|
109
109
|
)
|
|
110
110
|
if parent_concepts:
|
|
111
|
+
target_grain = BuildGrain.from_concepts(parent_concepts)
|
|
111
112
|
logger.info(
|
|
112
|
-
f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseBuildConceptList(concepts=parent_concepts)}"
|
|
113
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseBuildConceptList(concepts=parent_concepts)} with expected grain {target_grain}"
|
|
113
114
|
)
|
|
114
115
|
parent_concepts = unique(
|
|
115
116
|
[x for x in parent_concepts if not x.name == ALL_ROWS_CONCEPT], "address"
|
|
116
117
|
)
|
|
117
|
-
parent = source_concepts(
|
|
118
|
+
parent: StrategyNode | None = source_concepts(
|
|
118
119
|
mandatory_list=parent_concepts,
|
|
119
120
|
environment=environment,
|
|
120
121
|
g=g,
|
|
@@ -20,6 +20,9 @@ from trilogy.core.models.build import (
|
|
|
20
20
|
BuildWhereClause,
|
|
21
21
|
)
|
|
22
22
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
23
|
+
from trilogy.core.processing.discovery_utility import (
|
|
24
|
+
group_if_required,
|
|
25
|
+
)
|
|
23
26
|
from trilogy.core.processing.nodes import History, MergeNode, StrategyNode
|
|
24
27
|
from trilogy.core.processing.utility import padding
|
|
25
28
|
from trilogy.utility import unique
|
|
@@ -510,6 +513,7 @@ def subgraphs_to_merge_node(
|
|
|
510
513
|
search_conditions: BuildWhereClause | None = None,
|
|
511
514
|
enable_early_exit: bool = True,
|
|
512
515
|
):
|
|
516
|
+
target_grain = BuildGrain.from_concepts(output_concepts, environment=environment)
|
|
513
517
|
parents: List[StrategyNode] = []
|
|
514
518
|
logger.info(
|
|
515
519
|
f"{padding(depth)}{LOGGER_PREFIX} fetching subgraphs {[[c.address for c in subgraph] for subgraph in concept_subgraphs]}"
|
|
@@ -549,16 +553,20 @@ def subgraphs_to_merge_node(
|
|
|
549
553
|
output_c.append(y)
|
|
550
554
|
|
|
551
555
|
if len(parents) == 1 and enable_early_exit:
|
|
556
|
+
|
|
552
557
|
logger.info(
|
|
553
558
|
f"{padding(depth)}{LOGGER_PREFIX} only one parent node, exiting early w/ {[c.address for c in parents[0].output_concepts]}"
|
|
554
559
|
)
|
|
555
|
-
|
|
560
|
+
parent = parents[0]
|
|
561
|
+
return group_if_required(parent, output_concepts, environment)
|
|
562
|
+
|
|
556
563
|
rval = MergeNode(
|
|
557
564
|
input_concepts=unique(input_c, "address"),
|
|
558
|
-
output_concepts=
|
|
565
|
+
output_concepts=output_concepts,
|
|
559
566
|
environment=environment,
|
|
560
567
|
parents=parents,
|
|
561
568
|
depth=depth,
|
|
569
|
+
grain=target_grain,
|
|
562
570
|
# hidden_concepts=[]
|
|
563
571
|
# conditions=conditions,
|
|
564
572
|
# conditions=search_conditions.conditional,
|
|
@@ -579,6 +587,12 @@ def gen_merge_node(
|
|
|
579
587
|
conditions: BuildConditional | None = None,
|
|
580
588
|
search_conditions: BuildWhereClause | None = None,
|
|
581
589
|
) -> Optional[MergeNode]:
|
|
590
|
+
|
|
591
|
+
# we do not actually APPLY these conditions anywhere
|
|
592
|
+
# though we could look at doing that as an optimization
|
|
593
|
+
# it's important to include them so the base discovery loop that was generating
|
|
594
|
+
# the merge node can then add them automatically
|
|
595
|
+
# so we should not return a node with preexisting conditions
|
|
582
596
|
if search_conditions:
|
|
583
597
|
all_search_concepts = unique(
|
|
584
598
|
all_concepts + list(search_conditions.row_arguments), "address"
|
|
@@ -33,6 +33,8 @@ def gen_unnest_node(
|
|
|
33
33
|
logger.info(
|
|
34
34
|
f"{depth_prefix}{LOGGER_PREFIX} unnest node for {concept} with lineage {concept.lineage} has parents + optional {all_parents} and equivalent optional {equivalent_optional}"
|
|
35
35
|
)
|
|
36
|
+
local_conditions = False
|
|
37
|
+
expected_outputs = [concept] + local_optional
|
|
36
38
|
if arguments or local_optional:
|
|
37
39
|
parent = source_concepts(
|
|
38
40
|
mandatory_list=all_parents,
|
|
@@ -47,24 +49,49 @@ def gen_unnest_node(
|
|
|
47
49
|
f"{padding(depth)}{LOGGER_PREFIX} could not find unnest node parents"
|
|
48
50
|
)
|
|
49
51
|
return None
|
|
52
|
+
elif conditions:
|
|
53
|
+
logger.info(
|
|
54
|
+
f"{padding(depth)}{LOGGER_PREFIX} unnest node has no parents but conditions inputs {conditions.row_arguments} vs expected output {expected_outputs}"
|
|
55
|
+
)
|
|
56
|
+
if all([x.address in expected_outputs for x in conditions.row_arguments]):
|
|
57
|
+
local_conditions = True
|
|
58
|
+
else:
|
|
59
|
+
parent = source_concepts(
|
|
60
|
+
mandatory_list=conditions.conditional.row_arguments,
|
|
61
|
+
environment=environment,
|
|
62
|
+
g=g,
|
|
63
|
+
depth=depth + 1,
|
|
64
|
+
history=history,
|
|
65
|
+
conditions=conditions,
|
|
66
|
+
)
|
|
67
|
+
if not parent:
|
|
68
|
+
logger.info(
|
|
69
|
+
f"{padding(depth)}{LOGGER_PREFIX} could not find unnest node condition inputs with no parents"
|
|
70
|
+
)
|
|
71
|
+
return None
|
|
72
|
+
else:
|
|
73
|
+
parent = None
|
|
50
74
|
|
|
51
75
|
base = UnnestNode(
|
|
52
76
|
unnest_concepts=[concept] + equivalent_optional,
|
|
53
77
|
input_concepts=arguments + non_equivalent_optional,
|
|
54
78
|
output_concepts=[concept] + local_optional,
|
|
55
79
|
environment=environment,
|
|
56
|
-
parents=([parent] if
|
|
80
|
+
parents=([parent] if parent else []),
|
|
57
81
|
)
|
|
58
82
|
# we need to sometimes nest an unnest node,
|
|
59
83
|
# as unnest operations are not valid in all situations
|
|
60
84
|
# TODO: inline this node when we can detect it's safe
|
|
85
|
+
conditional = conditions.conditional if conditions else None
|
|
61
86
|
new = StrategyNode(
|
|
62
87
|
input_concepts=base.output_concepts,
|
|
63
88
|
output_concepts=base.output_concepts,
|
|
64
89
|
environment=environment,
|
|
65
90
|
parents=[base],
|
|
66
|
-
|
|
67
|
-
preexisting_conditions=
|
|
91
|
+
conditions=conditional if local_conditions is True else None,
|
|
92
|
+
preexisting_conditions=(
|
|
93
|
+
conditional if conditional and local_conditions is False else None
|
|
94
|
+
),
|
|
68
95
|
)
|
|
69
96
|
qds = new.resolve()
|
|
70
97
|
assert qds.source_map[concept.address] == {base.resolve()}
|
|
@@ -113,16 +113,18 @@ class GroupNode(StrategyNode):
|
|
|
113
113
|
environment.concepts[c] for c in (comp_grain - target_grain).components
|
|
114
114
|
]
|
|
115
115
|
logger.info(
|
|
116
|
-
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, difference {difference}"
|
|
116
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, difference {[x.address for x in difference]}"
|
|
117
117
|
)
|
|
118
118
|
|
|
119
119
|
# if the difference is all unique properties whose keys are in the source grain
|
|
120
120
|
# we can also suppress the group
|
|
121
121
|
if all(
|
|
122
122
|
[
|
|
123
|
-
x.
|
|
124
|
-
and
|
|
125
|
-
|
|
123
|
+
x.keys
|
|
124
|
+
and all(
|
|
125
|
+
environment.concepts[z].address in comp_grain.components
|
|
126
|
+
for z in x.keys
|
|
127
|
+
)
|
|
126
128
|
for x in difference
|
|
127
129
|
]
|
|
128
130
|
):
|
|
@@ -308,7 +308,13 @@ class MergeNode(StrategyNode):
|
|
|
308
308
|
f"{self.logging_prefix}{LOGGER_PREFIX} skipping existence only source with {source.output_concepts} from grain accumulation"
|
|
309
309
|
)
|
|
310
310
|
continue
|
|
311
|
+
logger.info(
|
|
312
|
+
f"{self.logging_prefix}{LOGGER_PREFIX} adding source grain {source.grain} from source {source.identifier} to pregrain"
|
|
313
|
+
)
|
|
311
314
|
pregrain += source.grain
|
|
315
|
+
logger.info(
|
|
316
|
+
f"{self.logging_prefix}{LOGGER_PREFIX} pregrain is now {pregrain}"
|
|
317
|
+
)
|
|
312
318
|
|
|
313
319
|
pregrain = BuildGrain.from_concepts(
|
|
314
320
|
pregrain.components, environment=self.environment
|
|
@@ -334,7 +340,9 @@ class MergeNode(StrategyNode):
|
|
|
334
340
|
if isinstance(join, BaseJoin) and join.join_type == JoinType.FULL:
|
|
335
341
|
full_join_concepts += join.input_concepts
|
|
336
342
|
|
|
337
|
-
if self.
|
|
343
|
+
if self.force_group is True:
|
|
344
|
+
force_group = True
|
|
345
|
+
elif self.whole_grain:
|
|
338
346
|
force_group = False
|
|
339
347
|
elif self.force_group is False:
|
|
340
348
|
force_group = False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|