pytrilogy 0.0.3.63__py3-none-any.whl → 0.0.3.64__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.63.dist-info → pytrilogy-0.0.3.64.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.63.dist-info → pytrilogy-0.0.3.64.dist-info}/RECORD +19 -19
- trilogy/__init__.py +1 -1
- trilogy/core/models/build.py +6 -1
- trilogy/core/processing/concept_strategies_v3.py +31 -15
- trilogy/core/processing/discovery_node_factory.py +2 -3
- trilogy/core/processing/node_generators/node_merge_node.py +0 -1
- trilogy/core/processing/node_generators/select_merge_node.py +52 -1
- trilogy/core/processing/node_generators/synonym_node.py +4 -2
- trilogy/core/processing/nodes/__init__.py +11 -29
- trilogy/core/statements/author.py +1 -1
- trilogy/dialect/base.py +2 -0
- trilogy/hooks/graph_hook.py +66 -12
- trilogy/parsing/common.py +2 -2
- trilogy/parsing/render.py +5 -1
- {pytrilogy-0.0.3.63.dist-info → pytrilogy-0.0.3.64.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.63.dist-info → pytrilogy-0.0.3.64.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.63.dist-info → pytrilogy-0.0.3.64.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.63.dist-info → pytrilogy-0.0.3.64.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.64.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=qOtWjDJ0j0xbQBLf8qO2rhFCUnbYN4Ds9lNcOIOte1k,303
|
|
3
3
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
trilogy/constants.py,sha256=lv_aJWP6dn6e2aF4BAE72jbnNtceFddfqtiDSsvzno0,1692
|
|
5
5
|
trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
|
|
@@ -24,7 +24,7 @@ trilogy/core/query_processor.py,sha256=QiE_w5HgheT4GLZFnaLssJ4plf4voK0TeTd6N3jhR
|
|
|
24
24
|
trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
|
|
25
25
|
trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
trilogy/core/models/author.py,sha256=8XbIsQr6cQrgo9uzee5qRoYiMdEG7yKF4FiiWImW7U0,77490
|
|
27
|
-
trilogy/core/models/build.py,sha256=
|
|
27
|
+
trilogy/core/models/build.py,sha256=80v9rxwl41O8_7uIJoHK4tnTUfgR6u8EPrwHg4ySqO4,63323
|
|
28
28
|
trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
|
|
29
29
|
trilogy/core/models/core.py,sha256=EMAuWTngoNVGCdfNrAY7_k6g528iodNQLwPRVip-8DA,10980
|
|
30
30
|
trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
|
|
@@ -35,9 +35,9 @@ trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnb
|
|
|
35
35
|
trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
|
|
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=uD_Bzy2l30TJ5-6v0tn9dO-vI6zFRgtHsIHUvge3Sps,22536
|
|
39
39
|
trilogy/core/processing/discovery_loop.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
-
trilogy/core/processing/discovery_node_factory.py,sha256=
|
|
40
|
+
trilogy/core/processing/discovery_node_factory.py,sha256=X3-ywUvGbbcwwWtMqKXsPc6xyh3o41eiLRuByXGCvY4,14915
|
|
41
41
|
trilogy/core/processing/discovery_utility.py,sha256=hF3aUbRHHZFeFT5aBjE6TuSeU60I90gzmj512QXG_t8,4856
|
|
42
42
|
trilogy/core/processing/discovery_validation.py,sha256=Ek9jviFgimLMUMYLXBChUQmOD94ihhwQ3NDVe6RTdWg,4930
|
|
43
43
|
trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
|
|
@@ -49,18 +49,18 @@ trilogy/core/processing/node_generators/filter_node.py,sha256=0hdfiS2I-Jvr6P-il3
|
|
|
49
49
|
trilogy/core/processing/node_generators/group_node.py,sha256=nIfiMrJQEksUfqAeeA3X5PS1343y4lmPTipYuCa-rvs,6141
|
|
50
50
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
|
|
51
51
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
52
|
-
trilogy/core/processing/node_generators/node_merge_node.py,sha256
|
|
52
|
+
trilogy/core/processing/node_generators/node_merge_node.py,sha256=-suq5RSkI9SZLH7p69lLL5nMaUDVfsRvpIZ9wiuoRps,16647
|
|
53
53
|
trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
|
|
54
54
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=2BiSsegbRF9csJ_Xl8P_CxIm4dAAb7dF29u6v_Odr-A,6709
|
|
55
|
-
trilogy/core/processing/node_generators/select_merge_node.py,sha256=
|
|
55
|
+
trilogy/core/processing/node_generators/select_merge_node.py,sha256=Usa58nRrp-nww9g2QtihKa_5B_duY3nMCjXnqT-bIY0,21557
|
|
56
56
|
trilogy/core/processing/node_generators/select_node.py,sha256=3dvw0d53eUtCRCUPN6J48I3qBEX1Wha7saQ_ndPu6_I,1777
|
|
57
|
-
trilogy/core/processing/node_generators/synonym_node.py,sha256=
|
|
57
|
+
trilogy/core/processing/node_generators/synonym_node.py,sha256=CN2swdGPEP_Irx4GykHp4gyLCK0dWd2vX7PYJUGxw7w,3548
|
|
58
58
|
trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
|
|
59
59
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=ueOQtoTf2iJHO09RzWHDFQ5iKZq2fVhGf2KAF2U2kU8,2677
|
|
60
60
|
trilogy/core/processing/node_generators/window_node.py,sha256=GP3Hvkbb0TDA6ef7W7bmvQEHVH-NRIfBT_0W4fcH3g4,6529
|
|
61
61
|
trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
62
|
trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=GMW07bb6hXurhF0hZLYoMAKSIS65tat5hwBjvqqPeSA,6516
|
|
63
|
-
trilogy/core/processing/nodes/__init__.py,sha256=
|
|
63
|
+
trilogy/core/processing/nodes/__init__.py,sha256=zTge1EzwzEydlcMliIFO_TT7h7lS8l37lyZuQDir1h0,5487
|
|
64
64
|
trilogy/core/processing/nodes/base_node.py,sha256=p6yljFNLQsXz277c5wTATMNqsKUbsdP_3e7--tezBMw,17691
|
|
65
65
|
trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
|
|
66
66
|
trilogy/core/processing/nodes/group_node.py,sha256=4EbOur1wSsOpPvP6znHih126o6A-TWbBXyvhiw5B0rs,10505
|
|
@@ -71,12 +71,12 @@ trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ
|
|
|
71
71
|
trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
|
|
72
72
|
trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
|
|
73
73
|
trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
|
-
trilogy/core/statements/author.py,sha256=
|
|
74
|
+
trilogy/core/statements/author.py,sha256=6cGCuKERNkH22T6iTsgoNp5CcIFwknF3WX-UmegbUPA,15409
|
|
75
75
|
trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
76
|
trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
|
|
77
77
|
trilogy/core/statements/execute.py,sha256=rqfuoMuXPcH7L7TmE1dSiZ_K_A1ohB8whVMfGimZBOk,1294
|
|
78
78
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
|
-
trilogy/dialect/base.py,sha256=
|
|
79
|
+
trilogy/dialect/base.py,sha256=5lmOd6MOkM0H-Yss9l6uyEJ4JTainPiEv1xVL9sEAh8,43102
|
|
80
80
|
trilogy/dialect/bigquery.py,sha256=6ghCqy-k7UioIJc1EEQ7gRo_PHaO8Vm7yYbiQ-kgpzs,3629
|
|
81
81
|
trilogy/dialect/common.py,sha256=hhzuMTFW9QQIP7TKLT9BlJy6lw2R03a68jKQ-7t4-2c,6070
|
|
82
82
|
trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
|
|
@@ -89,16 +89,16 @@ trilogy/dialect/snowflake.py,sha256=LQIcHuyuGZXbxrv6sH17aLXLzw7yFVuRoE9M4doNk5k,
|
|
|
89
89
|
trilogy/dialect/sql_server.py,sha256=z2Vg7Qvw83rbGiEFIvHHLqVWJTWiz2xs76kpQj4HdTU,3131
|
|
90
90
|
trilogy/hooks/__init__.py,sha256=T3SF3phuUDPLXKGRVE_Lf9mzuwoXWyaLolncR_1kY30,144
|
|
91
91
|
trilogy/hooks/base_hook.py,sha256=I_l-NBMNC7hKTDx1JgHZPVOOCvLQ36m2oIGaR5EUMXY,1180
|
|
92
|
-
trilogy/hooks/graph_hook.py,sha256=
|
|
92
|
+
trilogy/hooks/graph_hook.py,sha256=4jeC3ot4u_FyqiYiHr3Eit2ktD0zPk7_pcG8JgFoqMc,4844
|
|
93
93
|
trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
|
|
94
94
|
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
95
95
|
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
96
|
-
trilogy/parsing/common.py,sha256=
|
|
96
|
+
trilogy/parsing/common.py,sha256=yuKN3fQEtftRMZlJb0ESUX4TLOVFcAE0vw2CfImYG1A,29980
|
|
97
97
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
98
98
|
trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
|
|
99
99
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
100
100
|
trilogy/parsing/parse_engine.py,sha256=O7aM5nZ4SjKlqO2x8XWefI1BMCW06jYYLhABU4k1HCI,72430
|
|
101
|
-
trilogy/parsing/render.py,sha256=
|
|
101
|
+
trilogy/parsing/render.py,sha256=gGCFj2ue0UoaU2MR6qHGMAHXkYRMkTmHjnBowdcgFMY,19603
|
|
102
102
|
trilogy/parsing/trilogy.lark,sha256=x9D1BXtE1E9Kxatx5Kt7xCaid8zgedabwca_B7j7L7o,14331
|
|
103
103
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
104
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
@@ -110,8 +110,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
|
|
|
110
110
|
trilogy/std/net.preql,sha256=-bMV6dyofskl4Kvows-iQ4JCxjVUwsZOeWCy8JO5Ftw,135
|
|
111
111
|
trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
|
|
112
112
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
113
|
-
pytrilogy-0.0.3.
|
|
114
|
-
pytrilogy-0.0.3.
|
|
115
|
-
pytrilogy-0.0.3.
|
|
116
|
-
pytrilogy-0.0.3.
|
|
117
|
-
pytrilogy-0.0.3.
|
|
113
|
+
pytrilogy-0.0.3.64.dist-info/METADATA,sha256=-m9RjEszSqiTRWWSld1SMzy7JKn6e9SPxLUUrjsgqs8,9095
|
|
114
|
+
pytrilogy-0.0.3.64.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
115
|
+
pytrilogy-0.0.3.64.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
116
|
+
pytrilogy-0.0.3.64.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
117
|
+
pytrilogy-0.0.3.64.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/core/models/build.py
CHANGED
|
@@ -1586,7 +1586,10 @@ class Factory:
|
|
|
1586
1586
|
|
|
1587
1587
|
return BuildFunction.model_construct(
|
|
1588
1588
|
operator=base.operator,
|
|
1589
|
-
arguments=[
|
|
1589
|
+
arguments=[
|
|
1590
|
+
rval,
|
|
1591
|
+
*[self.handle_constant(self.build(c)) for c in raw_args[1:]],
|
|
1592
|
+
],
|
|
1590
1593
|
output_datatype=base.output_datatype,
|
|
1591
1594
|
output_purpose=base.output_purpose,
|
|
1592
1595
|
valid_inputs=base.valid_inputs,
|
|
@@ -2042,4 +2045,6 @@ class Factory:
|
|
|
2042
2045
|
and base.lineage.operator == FunctionType.CONSTANT
|
|
2043
2046
|
):
|
|
2044
2047
|
return BuildParamaterizedConceptReference(concept=base)
|
|
2048
|
+
elif isinstance(base, ConceptRef):
|
|
2049
|
+
return self.handle_constant(self.build(base))
|
|
2045
2050
|
return base
|
|
@@ -54,11 +54,7 @@ def generate_candidates_restrictive(
|
|
|
54
54
|
exhausted: set[str],
|
|
55
55
|
depth: int,
|
|
56
56
|
conditions: BuildWhereClause | None = None,
|
|
57
|
-
) ->
|
|
58
|
-
# if it's single row, joins are irrelevant. Fetch without keys.
|
|
59
|
-
if priority_concept.granularity == Granularity.SINGLE_ROW:
|
|
60
|
-
return []
|
|
61
|
-
|
|
57
|
+
) -> tuple[list[BuildConcept], BuildWhereClause | None]:
|
|
62
58
|
local_candidates = [
|
|
63
59
|
x
|
|
64
60
|
for x in list(candidates)
|
|
@@ -71,8 +67,16 @@ def generate_candidates_restrictive(
|
|
|
71
67
|
logger.info(
|
|
72
68
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Injecting additional conditional row arguments as all remaining concepts are roots or constant"
|
|
73
69
|
)
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
# otherwise, we can ignore the conditions now that we've injected inputs
|
|
71
|
+
return (
|
|
72
|
+
unique(list(conditions.row_arguments) + local_candidates, "address"),
|
|
73
|
+
None,
|
|
74
|
+
)
|
|
75
|
+
# if it's single row, joins are irrelevant. Fetch without keys.
|
|
76
|
+
if priority_concept.granularity == Granularity.SINGLE_ROW:
|
|
77
|
+
return [], conditions
|
|
78
|
+
|
|
79
|
+
return local_candidates, conditions
|
|
76
80
|
|
|
77
81
|
|
|
78
82
|
def append_existence_check(
|
|
@@ -104,9 +108,7 @@ def append_existence_check(
|
|
|
104
108
|
)
|
|
105
109
|
assert parent, "Could not resolve existence clause"
|
|
106
110
|
node.add_parents([parent])
|
|
107
|
-
logger.info(
|
|
108
|
-
f"{LOGGER_PREFIX} fetching existence clause inputs {[str(c) for c in subselect]}"
|
|
109
|
-
)
|
|
111
|
+
logger.info(f"{LOGGER_PREFIX} found {[str(c) for c in subselect]}")
|
|
110
112
|
node.add_existence_concepts([*subselect])
|
|
111
113
|
|
|
112
114
|
|
|
@@ -440,7 +442,19 @@ def _search_concepts(
|
|
|
440
442
|
accept_partial: bool = False,
|
|
441
443
|
conditions: BuildWhereClause | None = None,
|
|
442
444
|
) -> StrategyNode | None:
|
|
445
|
+
# check for direct materialization first
|
|
446
|
+
candidate = history.gen_select_node(
|
|
447
|
+
mandatory_list,
|
|
448
|
+
environment,
|
|
449
|
+
g,
|
|
450
|
+
depth + 1,
|
|
451
|
+
fail_if_not_found=False,
|
|
452
|
+
accept_partial=accept_partial,
|
|
453
|
+
conditions=conditions,
|
|
454
|
+
)
|
|
443
455
|
|
|
456
|
+
if candidate:
|
|
457
|
+
return candidate
|
|
444
458
|
context = initialize_loop_context(
|
|
445
459
|
mandatory_list=mandatory_list,
|
|
446
460
|
environment=environment,
|
|
@@ -460,19 +474,21 @@ def _search_concepts(
|
|
|
460
474
|
)
|
|
461
475
|
|
|
462
476
|
local_conditions = evaluate_loop_conditions(context, priority_concept)
|
|
463
|
-
logger.info(
|
|
464
|
-
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} priority concept is {str(priority_concept)} derivation {priority_concept.derivation} granularity {priority_concept.granularity} with conditions {local_conditions}"
|
|
465
|
-
)
|
|
466
477
|
|
|
467
478
|
candidates = [
|
|
468
479
|
c for c in context.mandatory_list if c.address != priority_concept.address
|
|
469
480
|
]
|
|
470
|
-
|
|
481
|
+
# the local conditions list may be override if we end up injecting conditions
|
|
482
|
+
candidate_list, local_conditions = generate_candidates_restrictive(
|
|
471
483
|
priority_concept,
|
|
472
484
|
candidates,
|
|
473
485
|
context.skip,
|
|
474
486
|
depth=depth,
|
|
475
|
-
conditions=
|
|
487
|
+
conditions=local_conditions,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
logger.info(
|
|
491
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} priority concept is {str(priority_concept)} derivation {priority_concept.derivation} granularity {priority_concept.granularity} with conditions {local_conditions}"
|
|
476
492
|
)
|
|
477
493
|
|
|
478
494
|
logger.info(
|
|
@@ -438,15 +438,14 @@ def generate_node(
|
|
|
438
438
|
)
|
|
439
439
|
|
|
440
440
|
# Try materialized concept first
|
|
441
|
+
# this is worth checking every loop iteration
|
|
441
442
|
candidate = history.gen_select_node(
|
|
442
|
-
concept,
|
|
443
|
-
local_optional,
|
|
443
|
+
[concept] + local_optional,
|
|
444
444
|
environment,
|
|
445
445
|
g,
|
|
446
446
|
depth + 1,
|
|
447
447
|
fail_if_not_found=False,
|
|
448
448
|
accept_partial=accept_partial,
|
|
449
|
-
accept_partial_optional=False,
|
|
450
449
|
conditions=conditions,
|
|
451
450
|
)
|
|
452
451
|
|
|
@@ -239,7 +239,6 @@ def resolve_weak_components(
|
|
|
239
239
|
if "__preql_internal" not in c.address
|
|
240
240
|
]
|
|
241
241
|
)
|
|
242
|
-
logger.debug(f"Resolving weak components for {node_list} in {search_graph.nodes}")
|
|
243
242
|
synonyms: set[str] = set()
|
|
244
243
|
for x in all_concepts:
|
|
245
244
|
synonyms = synonyms.union(x.pseudonyms)
|
|
@@ -68,6 +68,8 @@ def get_graph_exact_match(
|
|
|
68
68
|
if node in datasources:
|
|
69
69
|
ds = datasources[node]
|
|
70
70
|
if not isinstance(ds, list):
|
|
71
|
+
if not ds.non_partial_for:
|
|
72
|
+
continue
|
|
71
73
|
if ds.non_partial_for and conditions == ds.non_partial_for:
|
|
72
74
|
exact.add(node)
|
|
73
75
|
continue
|
|
@@ -95,6 +97,31 @@ def get_graph_grains(g: nx.DiGraph) -> dict[str, list[str]]:
|
|
|
95
97
|
return grain_length
|
|
96
98
|
|
|
97
99
|
|
|
100
|
+
def subgraph_is_complete(
|
|
101
|
+
nodes: list[str], targets: set[str], mapping: dict[str, str]
|
|
102
|
+
) -> bool:
|
|
103
|
+
mapped = set([mapping.get(n, n) for n in nodes])
|
|
104
|
+
return all([t in mapped for t in targets])
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def prune_sources_for_conditions(
|
|
108
|
+
g: nx.DiGraph,
|
|
109
|
+
depth: int,
|
|
110
|
+
conditions: BuildWhereClause | None,
|
|
111
|
+
):
|
|
112
|
+
|
|
113
|
+
complete = get_graph_exact_match(g, conditions)
|
|
114
|
+
to_remove = []
|
|
115
|
+
for node in g.nodes:
|
|
116
|
+
if node.startswith("ds~") and node not in complete:
|
|
117
|
+
to_remove.append(node)
|
|
118
|
+
logger.debug(
|
|
119
|
+
f"{padding(depth)}{LOGGER_PREFIX} removing datasource {node} as it is not a match for conditions {conditions}"
|
|
120
|
+
)
|
|
121
|
+
for node in to_remove:
|
|
122
|
+
g.remove_node(node)
|
|
123
|
+
|
|
124
|
+
|
|
98
125
|
def create_pruned_concept_graph(
|
|
99
126
|
g: nx.DiGraph,
|
|
100
127
|
all_concepts: List[BuildConcept],
|
|
@@ -104,7 +131,10 @@ def create_pruned_concept_graph(
|
|
|
104
131
|
depth: int = 0,
|
|
105
132
|
) -> nx.DiGraph:
|
|
106
133
|
orig_g = g
|
|
134
|
+
|
|
107
135
|
g = g.copy()
|
|
136
|
+
if conditions:
|
|
137
|
+
prune_sources_for_conditions(g, depth, conditions)
|
|
108
138
|
union_options = get_union_sources(datasources, all_concepts)
|
|
109
139
|
for ds_list in union_options:
|
|
110
140
|
node_address = "ds~" + "-".join([x.name for x in ds_list])
|
|
@@ -183,6 +213,13 @@ def create_pruned_concept_graph(
|
|
|
183
213
|
)
|
|
184
214
|
|
|
185
215
|
subgraphs = list(nx.connected_components(g.to_undirected()))
|
|
216
|
+
|
|
217
|
+
subgraphs = [
|
|
218
|
+
s
|
|
219
|
+
for s in subgraphs
|
|
220
|
+
if subgraph_is_complete(s, target_addresses, relevant_concepts_pre)
|
|
221
|
+
]
|
|
222
|
+
|
|
186
223
|
if not subgraphs:
|
|
187
224
|
logger.info(
|
|
188
225
|
f"{padding(depth)}{LOGGER_PREFIX} cannot resolve root graph - no subgraphs after node prune"
|
|
@@ -486,6 +523,20 @@ def gen_select_merge_node(
|
|
|
486
523
|
non_constant = [c for c in all_concepts if c.derivation != Derivation.CONSTANT]
|
|
487
524
|
constants = [c for c in all_concepts if c.derivation == Derivation.CONSTANT]
|
|
488
525
|
if not non_constant and constants:
|
|
526
|
+
logger.info(
|
|
527
|
+
f"{padding(depth)}{LOGGER_PREFIX} only constant inputs to discovery, returning constant node directly"
|
|
528
|
+
)
|
|
529
|
+
if conditions:
|
|
530
|
+
if not all(
|
|
531
|
+
[x.derivation == Derivation.CONSTANT for x in conditions.row_arguments]
|
|
532
|
+
):
|
|
533
|
+
logger.info(
|
|
534
|
+
f"{padding(depth)}{LOGGER_PREFIX} conditions being passed in to constant node {conditions}, but not all concepts are constants."
|
|
535
|
+
)
|
|
536
|
+
return None
|
|
537
|
+
else:
|
|
538
|
+
constants += conditions.row_arguments
|
|
539
|
+
|
|
489
540
|
return ConstantNode(
|
|
490
541
|
output_concepts=constants,
|
|
491
542
|
input_concepts=[],
|
|
@@ -494,7 +545,7 @@ def gen_select_merge_node(
|
|
|
494
545
|
depth=depth,
|
|
495
546
|
partial_concepts=[],
|
|
496
547
|
force_group=False,
|
|
497
|
-
|
|
548
|
+
conditions=conditions.conditional if conditions else None,
|
|
498
549
|
)
|
|
499
550
|
for attempt in [False, True]:
|
|
500
551
|
pruned_concept_graph = create_pruned_concept_graph(
|
|
@@ -50,7 +50,9 @@ def gen_synonym_node(
|
|
|
50
50
|
|
|
51
51
|
logger.info(f"{local_prefix} Generating Synonym Node with {len(synonyms)} synonyms")
|
|
52
52
|
sorted_keys = sorted(synonyms.keys())
|
|
53
|
-
combinations_list
|
|
53
|
+
combinations_list: list[tuple[BuildConcept, ...]] = list(
|
|
54
|
+
itertools.product(*(synonyms[obj] for obj in sorted_keys))
|
|
55
|
+
)
|
|
54
56
|
|
|
55
57
|
def similarity_sort_key(combo):
|
|
56
58
|
addresses = [x.address for x in combo]
|
|
@@ -83,7 +85,7 @@ def gen_synonym_node(
|
|
|
83
85
|
f"{local_prefix} checking combination {fingerprint} with {len(combo)} concepts"
|
|
84
86
|
)
|
|
85
87
|
attempt: StrategyNode | None = source_concepts(
|
|
86
|
-
combo,
|
|
88
|
+
list(combo),
|
|
87
89
|
history=history,
|
|
88
90
|
environment=environment,
|
|
89
91
|
depth=depth,
|
|
@@ -124,51 +124,31 @@ class History(BaseModel):
|
|
|
124
124
|
in self.started
|
|
125
125
|
)
|
|
126
126
|
|
|
127
|
-
def _select_concepts_to_lookup(
|
|
128
|
-
self,
|
|
129
|
-
main: BuildConcept,
|
|
130
|
-
search: list[BuildConcept],
|
|
131
|
-
accept_partial: bool,
|
|
132
|
-
fail_if_not_found: bool,
|
|
133
|
-
accept_partial_optional: bool,
|
|
134
|
-
conditions: BuildWhereClause | None = None,
|
|
135
|
-
) -> str:
|
|
136
|
-
return (
|
|
137
|
-
str(main.address)
|
|
138
|
-
+ "|"
|
|
139
|
-
+ "-".join([c.address for c in search])
|
|
140
|
-
+ str(accept_partial)
|
|
141
|
-
+ str(fail_if_not_found)
|
|
142
|
-
+ str(accept_partial_optional)
|
|
143
|
-
+ str(conditions)
|
|
144
|
-
)
|
|
145
|
-
|
|
146
127
|
def gen_select_node(
|
|
147
128
|
self,
|
|
148
|
-
|
|
149
|
-
local_optional: list[BuildConcept],
|
|
129
|
+
concepts: list[BuildConcept],
|
|
150
130
|
environment: BuildEnvironment,
|
|
151
131
|
g,
|
|
152
132
|
depth: int,
|
|
153
133
|
fail_if_not_found: bool = False,
|
|
154
134
|
accept_partial: bool = False,
|
|
155
|
-
accept_partial_optional: bool = False,
|
|
156
135
|
conditions: BuildWhereClause | None = None,
|
|
157
136
|
) -> StrategyNode | None:
|
|
158
137
|
from trilogy.core.processing.node_generators.select_node import gen_select_node
|
|
159
138
|
|
|
160
|
-
fingerprint = self.
|
|
161
|
-
|
|
162
|
-
local_optional,
|
|
139
|
+
fingerprint = self._concepts_to_lookup(
|
|
140
|
+
concepts,
|
|
163
141
|
accept_partial,
|
|
164
|
-
fail_if_not_found,
|
|
165
|
-
accept_partial_optional=accept_partial_optional,
|
|
166
142
|
conditions=conditions,
|
|
167
143
|
)
|
|
168
144
|
if fingerprint in self.select_history:
|
|
169
|
-
|
|
145
|
+
rval = self.select_history[fingerprint]
|
|
146
|
+
if rval:
|
|
147
|
+
# all nodes must be copied before returning
|
|
148
|
+
return rval.copy()
|
|
149
|
+
return rval
|
|
170
150
|
gen = gen_select_node(
|
|
171
|
-
|
|
151
|
+
concepts,
|
|
172
152
|
environment,
|
|
173
153
|
g,
|
|
174
154
|
depth + 1,
|
|
@@ -177,6 +157,8 @@ class History(BaseModel):
|
|
|
177
157
|
conditions=conditions,
|
|
178
158
|
)
|
|
179
159
|
self.select_history[fingerprint] = gen
|
|
160
|
+
if gen:
|
|
161
|
+
return gen.copy()
|
|
180
162
|
return gen
|
|
181
163
|
|
|
182
164
|
|
trilogy/dialect/base.py
CHANGED
|
@@ -58,6 +58,7 @@ from trilogy.core.query_processor import process_copy, process_persist, process_
|
|
|
58
58
|
from trilogy.core.statements.author import (
|
|
59
59
|
ConceptDeclarationStatement,
|
|
60
60
|
CopyStatement,
|
|
61
|
+
FunctionDeclaration,
|
|
61
62
|
ImportStatement,
|
|
62
63
|
MergeStatementV2,
|
|
63
64
|
MultiSelectStatement,
|
|
@@ -980,6 +981,7 @@ class BaseDialect:
|
|
|
980
981
|
ImportStatement,
|
|
981
982
|
RowsetDerivationStatement,
|
|
982
983
|
Datasource,
|
|
984
|
+
FunctionDeclaration,
|
|
983
985
|
),
|
|
984
986
|
):
|
|
985
987
|
continue
|
trilogy/hooks/graph_hook.py
CHANGED
|
@@ -22,7 +22,6 @@ class GraphHook(BaseHook):
|
|
|
22
22
|
pass
|
|
23
23
|
except ImportError:
|
|
24
24
|
raise ImportError("GraphHook requires matplotlib and scipy to be installed")
|
|
25
|
-
|
|
26
25
|
# https://github.com/python/cpython/issues/125235#issuecomment-2412948604
|
|
27
26
|
|
|
28
27
|
def query_graph_built(
|
|
@@ -39,8 +38,10 @@ class GraphHook(BaseHook):
|
|
|
39
38
|
for node in nodes:
|
|
40
39
|
if "__preql_internal" in node:
|
|
41
40
|
graph.remove_node(node)
|
|
41
|
+
|
|
42
42
|
if remove_isolates:
|
|
43
43
|
graph.remove_nodes_from(list(nx.isolates(graph)))
|
|
44
|
+
|
|
44
45
|
color_map = []
|
|
45
46
|
highlight_nodes = highlight_nodes or []
|
|
46
47
|
for node in graph:
|
|
@@ -50,9 +51,10 @@ class GraphHook(BaseHook):
|
|
|
50
51
|
color_map.append("blue")
|
|
51
52
|
else:
|
|
52
53
|
color_map.append("green")
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
pos = nx.spring_layout(graph)
|
|
55
56
|
kwargs = {}
|
|
57
|
+
|
|
56
58
|
if target:
|
|
57
59
|
edge_colors = []
|
|
58
60
|
descendents = nx.descendants(graph, target)
|
|
@@ -66,21 +68,73 @@ class GraphHook(BaseHook):
|
|
|
66
68
|
else:
|
|
67
69
|
edge_colors.append("black")
|
|
68
70
|
kwargs["edge_color"] = edge_colors
|
|
71
|
+
|
|
72
|
+
# Draw the graph without labels first
|
|
69
73
|
nx.draw(
|
|
70
74
|
graph,
|
|
71
75
|
pos=pos,
|
|
72
76
|
node_color=color_map,
|
|
73
77
|
connectionstyle="arc3, rad = 0.1",
|
|
78
|
+
with_labels=False, # Important: don't draw labels with nx.draw
|
|
74
79
|
**kwargs
|
|
75
|
-
)
|
|
76
|
-
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Draw labels with manual spacing
|
|
83
|
+
self._draw_labels_with_manual_spacing(graph, pos)
|
|
84
|
+
|
|
85
|
+
plt.show()
|
|
86
|
+
|
|
87
|
+
def _draw_labels_with_manual_spacing(self, graph, pos):
|
|
88
|
+
"""Fallback method for manual label spacing when adjustText is not available"""
|
|
89
|
+
import numpy as np
|
|
90
|
+
|
|
77
91
|
pos_labels = {}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
92
|
+
node_positions = list(pos.values())
|
|
93
|
+
|
|
94
|
+
# Calculate average distance between nodes to determine spacing
|
|
95
|
+
if len(node_positions) > 1:
|
|
96
|
+
distances = []
|
|
97
|
+
for i, (x1, y1) in enumerate(node_positions):
|
|
98
|
+
for j, (x2, y2) in enumerate(node_positions[i + 1 :], i + 1):
|
|
99
|
+
dist = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
|
100
|
+
distances.append(dist)
|
|
101
|
+
|
|
102
|
+
avg_distance = np.mean(distances)
|
|
103
|
+
min_spacing = max(
|
|
104
|
+
0.1, avg_distance * 0.3
|
|
105
|
+
) # Minimum spacing as fraction of average distance
|
|
106
|
+
else:
|
|
107
|
+
min_spacing = 0.1
|
|
108
|
+
|
|
109
|
+
# Simple spacing algorithm - offset labels that are too close
|
|
110
|
+
for i, node in enumerate(graph.nodes()):
|
|
111
|
+
x, y = pos[node]
|
|
112
|
+
|
|
113
|
+
# Check for nearby labels and adjust position
|
|
114
|
+
adjusted_x, adjusted_y = x, y
|
|
115
|
+
for j, other_node in enumerate(
|
|
116
|
+
list(graph.nodes())[:i]
|
|
117
|
+
): # Only check previous nodes
|
|
118
|
+
other_x, other_y = pos_labels.get(other_node, pos[other_node])
|
|
119
|
+
distance = np.sqrt(
|
|
120
|
+
(adjusted_x - other_x) ** 2 + (adjusted_y - other_y) ** 2
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if distance < min_spacing:
|
|
124
|
+
# Calculate offset direction
|
|
125
|
+
if distance > 0:
|
|
126
|
+
offset_x = (adjusted_x - other_x) / distance * min_spacing
|
|
127
|
+
offset_y = (adjusted_y - other_y) / distance * min_spacing
|
|
128
|
+
else:
|
|
129
|
+
# If nodes are at exact same position, use random offset
|
|
130
|
+
angle = np.random.random() * 2 * np.pi
|
|
131
|
+
offset_x = np.cos(angle) * min_spacing
|
|
132
|
+
offset_y = np.sin(angle) * min_spacing
|
|
133
|
+
|
|
134
|
+
adjusted_x = other_x + offset_x
|
|
135
|
+
adjusted_y = other_y + offset_y
|
|
136
|
+
|
|
137
|
+
pos_labels[node] = (adjusted_x, adjusted_y)
|
|
138
|
+
|
|
139
|
+
# Draw the labels at adjusted positions
|
|
85
140
|
nx.draw_networkx_labels(graph, pos=pos_labels, font_size=10)
|
|
86
|
-
plt.show()
|
trilogy/parsing/common.py
CHANGED
|
@@ -86,7 +86,7 @@ def process_function_arg(
|
|
|
86
86
|
if concept.metadata and meta:
|
|
87
87
|
concept.metadata.line_number = meta.line
|
|
88
88
|
environment.add_concept(concept, meta=meta)
|
|
89
|
-
return concept
|
|
89
|
+
return concept.reference
|
|
90
90
|
elif isinstance(
|
|
91
91
|
arg,
|
|
92
92
|
(ListWrapper, MapWrapper),
|
|
@@ -103,7 +103,7 @@ def process_function_arg(
|
|
|
103
103
|
if concept.metadata and meta:
|
|
104
104
|
concept.metadata.line_number = meta.line
|
|
105
105
|
environment.add_concept(concept, meta=meta)
|
|
106
|
-
return concept
|
|
106
|
+
return concept.reference
|
|
107
107
|
elif isinstance(arg, Concept):
|
|
108
108
|
return arg.reference
|
|
109
109
|
elif isinstance(arg, ConceptRef):
|
trilogy/parsing/render.py
CHANGED
|
@@ -506,7 +506,11 @@ class Renderer:
|
|
|
506
506
|
return f"{args[0]} % {args[1]}"
|
|
507
507
|
if arg.operator == FunctionType.PARENTHETICAL:
|
|
508
508
|
return f"({args[0]})"
|
|
509
|
-
|
|
509
|
+
if arg.operator == FunctionType.GROUP:
|
|
510
|
+
arg_string = ", ".join(args[1:])
|
|
511
|
+
if len(args) == 1:
|
|
512
|
+
return f"group({args[0]})"
|
|
513
|
+
return f"group({args[0]}) by {arg_string}"
|
|
510
514
|
inputs = ",".join(args)
|
|
511
515
|
|
|
512
516
|
if arg.operator == FunctionType.CONSTANT:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|