pytrilogy 0.0.3.31__tar.gz → 0.0.3.33__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.31/pytrilogy.egg-info → pytrilogy-0.0.3.33}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_environment.py +57 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_user_functions.py +66 -3
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/enums.py +1 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/environment_helpers.py +25 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/models/author.py +25 -7
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/models/build.py +1 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/models/environment.py +1 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/models/execute.py +1 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/optimization.py +8 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/concept_strategies_v3.py +46 -2
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/group_node.py +3 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/rowset_node.py +5 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/group_node.py +58 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/merge_node.py +13 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/duckdb.py +1 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parsing/common.py +78 -29
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parsing/parse_engine.py +9 -2
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parsing/trilogy.lark +2 -1
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/README.md +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/setup.cfg +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/setup.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/authoring/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/models/core.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/statements/author.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/base.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/std/dashboard.preql +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/trilogy/utility.py +0 -0
|
@@ -13,6 +13,63 @@ from trilogy.core.models.core import DataType
|
|
|
13
13
|
from trilogy.core.models.environment import Environment, LazyEnvironment
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def test_concept_rehydration():
|
|
17
|
+
test = {
|
|
18
|
+
"name": "order_timestamp.month_start",
|
|
19
|
+
"datatype": "date",
|
|
20
|
+
"purpose": "property",
|
|
21
|
+
"derivation": "root",
|
|
22
|
+
"granularity": "multi_row",
|
|
23
|
+
"metadata": {
|
|
24
|
+
"description": "Auto-derived from a local.order_timestamp. The date truncated to the month.",
|
|
25
|
+
"line_number": None,
|
|
26
|
+
"concept_source": "auto_derived",
|
|
27
|
+
},
|
|
28
|
+
"lineage": {
|
|
29
|
+
"operator": "date_truncate",
|
|
30
|
+
"arg_count": 2,
|
|
31
|
+
"output_datatype": "date",
|
|
32
|
+
"output_purpose": "property",
|
|
33
|
+
"valid_inputs": [
|
|
34
|
+
["datetime", "string", "timestamp", "date"],
|
|
35
|
+
["date_part"],
|
|
36
|
+
],
|
|
37
|
+
"arguments": [
|
|
38
|
+
{
|
|
39
|
+
"address": "local.order_timestamp",
|
|
40
|
+
"datatype": "timestamp",
|
|
41
|
+
"metadata": {
|
|
42
|
+
"description": None,
|
|
43
|
+
"line_number": None,
|
|
44
|
+
"concept_source": "manual",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
"month",
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
"namespace": "local",
|
|
51
|
+
"keys": ["local.order_timestamp"],
|
|
52
|
+
"grain": {"components": ["local.order_id"], "where_clause": None},
|
|
53
|
+
"modifiers": [],
|
|
54
|
+
"pseudonyms": [],
|
|
55
|
+
}
|
|
56
|
+
Concept.model_validate(test)
|
|
57
|
+
test["lineage"]["arguments"] = [
|
|
58
|
+
{
|
|
59
|
+
"address": "local.order_timestamp",
|
|
60
|
+
"datatype": "timestamp",
|
|
61
|
+
"metadata": {
|
|
62
|
+
"description": None,
|
|
63
|
+
"line_number": None,
|
|
64
|
+
"concept_source": "manual",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
"monthz",
|
|
68
|
+
]
|
|
69
|
+
with raises(TypeError):
|
|
70
|
+
Concept.model_validate(test)
|
|
71
|
+
|
|
72
|
+
|
|
16
73
|
def test_environment_serialization(test_environment: Environment):
|
|
17
74
|
|
|
18
75
|
path = test_environment.to_cache()
|
|
@@ -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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from trilogy.constants import DEFAULT_NAMESPACE
|
|
2
|
-
from trilogy.core.enums import ConceptSource, FunctionType, Purpose
|
|
2
|
+
from trilogy.core.enums import ConceptSource, DatePart, FunctionType, Purpose
|
|
3
3
|
from trilogy.core.functions import AttrAccess, FunctionFactory
|
|
4
4
|
from trilogy.core.models.author import Concept, Function, Metadata
|
|
5
5
|
from trilogy.core.models.core import DataType, StructType, arg_to_datatype
|
|
@@ -64,6 +64,30 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
64
64
|
if new_concept.name in environment.concepts:
|
|
65
65
|
continue
|
|
66
66
|
environment.add_concept(new_concept, add_derived=False)
|
|
67
|
+
for grain in [DatePart.MONTH, DatePart.YEAR]:
|
|
68
|
+
function = factory.create_function(
|
|
69
|
+
operator=FunctionType.DATE_TRUNCATE,
|
|
70
|
+
args=[concept, grain],
|
|
71
|
+
)
|
|
72
|
+
new_concept = Concept(
|
|
73
|
+
name=f"{concept.name}.{grain.value}_start",
|
|
74
|
+
datatype=DataType.DATE,
|
|
75
|
+
purpose=Purpose.PROPERTY,
|
|
76
|
+
lineage=function,
|
|
77
|
+
grain=concept.grain,
|
|
78
|
+
namespace=concept.namespace,
|
|
79
|
+
keys=set(
|
|
80
|
+
[concept.address],
|
|
81
|
+
),
|
|
82
|
+
metadata=Metadata(
|
|
83
|
+
description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
|
|
84
|
+
line_number=base_line_number,
|
|
85
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
if new_concept.name in environment.concepts:
|
|
89
|
+
continue
|
|
90
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
67
91
|
|
|
68
92
|
|
|
69
93
|
def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
@@ -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:
|
|
@@ -1658,8 +1667,13 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1658
1667
|
# attempt to exit early to avoid checking all types
|
|
1659
1668
|
break
|
|
1660
1669
|
elif isinstance(arg, ptype):
|
|
1670
|
+
if isinstance(arg, str) and DataType.DATE_PART in valid_inputs[idx]:
|
|
1671
|
+
if arg not in [x.value for x in DatePart]:
|
|
1672
|
+
pass
|
|
1673
|
+
else:
|
|
1674
|
+
break
|
|
1661
1675
|
raise TypeError(
|
|
1662
|
-
f
|
|
1676
|
+
f'Invalid {dtype} constant passed into {operator_name} "{arg}", expecting one of {valid_inputs[idx]}'
|
|
1663
1677
|
)
|
|
1664
1678
|
return v
|
|
1665
1679
|
|
|
@@ -1782,6 +1796,10 @@ class FunctionCallWrapper(
|
|
|
1782
1796
|
],
|
|
1783
1797
|
)
|
|
1784
1798
|
|
|
1799
|
+
def with_reference_replacement(self, source, target):
|
|
1800
|
+
raise NotImplementedError("Cannot reference replace")
|
|
1801
|
+
return self
|
|
1802
|
+
|
|
1785
1803
|
def with_merge(
|
|
1786
1804
|
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
1787
1805
|
) -> "FunctionCallWrapper":
|
|
@@ -2362,6 +2380,7 @@ FuncArgs = (
|
|
|
2362
2380
|
| FilterItem
|
|
2363
2381
|
| int
|
|
2364
2382
|
| float
|
|
2383
|
+
| DatePart
|
|
2365
2384
|
| str
|
|
2366
2385
|
| date
|
|
2367
2386
|
| datetime
|
|
@@ -2371,7 +2390,6 @@ FuncArgs = (
|
|
|
2371
2390
|
| ListType
|
|
2372
2391
|
| MapType
|
|
2373
2392
|
| NumericType
|
|
2374
|
-
| DatePart
|
|
2375
2393
|
| list
|
|
2376
2394
|
| ListWrapper[Any]
|
|
2377
2395
|
)
|
|
@@ -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),
|
|
@@ -250,7 +250,7 @@ class Environment(BaseModel):
|
|
|
250
250
|
imports=dict(self.imports),
|
|
251
251
|
namespace=self.namespace,
|
|
252
252
|
working_path=self.working_path,
|
|
253
|
-
environment_config=self.config,
|
|
253
|
+
environment_config=self.config.model_copy(deep=True),
|
|
254
254
|
version=self.version,
|
|
255
255
|
cte_name_map=dict(self.cte_name_map),
|
|
256
256
|
materialized_concepts=set(self.materialized_concepts),
|
|
@@ -637,7 +637,7 @@ class QueryDatasource(BaseModel):
|
|
|
637
637
|
and CONFIG.validate_missing
|
|
638
638
|
):
|
|
639
639
|
raise SyntaxError(
|
|
640
|
-
f"On query datasource missing source map for {concept.address} on {key}, have {v}"
|
|
640
|
+
f"On query datasource missing source map for {concept.address} on {key} with pseudonyms {concept.pseudonyms}, have {v}"
|
|
641
641
|
)
|
|
642
642
|
return v
|
|
643
643
|
|
|
@@ -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}")
|
|
@@ -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}
|
|
@@ -948,6 +951,11 @@ def _search_concepts(
|
|
|
948
951
|
if complete == ValidationResult.COMPLETE:
|
|
949
952
|
condition_required = True
|
|
950
953
|
non_virtual = [c for c in completion_mandatory if c.address not in virtual]
|
|
954
|
+
non_virtual_output = [c for c in original_mandatory if c.address not in virtual]
|
|
955
|
+
non_virtual_different = len(completion_mandatory) != len(original_mandatory)
|
|
956
|
+
non_virtual_difference_values = set(
|
|
957
|
+
[x.address for x in completion_mandatory]
|
|
958
|
+
).difference(set([x.address for x in original_mandatory]))
|
|
951
959
|
if not conditions:
|
|
952
960
|
condition_required = False
|
|
953
961
|
non_virtual = [c for c in mandatory_list if c.address not in virtual]
|
|
@@ -966,7 +974,19 @@ def _search_concepts(
|
|
|
966
974
|
)
|
|
967
975
|
if len(stack) == 1:
|
|
968
976
|
output: StrategyNode = stack[0]
|
|
969
|
-
|
|
977
|
+
if non_virtual_different:
|
|
978
|
+
logger.info(
|
|
979
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Found different non-virtual output concepts ({non_virtual_difference_values}), removing condition injected values"
|
|
980
|
+
)
|
|
981
|
+
output.set_output_concepts(
|
|
982
|
+
[
|
|
983
|
+
x
|
|
984
|
+
for x in output.output_concepts
|
|
985
|
+
if x.address in non_virtual_output
|
|
986
|
+
],
|
|
987
|
+
rebuild=False,
|
|
988
|
+
)
|
|
989
|
+
|
|
970
990
|
logger.info(
|
|
971
991
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Source stack has single node, returning that {type(output)}"
|
|
972
992
|
)
|
|
@@ -995,6 +1015,30 @@ def _search_concepts(
|
|
|
995
1015
|
logger.info(
|
|
996
1016
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Graph is connected, returning {type(output)} node partial {[c.address for c in output.partial_concepts]}"
|
|
997
1017
|
)
|
|
1018
|
+
if condition_required and conditions and non_virtual_different:
|
|
1019
|
+
logger.info(
|
|
1020
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Conditions {conditions} were injected, checking if we need a group to restore grain"
|
|
1021
|
+
)
|
|
1022
|
+
result = GroupNode.check_if_required(
|
|
1023
|
+
downstream_concepts=original_mandatory,
|
|
1024
|
+
parents=[output.resolve()],
|
|
1025
|
+
environment=environment,
|
|
1026
|
+
depth=depth,
|
|
1027
|
+
)
|
|
1028
|
+
logger.info(f"gcheck result is {result}")
|
|
1029
|
+
if result.required:
|
|
1030
|
+
logger.info(
|
|
1031
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Adding group node"
|
|
1032
|
+
)
|
|
1033
|
+
return GroupNode(
|
|
1034
|
+
output_concepts=original_mandatory,
|
|
1035
|
+
input_concepts=original_mandatory,
|
|
1036
|
+
environment=environment,
|
|
1037
|
+
parents=[output],
|
|
1038
|
+
partial_concepts=output.partial_concepts,
|
|
1039
|
+
preexisting_conditions=conditions.conditional,
|
|
1040
|
+
depth=depth,
|
|
1041
|
+
)
|
|
998
1042
|
return output
|
|
999
1043
|
|
|
1000
1044
|
# if we can't find it after expanding to a merge, then
|
{pytrilogy-0.0.3.31 → pytrilogy-0.0.3.33}/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.31 → pytrilogy-0.0.3.33}/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(
|
|
@@ -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"),
|
|
@@ -27,6 +27,7 @@ FUNCTION_MAP = {
|
|
|
27
27
|
FunctionType.INDEX_ACCESS: lambda args: (f"{args[0]}[{args[1]}]"),
|
|
28
28
|
# datetime is aliased
|
|
29
29
|
FunctionType.CURRENT_DATETIME: lambda x: "cast(get_current_timestamp() as datetime)",
|
|
30
|
+
FunctionType.DATE: lambda x: f"cast({x[0]} as date)",
|
|
30
31
|
FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc('{x[1]}', {x[0]})",
|
|
31
32
|
FunctionType.DATE_ADD: lambda x: f"date_add({x[0]}, {x[2]} * INTERVAL 1 {x[1]})",
|
|
32
33
|
FunctionType.DATE_PART: lambda x: f"date_part('{x[1]}', {x[0]})",
|