pytrilogy 0.0.3.32__tar.gz → 0.0.3.34__tar.gz
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.32/pytrilogy.egg-info → pytrilogy-0.0.3.34}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_query_processing.py +3 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_user_functions.py +66 -3
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/authoring/__init__.py +6 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/enums.py +1 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/models/author.py +19 -6
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/models/build.py +1 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/models/build_environment.py +6 -13
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/models/environment.py +2 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/models/execute.py +9 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/optimization.py +8 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/concept_strategies_v3.py +63 -3
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/group_node.py +3 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/rowset_node.py +5 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/select_merge_node.py +2 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/group_node.py +58 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/merge_node.py +13 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/utility.py +11 -5
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/statements/author.py +5 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parsing/common.py +78 -29
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parsing/parse_engine.py +33 -2
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parsing/render.py +6 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parsing/trilogy.lark +5 -1
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/README.md +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/setup.cfg +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/setup.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/models/core.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/base.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/std/dashboard.preql +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/utility.py +0 -0
|
@@ -164,7 +164,9 @@ def test_query_aggregation(test_environment, test_environment_graph):
|
|
|
164
164
|
select = SelectStatement(selection=[test_environment.concepts["total_revenue"]])
|
|
165
165
|
datasource = get_query_datasources(environment=test_environment, statement=select)
|
|
166
166
|
|
|
167
|
-
assert {datasource.identifier} == {
|
|
167
|
+
assert {datasource.identifier} == {
|
|
168
|
+
"revenue_at_local_order_id_grouped_by__at_abstract"
|
|
169
|
+
}
|
|
168
170
|
check = datasource
|
|
169
171
|
assert len(check.input_concepts) == 2
|
|
170
172
|
assert check.input_concepts[0].name == "revenue"
|
|
@@ -119,9 +119,6 @@ select rowset.total;
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
def test_user_function_case():
|
|
122
|
-
from trilogy.hooks.query_debugger import DebuggingHook
|
|
123
|
-
|
|
124
|
-
DebuggingHook()
|
|
125
122
|
x = Dialects.DUCK_DB.default_executor()
|
|
126
123
|
|
|
127
124
|
results = x.execute_query(
|
|
@@ -228,3 +225,69 @@ select
|
|
|
228
225
|
)
|
|
229
226
|
results = results.fetchall()
|
|
230
227
|
assert results[0].quad_test == 16.414213562373096
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_user_function_nesting():
|
|
231
|
+
x = Dialects.DUCK_DB.default_executor()
|
|
232
|
+
|
|
233
|
+
results = x.execute_query(
|
|
234
|
+
"""
|
|
235
|
+
key x int;
|
|
236
|
+
key y int;
|
|
237
|
+
property x.price float;
|
|
238
|
+
|
|
239
|
+
datasource raw_data (
|
|
240
|
+
x: x,
|
|
241
|
+
price: price
|
|
242
|
+
)
|
|
243
|
+
grain (x)
|
|
244
|
+
query '''
|
|
245
|
+
select 1 as x, 2.0 as price
|
|
246
|
+
union all
|
|
247
|
+
select 2 as x, 3.0 as price
|
|
248
|
+
union all
|
|
249
|
+
select 10 as x, 5.0 as price
|
|
250
|
+
''';
|
|
251
|
+
|
|
252
|
+
datasource join_x_y (
|
|
253
|
+
x: x,
|
|
254
|
+
y: y)
|
|
255
|
+
grain (x, y)
|
|
256
|
+
query '''
|
|
257
|
+
select 1 as x, 1 as y
|
|
258
|
+
union all
|
|
259
|
+
select 2 as x, 1 as y
|
|
260
|
+
union all
|
|
261
|
+
select 10 as x, 2 as y
|
|
262
|
+
''';
|
|
263
|
+
|
|
264
|
+
def weekday_sales(weekday) ->
|
|
265
|
+
SUM(CASE WHEN 10 = weekday THEN x ELSE 0 END) +
|
|
266
|
+
SUM(CASE WHEN 10 = weekday THEN price ELSE 0.0 END)
|
|
267
|
+
;
|
|
268
|
+
|
|
269
|
+
def plus_two(a) -> a + 2;
|
|
270
|
+
|
|
271
|
+
# auto random_no_f <- SUM(CASE WHEN 10 = weekday THEN x ELSE 0 END) + SUM(CASE WHEN 10 = weekday THEN price ELSE 0.0 END) +2;
|
|
272
|
+
auto random_one_f <- @weekday_sales(10) +2;
|
|
273
|
+
auto random <- @plus_two(@weekday_sales(10));
|
|
274
|
+
|
|
275
|
+
"""
|
|
276
|
+
)
|
|
277
|
+
# assert x.environment.concepts['random_no_f'].purpose == Purpose.METRIC, x.environment.concepts['random']
|
|
278
|
+
assert (
|
|
279
|
+
x.environment.concepts["random_one_f"].purpose == Purpose.METRIC
|
|
280
|
+
), x.environment.concepts["random"]
|
|
281
|
+
assert (
|
|
282
|
+
x.environment.concepts["random"].purpose == Purpose.METRIC
|
|
283
|
+
), x.environment.concepts["random"]
|
|
284
|
+
|
|
285
|
+
results = x.execute_query(
|
|
286
|
+
"""select
|
|
287
|
+
y,
|
|
288
|
+
@plus_two(@weekday_sales(10)) -> test2
|
|
289
|
+
order by y asc;"""
|
|
290
|
+
)
|
|
291
|
+
results = results.fetchall()
|
|
292
|
+
assert results[0].test2 == 10
|
|
293
|
+
assert results[1].test2 == 17
|
|
@@ -19,12 +19,15 @@ from trilogy.core.models.author import (
|
|
|
19
19
|
Conditional,
|
|
20
20
|
FilterItem,
|
|
21
21
|
Function,
|
|
22
|
+
FunctionCallWrapper,
|
|
22
23
|
HavingClause,
|
|
23
24
|
MagicConstants,
|
|
24
25
|
Metadata,
|
|
26
|
+
MultiSelectLineage,
|
|
25
27
|
OrderBy,
|
|
26
28
|
OrderItem,
|
|
27
29
|
Parenthetical,
|
|
30
|
+
RowsetItem,
|
|
28
31
|
SubselectComparison,
|
|
29
32
|
WhereClause,
|
|
30
33
|
WindowItem,
|
|
@@ -103,4 +106,7 @@ __all__ = [
|
|
|
103
106
|
"RawSQLStatement",
|
|
104
107
|
"Datasource",
|
|
105
108
|
"DatasourceMetadata",
|
|
109
|
+
"MultiSelectLineage",
|
|
110
|
+
"RowsetItem",
|
|
111
|
+
"FunctionCallWrapper",
|
|
106
112
|
]
|
|
@@ -1089,7 +1089,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1089
1089
|
pseudonyms=self.pseudonyms,
|
|
1090
1090
|
)
|
|
1091
1091
|
|
|
1092
|
-
@
|
|
1092
|
+
@cached_property
|
|
1093
1093
|
def sources(self) -> List["ConceptRef"]:
|
|
1094
1094
|
if self.lineage:
|
|
1095
1095
|
output: List[ConceptRef] = []
|
|
@@ -1326,7 +1326,7 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
1326
1326
|
|
|
1327
1327
|
class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
1328
1328
|
type: WindowType
|
|
1329
|
-
content:
|
|
1329
|
+
content: FuncArgs
|
|
1330
1330
|
order_by: List["OrderItem"]
|
|
1331
1331
|
over: List["ConceptRef"] = Field(default_factory=list)
|
|
1332
1332
|
index: Optional[int] = None
|
|
@@ -1335,7 +1335,7 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1335
1335
|
return self.__repr__()
|
|
1336
1336
|
|
|
1337
1337
|
def __repr__(self):
|
|
1338
|
-
return f"{self.type}
|
|
1338
|
+
return f"{self.type.value} {self.content} by {self.index} over {self.over} order {self.order_by}"
|
|
1339
1339
|
|
|
1340
1340
|
@field_validator("content", mode="before")
|
|
1341
1341
|
def enforce_concept_ref(cls, v):
|
|
@@ -1358,7 +1358,11 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1358
1358
|
) -> "WindowItem":
|
|
1359
1359
|
output = WindowItem.model_construct(
|
|
1360
1360
|
type=self.type,
|
|
1361
|
-
content=
|
|
1361
|
+
content=(
|
|
1362
|
+
self.content.with_merge(source, target, modifiers)
|
|
1363
|
+
if isinstance(self.content, Mergeable)
|
|
1364
|
+
else self.content
|
|
1365
|
+
),
|
|
1362
1366
|
over=[x.with_merge(source, target, modifiers) for x in self.over],
|
|
1363
1367
|
order_by=[x.with_merge(source, target, modifiers) for x in self.order_by],
|
|
1364
1368
|
index=self.index,
|
|
@@ -1379,7 +1383,11 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1379
1383
|
def with_namespace(self, namespace: str) -> "WindowItem":
|
|
1380
1384
|
return WindowItem.model_construct(
|
|
1381
1385
|
type=self.type,
|
|
1382
|
-
content=
|
|
1386
|
+
content=(
|
|
1387
|
+
self.content.with_namespace(namespace)
|
|
1388
|
+
if isinstance(self.content, Namespaced)
|
|
1389
|
+
else self.content
|
|
1390
|
+
),
|
|
1383
1391
|
over=[x.with_namespace(namespace) for x in self.over],
|
|
1384
1392
|
order_by=[x.with_namespace(namespace) for x in self.order_by],
|
|
1385
1393
|
index=self.index,
|
|
@@ -1387,7 +1395,8 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1387
1395
|
|
|
1388
1396
|
@property
|
|
1389
1397
|
def concept_arguments(self) -> List[ConceptRef]:
|
|
1390
|
-
output = [
|
|
1398
|
+
output = []
|
|
1399
|
+
output += get_concept_arguments(self.content)
|
|
1391
1400
|
for order in self.order_by:
|
|
1392
1401
|
output += get_concept_arguments(order)
|
|
1393
1402
|
for item in self.over:
|
|
@@ -1787,6 +1796,10 @@ class FunctionCallWrapper(
|
|
|
1787
1796
|
],
|
|
1788
1797
|
)
|
|
1789
1798
|
|
|
1799
|
+
def with_reference_replacement(self, source, target):
|
|
1800
|
+
raise NotImplementedError("Cannot reference replace")
|
|
1801
|
+
return self
|
|
1802
|
+
|
|
1790
1803
|
def with_merge(
|
|
1791
1804
|
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
1792
1805
|
) -> "FunctionCallWrapper":
|
|
@@ -1646,7 +1646,7 @@ class Factory:
|
|
|
1646
1646
|
|
|
1647
1647
|
@build.register
|
|
1648
1648
|
def _(self, base: WindowItem) -> BuildWindowItem:
|
|
1649
|
-
|
|
1649
|
+
# to do proper discovery, we need to inject virtual intermediate ocncepts
|
|
1650
1650
|
return BuildWindowItem.model_construct(
|
|
1651
1651
|
type=base.type,
|
|
1652
1652
|
content=self.build(base.content),
|
|
@@ -31,19 +31,12 @@ class BuildEnvironmentConceptDict(dict):
|
|
|
31
31
|
def raise_undefined(
|
|
32
32
|
self, key: str, line_no: int | None = None, file: Path | str | None = None
|
|
33
33
|
) -> Never:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
message
|
|
39
|
-
|
|
40
|
-
if line_no:
|
|
41
|
-
if file:
|
|
42
|
-
raise UndefinedConceptException(
|
|
43
|
-
f"{file}: {line_no}: " + message, matches
|
|
44
|
-
)
|
|
45
|
-
raise UndefinedConceptException(f"line: {line_no}: " + message, matches)
|
|
46
|
-
raise UndefinedConceptException(message, matches)
|
|
34
|
+
# build environment should never check for missing values.
|
|
35
|
+
if line_no is not None:
|
|
36
|
+
message = f"Concept '{key}' not found in environment at line {line_no}."
|
|
37
|
+
else:
|
|
38
|
+
message = f"Concept '{key}' not found in environment."
|
|
39
|
+
raise UndefinedConceptException(message, [])
|
|
47
40
|
|
|
48
41
|
def __getitem__(
|
|
49
42
|
self, key: str, line_no: int | None = None, file: Path | None = None
|
|
@@ -686,7 +686,8 @@ class Environment(BaseModel):
|
|
|
686
686
|
replacements[k] = target
|
|
687
687
|
# we need to update keys and grains of all concepts
|
|
688
688
|
else:
|
|
689
|
-
|
|
689
|
+
if source.address in v.sources or source.address in v.grain.components:
|
|
690
|
+
replacements[k] = v.with_merge(source, target, modifiers)
|
|
690
691
|
self.concepts.update(replacements)
|
|
691
692
|
for k, ds in self.datasources.items():
|
|
692
693
|
if source.address in ds.output_lcl:
|
|
@@ -282,6 +282,7 @@ class CTE(BaseModel):
|
|
|
282
282
|
**self.existence_source_map,
|
|
283
283
|
**other.existence_source_map,
|
|
284
284
|
}
|
|
285
|
+
|
|
285
286
|
return self
|
|
286
287
|
|
|
287
288
|
@property
|
|
@@ -637,7 +638,7 @@ class QueryDatasource(BaseModel):
|
|
|
637
638
|
and CONFIG.validate_missing
|
|
638
639
|
):
|
|
639
640
|
raise SyntaxError(
|
|
640
|
-
f"On query datasource missing source map for {concept.address} on {key}, have {v}"
|
|
641
|
+
f"On query datasource missing source map for {concept.address} on {key} with pseudonyms {concept.pseudonyms}, have {v}"
|
|
641
642
|
)
|
|
642
643
|
return v
|
|
643
644
|
|
|
@@ -764,8 +765,15 @@ class QueryDatasource(BaseModel):
|
|
|
764
765
|
def identifier(self) -> str:
|
|
765
766
|
filters = abs(hash(str(self.condition))) if self.condition else ""
|
|
766
767
|
grain = "_".join([str(c).replace(".", "_") for c in self.grain.components])
|
|
768
|
+
group = ""
|
|
769
|
+
if self.source_type == SourceType.GROUP:
|
|
770
|
+
keys = [
|
|
771
|
+
x.address for x in self.output_concepts if x.purpose != Purpose.METRIC
|
|
772
|
+
]
|
|
773
|
+
group = "_grouped_by_" + "_".join(keys)
|
|
767
774
|
return (
|
|
768
775
|
"_join_".join([d.identifier for d in self.datasources])
|
|
776
|
+
+ group
|
|
769
777
|
+ (f"_at_{grain}" if grain else "_at_abstract")
|
|
770
778
|
+ (f"_filtered_by_{filters}" if filters else "")
|
|
771
779
|
)
|
|
@@ -94,6 +94,11 @@ def filter_irrelevant_ctes(
|
|
|
94
94
|
inverse_map = gen_inverse_map(input)
|
|
95
95
|
recurse(root_cte, inverse_map)
|
|
96
96
|
final = [cte for cte in input if cte.name in relevant_ctes]
|
|
97
|
+
filtered = [cte for cte in input if cte.name not in relevant_ctes]
|
|
98
|
+
if filtered:
|
|
99
|
+
logger.info(
|
|
100
|
+
f"[Optimization][Irrelevent CTE filtering] Removing redundant CTEs {[x.name for x in filtered]}"
|
|
101
|
+
)
|
|
97
102
|
if len(final) == len(input):
|
|
98
103
|
return input
|
|
99
104
|
return filter_irrelevant_ctes(final, root_cte)
|
|
@@ -130,6 +135,9 @@ def is_direct_return_eligible(cte: CTE | UnionCTE) -> CTE | UnionCTE | None:
|
|
|
130
135
|
if not output_addresses.issubset(parent_output_addresses):
|
|
131
136
|
return None
|
|
132
137
|
if not direct_parent.grain == cte.grain:
|
|
138
|
+
logger.info("grain mismatch, cannot early exit")
|
|
139
|
+
logger.info(direct_parent.grain)
|
|
140
|
+
logger.info(cte.grain)
|
|
133
141
|
return None
|
|
134
142
|
|
|
135
143
|
assert isinstance(cte, CTE)
|
|
@@ -625,6 +625,7 @@ def validate_stack(
|
|
|
625
625
|
for concept in resolved.output_concepts:
|
|
626
626
|
if concept.address in resolved.hidden_concepts:
|
|
627
627
|
continue
|
|
628
|
+
|
|
628
629
|
validate_concept(
|
|
629
630
|
concept,
|
|
630
631
|
node,
|
|
@@ -773,6 +774,8 @@ def _search_concepts(
|
|
|
773
774
|
) -> StrategyNode | None:
|
|
774
775
|
# these are the concepts we need in the output projection
|
|
775
776
|
mandatory_list = unique(mandatory_list, "address")
|
|
777
|
+
# cache our values before an filter injection
|
|
778
|
+
original_mandatory = [*mandatory_list]
|
|
776
779
|
for x in mandatory_list:
|
|
777
780
|
if isinstance(x, UndefinedConcept):
|
|
778
781
|
raise SyntaxError(f"Undefined concept {x.address}")
|
|
@@ -857,7 +860,7 @@ def _search_concepts(
|
|
|
857
860
|
and priority_concept.address not in conditions.row_arguments
|
|
858
861
|
):
|
|
859
862
|
logger.info(
|
|
860
|
-
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Force including conditions to push filtering above complex condition that is not condition member or parent"
|
|
863
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Force including conditions in {priority_concept.address} to push filtering above complex condition that is not condition member or parent"
|
|
861
864
|
)
|
|
862
865
|
local_conditions = conditions
|
|
863
866
|
|
|
@@ -921,7 +924,7 @@ def _search_concepts(
|
|
|
921
924
|
mandatory_completion = [c.address for c in completion_mandatory]
|
|
922
925
|
logger.info(
|
|
923
926
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} finished concept loop for {priority_concept} {priority_concept.derivation} condition {conditions} flag for accepting partial addresses is"
|
|
924
|
-
f" {accept_partial} (complete: {complete}), have {found} from {[n for n in stack]} (missing {missing} partial {partial} virtual {virtual}), attempted {attempted}, mandatory w/ filter {mandatory_completion}"
|
|
927
|
+
f" {accept_partial} (complete: {complete}), have {found} from {[n for n in stack]} (missing {missing} synonyms partial {partial} virtual {virtual}), attempted {attempted}, mandatory w/ filter {mandatory_completion}"
|
|
925
928
|
)
|
|
926
929
|
if complete == ValidationResult.INCOMPLETE_CONDITION:
|
|
927
930
|
cond_dict = {str(node): node.preexisting_conditions for node in stack}
|
|
@@ -933,7 +936,23 @@ def _search_concepts(
|
|
|
933
936
|
if complete == ValidationResult.COMPLETE and (
|
|
934
937
|
not accept_partial or (accept_partial and not partial)
|
|
935
938
|
):
|
|
939
|
+
logger.info(
|
|
940
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} breaking loop, complete"
|
|
941
|
+
)
|
|
936
942
|
break
|
|
943
|
+
elif complete == ValidationResult.COMPLETE and accept_partial and partial:
|
|
944
|
+
if len(attempted) == len(mandatory_list):
|
|
945
|
+
logger.info(
|
|
946
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Breaking as we have attempted all nodes"
|
|
947
|
+
)
|
|
948
|
+
break
|
|
949
|
+
logger.info(
|
|
950
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Found complete stack with partials {partial}, continuing search, attempted {attempted} all {len(mandatory_list)}"
|
|
951
|
+
)
|
|
952
|
+
else:
|
|
953
|
+
logger.info(
|
|
954
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Not complete, continuing search"
|
|
955
|
+
)
|
|
937
956
|
# if we have attempted on root node, we've tried them all.
|
|
938
957
|
# inject in another search with filter concepts
|
|
939
958
|
if priority_concept.derivation == Derivation.ROOT:
|
|
@@ -948,6 +967,11 @@ def _search_concepts(
|
|
|
948
967
|
if complete == ValidationResult.COMPLETE:
|
|
949
968
|
condition_required = True
|
|
950
969
|
non_virtual = [c for c in completion_mandatory if c.address not in virtual]
|
|
970
|
+
non_virtual_output = [c for c in original_mandatory if c.address not in virtual]
|
|
971
|
+
non_virtual_different = len(completion_mandatory) != len(original_mandatory)
|
|
972
|
+
non_virtual_difference_values = set(
|
|
973
|
+
[x.address for x in completion_mandatory]
|
|
974
|
+
).difference(set([x.address for x in original_mandatory]))
|
|
951
975
|
if not conditions:
|
|
952
976
|
condition_required = False
|
|
953
977
|
non_virtual = [c for c in mandatory_list if c.address not in virtual]
|
|
@@ -966,7 +990,19 @@ def _search_concepts(
|
|
|
966
990
|
)
|
|
967
991
|
if len(stack) == 1:
|
|
968
992
|
output: StrategyNode = stack[0]
|
|
969
|
-
|
|
993
|
+
if non_virtual_different:
|
|
994
|
+
logger.info(
|
|
995
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Found different non-virtual output concepts ({non_virtual_difference_values}), removing condition injected values"
|
|
996
|
+
)
|
|
997
|
+
output.set_output_concepts(
|
|
998
|
+
[
|
|
999
|
+
x
|
|
1000
|
+
for x in output.output_concepts
|
|
1001
|
+
if x.address in non_virtual_output
|
|
1002
|
+
],
|
|
1003
|
+
rebuild=False,
|
|
1004
|
+
)
|
|
1005
|
+
|
|
970
1006
|
logger.info(
|
|
971
1007
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Source stack has single node, returning that {type(output)}"
|
|
972
1008
|
)
|
|
@@ -995,6 +1031,30 @@ def _search_concepts(
|
|
|
995
1031
|
logger.info(
|
|
996
1032
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Graph is connected, returning {type(output)} node partial {[c.address for c in output.partial_concepts]}"
|
|
997
1033
|
)
|
|
1034
|
+
if condition_required and conditions and non_virtual_different:
|
|
1035
|
+
logger.info(
|
|
1036
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Conditions {conditions} were injected, checking if we need a group to restore grain"
|
|
1037
|
+
)
|
|
1038
|
+
result = GroupNode.check_if_required(
|
|
1039
|
+
downstream_concepts=original_mandatory,
|
|
1040
|
+
parents=[output.resolve()],
|
|
1041
|
+
environment=environment,
|
|
1042
|
+
depth=depth,
|
|
1043
|
+
)
|
|
1044
|
+
logger.info(f"gcheck result is {result}")
|
|
1045
|
+
if result.required:
|
|
1046
|
+
logger.info(
|
|
1047
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Adding group node"
|
|
1048
|
+
)
|
|
1049
|
+
return GroupNode(
|
|
1050
|
+
output_concepts=original_mandatory,
|
|
1051
|
+
input_concepts=original_mandatory,
|
|
1052
|
+
environment=environment,
|
|
1053
|
+
parents=[output],
|
|
1054
|
+
partial_concepts=output.partial_concepts,
|
|
1055
|
+
preexisting_conditions=conditions.conditional,
|
|
1056
|
+
depth=depth,
|
|
1057
|
+
)
|
|
998
1058
|
return output
|
|
999
1059
|
|
|
1000
1060
|
# if we can't find it after expanding to a merge, then
|
{pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/group_node.py
RENAMED
|
@@ -149,7 +149,9 @@ def gen_group_node(
|
|
|
149
149
|
g=g,
|
|
150
150
|
depth=depth,
|
|
151
151
|
source_concepts=source_concepts,
|
|
152
|
-
log_lambda=create_log_lambda(
|
|
152
|
+
log_lambda=create_log_lambda(
|
|
153
|
+
LOGGER_PREFIX + f" for {concept.address}", depth, logger
|
|
154
|
+
),
|
|
153
155
|
history=history,
|
|
154
156
|
conditions=conditions,
|
|
155
157
|
)
|
{pytrilogy-0.0.3.32 → pytrilogy-0.0.3.34}/trilogy/core/processing/node_generators/rowset_node.py
RENAMED
|
@@ -117,7 +117,11 @@ def gen_rowset_node(
|
|
|
117
117
|
f"{padding(depth)}{LOGGER_PREFIX} final output is {[x.address for x in node.output_concepts]}"
|
|
118
118
|
)
|
|
119
119
|
if not local_optional or all(
|
|
120
|
-
|
|
120
|
+
(
|
|
121
|
+
x.address in node.output_concepts
|
|
122
|
+
or (z in x.pseudonyms for z in node.output_concepts)
|
|
123
|
+
)
|
|
124
|
+
and x.address not in node.partial_concepts
|
|
121
125
|
for x in local_optional
|
|
122
126
|
):
|
|
123
127
|
logger.info(
|
|
@@ -344,12 +344,14 @@ def create_datasource_node(
|
|
|
344
344
|
for c in datasource.columns
|
|
345
345
|
if not c.is_complete and c.concept.address in all_concepts
|
|
346
346
|
]
|
|
347
|
+
|
|
347
348
|
partial_lcl = LooseBuildConceptList(concepts=partial_concepts)
|
|
348
349
|
nullable_concepts = [
|
|
349
350
|
c.concept
|
|
350
351
|
for c in datasource.columns
|
|
351
352
|
if c.is_nullable and c.concept.address in all_concepts
|
|
352
353
|
]
|
|
354
|
+
|
|
353
355
|
nullable_lcl = LooseBuildConceptList(concepts=nullable_concepts)
|
|
354
356
|
partial_is_full = conditions and (conditions == datasource.non_partial_for)
|
|
355
357
|
|
|
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
from typing import List, Optional
|
|
3
3
|
|
|
4
4
|
from trilogy.constants import logger
|
|
5
|
-
from trilogy.core.enums import SourceType
|
|
5
|
+
from trilogy.core.enums import Purpose, SourceType
|
|
6
6
|
from trilogy.core.models.build import (
|
|
7
7
|
BuildComparison,
|
|
8
8
|
BuildConcept,
|
|
@@ -78,7 +78,9 @@ class GroupNode(StrategyNode):
|
|
|
78
78
|
downstream_concepts: List[BuildConcept],
|
|
79
79
|
parents: list[QueryDatasource | BuildDatasource],
|
|
80
80
|
environment: BuildEnvironment,
|
|
81
|
+
depth: int = 0,
|
|
81
82
|
) -> GroupRequiredResponse:
|
|
83
|
+
padding = "\t" * depth
|
|
82
84
|
target_grain = BuildGrain.from_concepts(
|
|
83
85
|
downstream_concepts,
|
|
84
86
|
environment=environment,
|
|
@@ -95,12 +97,67 @@ class GroupNode(StrategyNode):
|
|
|
95
97
|
lookups: list[BuildConcept | str] = [
|
|
96
98
|
concept_map[x] if x in concept_map else x for x in comp_grain.components
|
|
97
99
|
]
|
|
100
|
+
|
|
98
101
|
comp_grain = BuildGrain.from_concepts(lookups, environment=environment)
|
|
102
|
+
|
|
99
103
|
# dynamically select if we need to group
|
|
100
104
|
# because sometimes, we are already at required grain
|
|
101
105
|
if comp_grain.issubset(target_grain):
|
|
106
|
+
|
|
107
|
+
logger.info(
|
|
108
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, is subset, no grain required"
|
|
109
|
+
)
|
|
102
110
|
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
111
|
+
# find out what extra is in the comp grain vs target grain
|
|
112
|
+
difference = [
|
|
113
|
+
environment.concepts[c] for c in (comp_grain - target_grain).components
|
|
114
|
+
]
|
|
115
|
+
logger.info(
|
|
116
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, difference {difference}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# if the difference is all unique properties whose keys are in the source grain
|
|
120
|
+
# we can also suppress the group
|
|
121
|
+
if all(
|
|
122
|
+
[
|
|
123
|
+
x.purpose == Purpose.UNIQUE_PROPERTY
|
|
124
|
+
and x.keys
|
|
125
|
+
and all(z in comp_grain.components for z in x.keys)
|
|
126
|
+
for x in difference
|
|
127
|
+
]
|
|
128
|
+
):
|
|
129
|
+
logger.info(
|
|
130
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: skipped due to unique property validation"
|
|
131
|
+
)
|
|
132
|
+
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
133
|
+
if all([x.purpose == Purpose.KEY for x in difference]):
|
|
134
|
+
logger.info(
|
|
135
|
+
f"{padding}{LOGGER_PREFIX} checking if downstream is unique properties of key"
|
|
136
|
+
)
|
|
137
|
+
replaced_grain_raw: list[set[str]] = [
|
|
138
|
+
(
|
|
139
|
+
x.keys or set()
|
|
140
|
+
if x.purpose == Purpose.UNIQUE_PROPERTY
|
|
141
|
+
else set([x.address])
|
|
142
|
+
)
|
|
143
|
+
for x in downstream_concepts
|
|
144
|
+
if x.address in target_grain.components
|
|
145
|
+
]
|
|
146
|
+
# flatten the list of lists
|
|
147
|
+
replaced_grain = [
|
|
148
|
+
item for sublist in replaced_grain_raw for item in sublist
|
|
149
|
+
]
|
|
150
|
+
# if the replaced grain is a subset of the comp grain, we can skip the group
|
|
151
|
+
unique_grain_comp = BuildGrain.from_concepts(
|
|
152
|
+
replaced_grain, environment=environment
|
|
153
|
+
)
|
|
154
|
+
if comp_grain.issubset(unique_grain_comp):
|
|
155
|
+
logger.info(
|
|
156
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: skipped due to unique property validation"
|
|
157
|
+
)
|
|
158
|
+
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
103
159
|
|
|
160
|
+
logger.info(f"{padding}{LOGGER_PREFIX} Group requirement check: group required")
|
|
104
161
|
return GroupRequiredResponse(target_grain, comp_grain, True)
|
|
105
162
|
|
|
106
163
|
def _resolve(self) -> QueryDatasource:
|
|
@@ -296,9 +296,21 @@ class MergeNode(StrategyNode):
|
|
|
296
296
|
return dataset
|
|
297
297
|
|
|
298
298
|
pregrain = BuildGrain()
|
|
299
|
+
|
|
299
300
|
for source in final_datasets:
|
|
301
|
+
if all(
|
|
302
|
+
[x.address in self.existence_concepts for x in source.output_concepts]
|
|
303
|
+
):
|
|
304
|
+
logger.info(
|
|
305
|
+
f"{self.logging_prefix}{LOGGER_PREFIX} skipping existence only source with {source.output_concepts} from grain accumulation"
|
|
306
|
+
)
|
|
307
|
+
continue
|
|
300
308
|
pregrain += source.grain
|
|
301
309
|
|
|
310
|
+
pregrain = BuildGrain.from_concepts(
|
|
311
|
+
pregrain.components, environment=self.environment
|
|
312
|
+
)
|
|
313
|
+
|
|
302
314
|
grain = self.grain if self.grain else pregrain
|
|
303
315
|
logger.info(
|
|
304
316
|
f"{self.logging_prefix}{LOGGER_PREFIX} has pre grain {pregrain} and final merge node grain {grain}"
|
|
@@ -310,6 +322,7 @@ class MergeNode(StrategyNode):
|
|
|
310
322
|
)
|
|
311
323
|
else:
|
|
312
324
|
joins = []
|
|
325
|
+
|
|
313
326
|
logger.info(
|
|
314
327
|
f"{self.logging_prefix}{LOGGER_PREFIX} Final join count for CTE parent count {len(join_candidates)} is {len(joins)}"
|
|
315
328
|
)
|
|
@@ -343,7 +356,6 @@ class MergeNode(StrategyNode):
|
|
|
343
356
|
nullable_concepts = find_nullable_concepts(
|
|
344
357
|
source_map=source_map, joins=joins, datasources=final_datasets
|
|
345
358
|
)
|
|
346
|
-
|
|
347
359
|
qds = QueryDatasource(
|
|
348
360
|
input_concepts=unique(self.input_concepts, "address"),
|
|
349
361
|
output_concepts=unique(self.output_concepts, "address"),
|