pytrilogy 0.0.3.45__tar.gz → 0.0.3.47__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.45/pytrilogy.egg-info → pytrilogy-0.0.3.47}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_functions.py +3 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_parsing_failures.py +2 -2
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_partial_handling.py +3 -3
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/environment_helpers.py +2 -27
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/author.py +4 -1
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/build.py +33 -29
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/filter_node.py +31 -16
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/__init__.py +2 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/query_processor.py +1 -2
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/author.py +21 -9
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/base.py +1 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/common.py +74 -16
- pytrilogy-0.0.3.47/trilogy/parsing/exceptions.py +8 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/parse_engine.py +33 -28
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/trilogy.lark +1 -4
- pytrilogy-0.0.3.45/trilogy/parsing/exceptions.py +0 -2
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/README.md +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/setup.cfg +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/setup.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_failure.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_user_functions.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/authoring/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/core.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/environment.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/execute.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/concept_strategies_v3.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/report.preql +0 -0
- {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/utility.py +0 -0
|
@@ -123,6 +123,7 @@ def test_date_functions(test_environment):
|
|
|
123
123
|
year(order_timestamp) -> order_year,
|
|
124
124
|
date_trunc(order_timestamp, month) -> order_month_trunc,
|
|
125
125
|
date_add(order_timestamp, month, 1) -> one_month_post_order,
|
|
126
|
+
date_sub(order_timestamp, month, 1) -> one_month_pre_order,
|
|
126
127
|
date_trunc(order_timestamp, day) -> order_day_trunc,
|
|
127
128
|
date_trunc(order_timestamp, year) -> order_year_trunc,
|
|
128
129
|
date_trunc(order_timestamp, hour) -> order_hour_trunc,
|
|
@@ -212,6 +213,7 @@ def test_math_functions(test_environment):
|
|
|
212
213
|
property order_sub <- revenue - 2;
|
|
213
214
|
property order_id.order_nested <- revenue * 2/2;
|
|
214
215
|
property order_id.rounded <- round(revenue + 2.01,2);
|
|
216
|
+
property order_id.rounded_default <- round(revenue + 2.01);
|
|
215
217
|
constant random <- random(1);
|
|
216
218
|
select
|
|
217
219
|
order_id,
|
|
@@ -221,6 +223,7 @@ def test_math_functions(test_environment):
|
|
|
221
223
|
order_sub,
|
|
222
224
|
order_add,
|
|
223
225
|
rounded,
|
|
226
|
+
rounded_default,
|
|
224
227
|
random,
|
|
225
228
|
;"""
|
|
226
229
|
env, parsed = parse(declarations, environment=test_environment)
|
|
@@ -8,7 +8,7 @@ from trilogy.core.models.environment import (
|
|
|
8
8
|
EnvironmentOptions,
|
|
9
9
|
)
|
|
10
10
|
from trilogy.parsing.parse_engine import (
|
|
11
|
-
|
|
11
|
+
NameShadowError,
|
|
12
12
|
parse_text,
|
|
13
13
|
)
|
|
14
14
|
|
|
@@ -77,7 +77,7 @@ SELECT
|
|
|
77
77
|
1+2->scalar
|
|
78
78
|
;
|
|
79
79
|
"""
|
|
80
|
-
with raises(
|
|
80
|
+
with raises(NameShadowError) as e:
|
|
81
81
|
env, parsed = parse_text(
|
|
82
82
|
x, parse_config=Parsing(strict_name_shadow_enforcement=True)
|
|
83
83
|
)
|
|
@@ -12,7 +12,7 @@ from trilogy.core.processing.concept_strategies_v3 import History, search_concep
|
|
|
12
12
|
from trilogy.core.processing.node_generators import (
|
|
13
13
|
gen_filter_node,
|
|
14
14
|
)
|
|
15
|
-
from trilogy.core.processing.nodes import
|
|
15
|
+
from trilogy.core.processing.nodes import FilterNode
|
|
16
16
|
from trilogy.core.query_processor import generate_graph
|
|
17
17
|
from trilogy.hooks.query_debugger import DebuggingHook
|
|
18
18
|
|
|
@@ -139,8 +139,8 @@ def test_partial_assignment():
|
|
|
139
139
|
g=g,
|
|
140
140
|
depth=0,
|
|
141
141
|
)
|
|
142
|
-
assert isinstance(sourced,
|
|
143
|
-
assert len(sourced.parents) ==
|
|
142
|
+
assert isinstance(sourced, FilterNode)
|
|
143
|
+
assert len(sourced.parents) == 1
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
def test_filter_query():
|
|
@@ -6,23 +6,8 @@ from trilogy.core.models.core import DataType, StructType, arg_to_datatype
|
|
|
6
6
|
from trilogy.core.models.environment import Environment
|
|
7
7
|
from trilogy.parsing.common import Meta
|
|
8
8
|
|
|
9
|
-
FUNCTION_DESCRIPTION_MAPS = {
|
|
10
|
-
FunctionType.DATE: "The date part of a timestamp/date. Integer, 0-31 depending on month.",
|
|
11
|
-
FunctionType.MONTH: "The month part of a timestamp/date. Integer, 1-12.",
|
|
12
|
-
FunctionType.YEAR: "The year part of a timestamp/date. Integer.",
|
|
13
|
-
FunctionType.QUARTER: "The quarter part of a timestamp/date. Integer, 1-4.",
|
|
14
|
-
FunctionType.DAY_OF_WEEK: "The day of the week part of a timestamp/date. Integer, 0-6.",
|
|
15
|
-
FunctionType.HOUR: "The hour part of a timestamp. Integer, 0-23.",
|
|
16
|
-
FunctionType.MINUTE: "The minute part of a timestamp. Integer, 0-59.",
|
|
17
|
-
FunctionType.SECOND: "The second part of a timestamp. Integer, 0-59.",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
9
|
|
|
21
10
|
def generate_date_concepts(concept: Concept, environment: Environment):
|
|
22
|
-
if concept.metadata and concept.metadata.description:
|
|
23
|
-
base_description = concept.metadata.description
|
|
24
|
-
else:
|
|
25
|
-
base_description = f"a {concept.address}"
|
|
26
11
|
if concept.metadata and concept.metadata.line_number:
|
|
27
12
|
base_line_number = concept.metadata.line_number
|
|
28
13
|
else:
|
|
@@ -67,7 +52,6 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
67
52
|
[concept.address],
|
|
68
53
|
),
|
|
69
54
|
metadata=Metadata(
|
|
70
|
-
description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}",
|
|
71
55
|
line_number=base_line_number,
|
|
72
56
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
73
57
|
),
|
|
@@ -95,7 +79,7 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
95
79
|
[concept.address],
|
|
96
80
|
),
|
|
97
81
|
metadata=Metadata(
|
|
98
|
-
description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
|
|
82
|
+
# description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
|
|
99
83
|
line_number=base_line_number,
|
|
100
84
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
101
85
|
),
|
|
@@ -105,10 +89,6 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
105
89
|
|
|
106
90
|
|
|
107
91
|
def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
108
|
-
if concept.metadata and concept.metadata.description:
|
|
109
|
-
base_description = concept.metadata.description
|
|
110
|
-
else:
|
|
111
|
-
base_description = concept.address
|
|
112
92
|
if concept.metadata and concept.metadata.line_number:
|
|
113
93
|
base_line_number = concept.metadata.line_number
|
|
114
94
|
else:
|
|
@@ -146,7 +126,6 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
|
146
126
|
[concept.address],
|
|
147
127
|
),
|
|
148
128
|
metadata=Metadata(
|
|
149
|
-
description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}",
|
|
150
129
|
line_number=base_line_number,
|
|
151
130
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
152
131
|
),
|
|
@@ -157,10 +136,6 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
|
157
136
|
|
|
158
137
|
|
|
159
138
|
def generate_key_concepts(concept: Concept, environment: Environment):
|
|
160
|
-
if concept.metadata and concept.metadata.description:
|
|
161
|
-
base_description = concept.metadata.description
|
|
162
|
-
else:
|
|
163
|
-
base_description = f"a {concept.datatype.value}"
|
|
164
139
|
if concept.metadata and concept.metadata.line_number:
|
|
165
140
|
base_line_number = concept.metadata.line_number
|
|
166
141
|
else:
|
|
@@ -186,7 +161,7 @@ def generate_key_concepts(concept: Concept, environment: Environment):
|
|
|
186
161
|
namespace=concept.namespace,
|
|
187
162
|
keys=set(),
|
|
188
163
|
metadata=Metadata(
|
|
189
|
-
description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
|
|
164
|
+
# description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
|
|
190
165
|
line_number=base_line_number,
|
|
191
166
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
192
167
|
),
|
|
@@ -445,13 +445,16 @@ class Grain(Namespaced, BaseModel):
|
|
|
445
445
|
concepts: Iterable[Concept | ConceptRef | str],
|
|
446
446
|
environment: Environment | None = None,
|
|
447
447
|
where_clause: WhereClause | None = None,
|
|
448
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
448
449
|
) -> Grain:
|
|
449
450
|
from trilogy.parsing.common import concepts_to_grain_concepts
|
|
450
451
|
|
|
451
452
|
x = Grain.model_construct(
|
|
452
453
|
components={
|
|
453
454
|
c.address
|
|
454
|
-
for c in concepts_to_grain_concepts(
|
|
455
|
+
for c in concepts_to_grain_concepts(
|
|
456
|
+
concepts, environment=environment, local_concepts=local_concepts
|
|
457
|
+
)
|
|
455
458
|
},
|
|
456
459
|
where_clause=where_clause,
|
|
457
460
|
)
|
|
@@ -126,9 +126,14 @@ def concept_is_relevant(
|
|
|
126
126
|
|
|
127
127
|
return False
|
|
128
128
|
if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
|
|
129
|
-
if
|
|
130
|
-
|
|
129
|
+
if all([c in others for c in concept.keys]):
|
|
131
130
|
return False
|
|
131
|
+
if (
|
|
132
|
+
concept.purpose == Purpose.KEY
|
|
133
|
+
and concept.keys
|
|
134
|
+
and all([c in others for c in concept.keys])
|
|
135
|
+
):
|
|
136
|
+
return False
|
|
132
137
|
if concept.purpose in (Purpose.METRIC,):
|
|
133
138
|
if all([c in others for c in concept.grain.components]):
|
|
134
139
|
return False
|
|
@@ -1458,11 +1463,35 @@ class Factory:
|
|
|
1458
1463
|
{} if local_concepts is None else local_concepts
|
|
1459
1464
|
)
|
|
1460
1465
|
|
|
1466
|
+
def instantiate_concept(
|
|
1467
|
+
self,
|
|
1468
|
+
arg: (
|
|
1469
|
+
AggregateWrapper
|
|
1470
|
+
| FunctionCallWrapper
|
|
1471
|
+
| WindowItem
|
|
1472
|
+
| FilterItem
|
|
1473
|
+
| Function
|
|
1474
|
+
| ListWrapper[Any]
|
|
1475
|
+
| MapWrapper[Any, Any]
|
|
1476
|
+
| int
|
|
1477
|
+
| float
|
|
1478
|
+
| str
|
|
1479
|
+
),
|
|
1480
|
+
) -> tuple[Concept, BuildConcept]:
|
|
1481
|
+
from trilogy.parsing.common import arbitrary_to_concept
|
|
1482
|
+
|
|
1483
|
+
new = arbitrary_to_concept(
|
|
1484
|
+
arg,
|
|
1485
|
+
environment=self.environment,
|
|
1486
|
+
)
|
|
1487
|
+
built = self.build(new)
|
|
1488
|
+
self.local_concepts[new.address] = built
|
|
1489
|
+
return new, built
|
|
1490
|
+
|
|
1461
1491
|
@singledispatchmethod
|
|
1462
1492
|
def build(self, base):
|
|
1463
1493
|
raise NotImplementedError("Cannot build {}".format(type(base)))
|
|
1464
1494
|
|
|
1465
|
-
@build.register
|
|
1466
1495
|
@build.register
|
|
1467
1496
|
def _(
|
|
1468
1497
|
self,
|
|
@@ -1496,31 +1525,6 @@ class Factory:
|
|
|
1496
1525
|
):
|
|
1497
1526
|
return base
|
|
1498
1527
|
|
|
1499
|
-
def instantiate_concept(
|
|
1500
|
-
self,
|
|
1501
|
-
arg: (
|
|
1502
|
-
AggregateWrapper
|
|
1503
|
-
| FunctionCallWrapper
|
|
1504
|
-
| WindowItem
|
|
1505
|
-
| FilterItem
|
|
1506
|
-
| Function
|
|
1507
|
-
| ListWrapper[Any]
|
|
1508
|
-
| MapWrapper[Any, Any]
|
|
1509
|
-
| int
|
|
1510
|
-
| float
|
|
1511
|
-
| str
|
|
1512
|
-
),
|
|
1513
|
-
) -> tuple[Concept, BuildConcept]:
|
|
1514
|
-
from trilogy.parsing.common import arbitrary_to_concept
|
|
1515
|
-
|
|
1516
|
-
new = arbitrary_to_concept(
|
|
1517
|
-
arg,
|
|
1518
|
-
environment=self.environment,
|
|
1519
|
-
)
|
|
1520
|
-
built = self.build(new)
|
|
1521
|
-
self.local_concepts[new.address] = built
|
|
1522
|
-
return new, built
|
|
1523
|
-
|
|
1524
1528
|
@build.register
|
|
1525
1529
|
def _(self, base: None) -> None:
|
|
1526
1530
|
return base
|
|
@@ -1626,6 +1630,7 @@ class Factory:
|
|
|
1626
1630
|
derivation, final_grain, build_lineage
|
|
1627
1631
|
)
|
|
1628
1632
|
is_aggregate = Concept.calculate_is_aggregate(build_lineage)
|
|
1633
|
+
|
|
1629
1634
|
rval = BuildConcept.model_construct(
|
|
1630
1635
|
name=base.name,
|
|
1631
1636
|
datatype=base.datatype,
|
|
@@ -1646,7 +1651,6 @@ class Factory:
|
|
|
1646
1651
|
|
|
1647
1652
|
@build.register
|
|
1648
1653
|
def _(self, base: AggregateWrapper) -> BuildAggregateWrapper:
|
|
1649
|
-
|
|
1650
1654
|
if not base.by:
|
|
1651
1655
|
by = [
|
|
1652
1656
|
self.build(self.environment.concepts[c]) for c in self.grain.components
|
{pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
@@ -56,12 +56,40 @@ def gen_filter_node(
|
|
|
56
56
|
continue
|
|
57
57
|
if conditions and conditions == where:
|
|
58
58
|
optional_included.append(x)
|
|
59
|
+
|
|
60
|
+
# sometimes, it's okay to include other local optional above the filter
|
|
61
|
+
# in case it is, prep our list
|
|
62
|
+
extra_row_level_optional: list[BuildConcept] = []
|
|
63
|
+
for x in local_optional:
|
|
64
|
+
if x.address in optional_included:
|
|
65
|
+
continue
|
|
66
|
+
extra_row_level_optional.append(x)
|
|
67
|
+
|
|
68
|
+
# this flag controls whether we materialize the filter as a where on the prior CTE
|
|
69
|
+
# or do the filtering inline as a case statement
|
|
70
|
+
optimized_pushdown = False
|
|
71
|
+
if not is_scalar_condition(where.conditional):
|
|
72
|
+
optimized_pushdown = False
|
|
73
|
+
elif not local_optional:
|
|
74
|
+
optimized_pushdown = True
|
|
75
|
+
elif conditions and conditions == where:
|
|
76
|
+
logger.info(
|
|
77
|
+
f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
|
|
78
|
+
)
|
|
79
|
+
optimized_pushdown = True
|
|
80
|
+
elif optional_included == local_optional:
|
|
81
|
+
logger.info(
|
|
82
|
+
f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
|
|
83
|
+
)
|
|
84
|
+
optimized_pushdown = True
|
|
59
85
|
logger.info(
|
|
60
86
|
f"{padding(depth)}{LOGGER_PREFIX} filter `{concept}` condition `{concept.lineage.where}` derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
|
|
61
87
|
)
|
|
62
88
|
# we'll populate this with the row parent
|
|
63
89
|
# and the existence parent(s)
|
|
64
90
|
core_parents = []
|
|
91
|
+
if not optimized_pushdown:
|
|
92
|
+
parent_row_concepts += extra_row_level_optional
|
|
65
93
|
|
|
66
94
|
row_parent: StrategyNode = source_concepts(
|
|
67
95
|
mandatory_list=parent_row_concepts,
|
|
@@ -99,21 +127,6 @@ def gen_filter_node(
|
|
|
99
127
|
)
|
|
100
128
|
return None
|
|
101
129
|
|
|
102
|
-
optimized_pushdown = False
|
|
103
|
-
if not is_scalar_condition(where.conditional):
|
|
104
|
-
optimized_pushdown = False
|
|
105
|
-
elif not local_optional:
|
|
106
|
-
optimized_pushdown = True
|
|
107
|
-
elif conditions and conditions == where:
|
|
108
|
-
logger.info(
|
|
109
|
-
f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
|
|
110
|
-
)
|
|
111
|
-
optimized_pushdown = True
|
|
112
|
-
elif optional_included == local_optional:
|
|
113
|
-
logger.info(
|
|
114
|
-
f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
|
|
115
|
-
)
|
|
116
|
-
optimized_pushdown = True
|
|
117
130
|
if optimized_pushdown:
|
|
118
131
|
logger.info(
|
|
119
132
|
f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional}"
|
|
@@ -193,7 +206,9 @@ def gen_filter_node(
|
|
|
193
206
|
+ optional_outputs
|
|
194
207
|
)
|
|
195
208
|
return filter_node
|
|
196
|
-
|
|
209
|
+
logger.info(
|
|
210
|
+
f"{padding(depth)}{LOGGER_PREFIX} need to enrich filter node with additional concepts {[x.address for x in local_optional if x.address not in filter_node.output_concepts]}"
|
|
211
|
+
)
|
|
197
212
|
enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
|
|
198
213
|
# to then connect to the rest of the query
|
|
199
214
|
mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from pydantic import BaseModel, ConfigDict, Field
|
|
2
2
|
|
|
3
3
|
from trilogy.core.exceptions import UnresolvableQueryException
|
|
4
|
+
from trilogy.core.models.author import Concept
|
|
4
5
|
from trilogy.core.models.build import BuildConcept, BuildWhereClause
|
|
5
6
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
6
7
|
from trilogy.core.models.environment import Environment
|
|
@@ -17,6 +18,7 @@ from .window_node import WindowNode
|
|
|
17
18
|
|
|
18
19
|
class History(BaseModel):
|
|
19
20
|
base_environment: Environment
|
|
21
|
+
local_base_concepts: dict[str, Concept] = Field(default_factory=dict)
|
|
20
22
|
history: dict[str, StrategyNode | None] = Field(default_factory=dict)
|
|
21
23
|
select_history: dict[str, StrategyNode | None] = Field(default_factory=dict)
|
|
22
24
|
started: dict[str, int] = Field(default_factory=dict)
|
|
@@ -373,10 +373,9 @@ def get_query_node(
|
|
|
373
373
|
) -> StrategyNode:
|
|
374
374
|
if not statement.output_components:
|
|
375
375
|
raise ValueError(f"Statement has no output components {statement}")
|
|
376
|
-
|
|
377
376
|
history = history or History(base_environment=environment)
|
|
378
377
|
build_statement: BuildSelectLineage | BuildMultiSelectLineage = Factory(
|
|
379
|
-
environment=environment
|
|
378
|
+
environment=environment,
|
|
380
379
|
).build(statement)
|
|
381
380
|
|
|
382
381
|
# build_statement = statement
|
|
@@ -7,6 +7,7 @@ from pydantic.functional_validators import PlainValidator
|
|
|
7
7
|
|
|
8
8
|
from trilogy.constants import CONFIG
|
|
9
9
|
from trilogy.core.enums import (
|
|
10
|
+
ConceptSource,
|
|
10
11
|
FunctionClass,
|
|
11
12
|
IOType,
|
|
12
13
|
Modifier,
|
|
@@ -134,7 +135,7 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
134
135
|
meta=meta or Metadata(),
|
|
135
136
|
)
|
|
136
137
|
|
|
137
|
-
output.grain = output.calculate_grain(environment)
|
|
138
|
+
output.grain = output.calculate_grain(environment, output.local_concepts)
|
|
138
139
|
|
|
139
140
|
for x in selection:
|
|
140
141
|
if x.is_undefined and environment.concepts.fail_on_missing:
|
|
@@ -144,12 +145,13 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
144
145
|
elif isinstance(x.content, ConceptTransform):
|
|
145
146
|
if isinstance(x.content.output, UndefinedConcept):
|
|
146
147
|
continue
|
|
147
|
-
if
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
if CONFIG.parsing.select_as_definition and not environment.frozen:
|
|
149
|
+
if x.concept.address not in environment.concepts:
|
|
150
|
+
environment.add_concept(x.content.output)
|
|
151
|
+
elif x.concept.address in environment.concepts:
|
|
152
|
+
version = environment.concepts[x.concept.address]
|
|
153
|
+
if version.metadata.concept_source == ConceptSource.SELECT:
|
|
154
|
+
environment.add_concept(x.content.output, force=True)
|
|
153
155
|
x.content.output = x.content.output.set_select_grain(
|
|
154
156
|
output.grain, environment
|
|
155
157
|
)
|
|
@@ -160,16 +162,26 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
160
162
|
output.local_concepts[x.content.address] = environment.concepts[
|
|
161
163
|
x.content.address
|
|
162
164
|
]
|
|
165
|
+
|
|
166
|
+
output.grain = output.calculate_grain(environment, output.local_concepts)
|
|
167
|
+
|
|
163
168
|
output.validate_syntax(environment)
|
|
164
169
|
return output
|
|
165
170
|
|
|
166
|
-
def calculate_grain(
|
|
171
|
+
def calculate_grain(
|
|
172
|
+
self,
|
|
173
|
+
environment: Environment | None = None,
|
|
174
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
175
|
+
) -> Grain:
|
|
167
176
|
targets = []
|
|
168
177
|
for x in self.selection:
|
|
169
178
|
targets.append(x.concept)
|
|
170
179
|
|
|
171
180
|
result = Grain.from_concepts(
|
|
172
|
-
targets,
|
|
181
|
+
targets,
|
|
182
|
+
where_clause=self.where_clause,
|
|
183
|
+
environment=environment,
|
|
184
|
+
local_concepts=local_concepts,
|
|
173
185
|
)
|
|
174
186
|
return result
|
|
175
187
|
|
|
@@ -199,6 +199,7 @@ FUNCTION_MAP = {
|
|
|
199
199
|
FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
|
|
200
200
|
FunctionType.DATE_PART: lambda x: f"date_part({x[0]},{x[1]})",
|
|
201
201
|
FunctionType.DATE_ADD: lambda x: f"date_add({x[0]},{x[1]}, {x[2]})",
|
|
202
|
+
FunctionType.DATE_SUB: lambda x: f"date_sub({x[0]},{x[1]}, {x[2]})",
|
|
202
203
|
FunctionType.DATE_DIFF: lambda x: f"date_diff({x[0]},{x[1]}, {x[2]})",
|
|
203
204
|
FunctionType.DATE: lambda x: f"date({x[0]})",
|
|
204
205
|
FunctionType.DATETIME: lambda x: f"datetime({x[0]})",
|
|
@@ -24,6 +24,7 @@ from trilogy.core.models.author import (
|
|
|
24
24
|
AlignClause,
|
|
25
25
|
AlignItem,
|
|
26
26
|
Concept,
|
|
27
|
+
ConceptArgs,
|
|
27
28
|
ConceptRef,
|
|
28
29
|
FilterItem,
|
|
29
30
|
Function,
|
|
@@ -191,16 +192,53 @@ def constant_to_concept(
|
|
|
191
192
|
)
|
|
192
193
|
|
|
193
194
|
|
|
195
|
+
def atom_is_relevant(
|
|
196
|
+
atom,
|
|
197
|
+
others: list[Concept | ConceptRef],
|
|
198
|
+
environment: Environment | None = None,
|
|
199
|
+
):
|
|
200
|
+
if isinstance(atom, (ConceptRef, Concept)):
|
|
201
|
+
# when we are looking at atoms, if there is a concept that is in others
|
|
202
|
+
# return directly
|
|
203
|
+
if atom.address in others:
|
|
204
|
+
return False
|
|
205
|
+
return concept_is_relevant(atom, others, environment)
|
|
206
|
+
|
|
207
|
+
if isinstance(atom, AggregateWrapper) and not atom.by:
|
|
208
|
+
return False
|
|
209
|
+
elif isinstance(atom, AggregateWrapper):
|
|
210
|
+
return any(atom_is_relevant(x, others, environment) for x in atom.by)
|
|
211
|
+
|
|
212
|
+
if isinstance(atom, Function):
|
|
213
|
+
relevant = False
|
|
214
|
+
print("atom args")
|
|
215
|
+
for arg in atom.arguments:
|
|
216
|
+
relevant = relevant or atom_is_relevant(arg, others, environment)
|
|
217
|
+
return relevant
|
|
218
|
+
elif isinstance(atom, FunctionCallWrapper):
|
|
219
|
+
return any(
|
|
220
|
+
[atom_is_relevant(atom.content, others, environment)]
|
|
221
|
+
+ [atom_is_relevant(x, others, environment) for x in atom.args]
|
|
222
|
+
)
|
|
223
|
+
elif isinstance(atom, ConceptArgs):
|
|
224
|
+
# use atom is relevant here to trigger the early exit behavior for concpets in set
|
|
225
|
+
return any(
|
|
226
|
+
[atom_is_relevant(x, others, environment) for x in atom.concept_arguments]
|
|
227
|
+
)
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
|
|
194
231
|
def concept_is_relevant(
|
|
195
232
|
concept: Concept | ConceptRef,
|
|
196
233
|
others: list[Concept | ConceptRef],
|
|
197
234
|
environment: Environment | None = None,
|
|
198
235
|
) -> bool:
|
|
199
|
-
if isinstance(concept, UndefinedConcept):
|
|
200
236
|
|
|
237
|
+
if isinstance(concept, UndefinedConcept):
|
|
201
238
|
return False
|
|
202
239
|
if concept.datatype == DataType.UNKNOWN:
|
|
203
240
|
return False
|
|
241
|
+
|
|
204
242
|
if isinstance(concept, ConceptRef):
|
|
205
243
|
if environment:
|
|
206
244
|
concept = environment.concepts[concept.address]
|
|
@@ -208,41 +246,56 @@ def concept_is_relevant(
|
|
|
208
246
|
raise SyntaxError(
|
|
209
247
|
"Require environment to determine relevance of ConceptRef"
|
|
210
248
|
)
|
|
211
|
-
|
|
249
|
+
if concept.derivation == Derivation.CONSTANT:
|
|
250
|
+
return False
|
|
212
251
|
if concept.is_aggregate and not (
|
|
213
252
|
isinstance(concept.lineage, AggregateWrapper) and concept.lineage.by
|
|
214
253
|
):
|
|
215
254
|
|
|
216
255
|
return False
|
|
217
256
|
if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
|
|
218
|
-
if
|
|
219
|
-
|
|
257
|
+
if all([c in others for c in concept.keys]):
|
|
220
258
|
return False
|
|
259
|
+
if (
|
|
260
|
+
concept.purpose == Purpose.KEY
|
|
261
|
+
and concept.keys
|
|
262
|
+
and all([c in others for c in concept.keys])
|
|
263
|
+
):
|
|
264
|
+
return False
|
|
221
265
|
if concept.purpose in (Purpose.METRIC,):
|
|
222
266
|
if all([c in others for c in concept.grain.components]):
|
|
223
267
|
return False
|
|
224
|
-
if concept.derivation in (Derivation.BASIC,)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
268
|
+
if concept.derivation in (Derivation.BASIC,) and isinstance(
|
|
269
|
+
concept.lineage, Function
|
|
270
|
+
):
|
|
271
|
+
relevant = False
|
|
272
|
+
for arg in concept.lineage.arguments:
|
|
273
|
+
relevant = atom_is_relevant(arg, others, environment) or relevant
|
|
274
|
+
return relevant
|
|
229
275
|
if concept.granularity == Granularity.SINGLE_ROW:
|
|
230
276
|
return False
|
|
231
277
|
return True
|
|
232
278
|
|
|
233
279
|
|
|
234
280
|
def concepts_to_grain_concepts(
|
|
235
|
-
concepts: Iterable[Concept | ConceptRef | str],
|
|
281
|
+
concepts: Iterable[Concept | ConceptRef | str],
|
|
282
|
+
environment: Environment | None,
|
|
283
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
236
284
|
) -> list[Concept]:
|
|
237
285
|
pconcepts: list[Concept] = []
|
|
238
286
|
for c in concepts:
|
|
239
|
-
|
|
240
287
|
if isinstance(c, Concept):
|
|
241
288
|
pconcepts.append(c)
|
|
242
289
|
elif isinstance(c, ConceptRef) and environment:
|
|
243
|
-
|
|
290
|
+
if local_concepts and c.address in local_concepts:
|
|
291
|
+
pconcepts.append(local_concepts[c.address])
|
|
292
|
+
else:
|
|
293
|
+
pconcepts.append(environment.concepts[c.address])
|
|
244
294
|
elif isinstance(c, str) and environment:
|
|
245
|
-
|
|
295
|
+
if local_concepts and c in local_concepts:
|
|
296
|
+
pconcepts.append(local_concepts[c])
|
|
297
|
+
else:
|
|
298
|
+
pconcepts.append(environment.concepts[c])
|
|
246
299
|
else:
|
|
247
300
|
raise ValueError(
|
|
248
301
|
f"Unable to resolve input {c} without environment provided to concepts_to_grain call"
|
|
@@ -250,6 +303,7 @@ def concepts_to_grain_concepts(
|
|
|
250
303
|
|
|
251
304
|
final: List[Concept] = []
|
|
252
305
|
for sub in pconcepts:
|
|
306
|
+
|
|
253
307
|
if not concept_is_relevant(sub, pconcepts, environment): # type: ignore
|
|
254
308
|
continue
|
|
255
309
|
final.append(sub)
|
|
@@ -366,7 +420,12 @@ def function_to_concept(
|
|
|
366
420
|
is_metric = False
|
|
367
421
|
ref_args, is_metric = get_relevant_parent_concepts(parent)
|
|
368
422
|
concrete_args = [environment.concepts[c.address] for c in ref_args]
|
|
369
|
-
pkeys += [
|
|
423
|
+
pkeys += [
|
|
424
|
+
x
|
|
425
|
+
for x in concrete_args
|
|
426
|
+
if not x.derivation == Derivation.CONSTANT
|
|
427
|
+
and not (x.derivation == Derivation.AGGREGATE and not x.grain.components)
|
|
428
|
+
]
|
|
370
429
|
grain: Grain | None = Grain()
|
|
371
430
|
for x in pkeys:
|
|
372
431
|
grain += x.grain
|
|
@@ -376,7 +435,7 @@ def function_to_concept(
|
|
|
376
435
|
modifiers = get_upstream_modifiers(pkeys, environment)
|
|
377
436
|
key_grain: list[str] = []
|
|
378
437
|
for x in pkeys:
|
|
379
|
-
# metrics will group to keys, so do
|
|
438
|
+
# metrics will group to keys, so do not do key traversal
|
|
380
439
|
if is_metric:
|
|
381
440
|
key_grain.append(x.address)
|
|
382
441
|
# otherwse, for row ops, assume keys are transitive
|
|
@@ -419,7 +478,6 @@ def function_to_concept(
|
|
|
419
478
|
else:
|
|
420
479
|
derivation = Derivation.BASIC
|
|
421
480
|
granularity = Granularity.MULTI_ROW
|
|
422
|
-
|
|
423
481
|
if grain is not None:
|
|
424
482
|
r = Concept(
|
|
425
483
|
name=name,
|