pytrilogy 0.0.3.48__tar.gz → 0.0.3.52__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.48/pytrilogy.egg-info → pytrilogy-0.0.3.52}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_functions.py +4 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_parse_engine.py +23 -1
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_parsing.py +12 -4
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/enums.py +3 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/functions.py +27 -2
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/models/author.py +12 -9
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/models/build.py +18 -12
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/models/execute.py +29 -13
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/concept_strategies_v3.py +1 -1
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/common.py +3 -4
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/filter_node.py +142 -91
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/group_node.py +3 -4
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/base_node.py +4 -1
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/statements/author.py +0 -2
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/base.py +7 -2
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/bigquery.py +2 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/duckdb.py +2 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parsing/common.py +28 -18
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parsing/parse_engine.py +45 -12
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parsing/trilogy.lark +10 -4
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/README.md +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/setup.cfg +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/setup.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_failure.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_parsing_failures.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_user_functions.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/authoring/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/models/core.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/models/environment.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/std/report.preql +0 -0
- {pytrilogy-0.0.3.48 → pytrilogy-0.0.3.52}/trilogy/utility.py +0 -0
|
@@ -214,6 +214,8 @@ def test_math_functions(test_environment):
|
|
|
214
214
|
property order_id.order_nested <- revenue * 2/2;
|
|
215
215
|
property order_id.rounded <- round(revenue + 2.01,2);
|
|
216
216
|
property order_id.rounded_default <- round(revenue + 2.01);
|
|
217
|
+
property order_id.floor <- floor(revenue + 2.01);
|
|
218
|
+
property order_id.ceil <- ceil(revenue + 2.01);
|
|
217
219
|
constant random <- random(1);
|
|
218
220
|
select
|
|
219
221
|
order_id,
|
|
@@ -224,6 +226,8 @@ def test_math_functions(test_environment):
|
|
|
224
226
|
order_add,
|
|
225
227
|
rounded,
|
|
226
228
|
rounded_default,
|
|
229
|
+
floor,
|
|
230
|
+
ceil,
|
|
227
231
|
random,
|
|
228
232
|
;"""
|
|
229
233
|
env, parsed = parse(declarations, environment=test_environment)
|
|
@@ -2,7 +2,12 @@ from pytest import raises
|
|
|
2
2
|
|
|
3
3
|
from trilogy.core.exceptions import UndefinedConceptException
|
|
4
4
|
from trilogy.core.models.environment import Environment
|
|
5
|
-
from trilogy.parsing.parse_engine import
|
|
5
|
+
from trilogy.parsing.parse_engine import (
|
|
6
|
+
PARSER,
|
|
7
|
+
InvalidSyntaxException,
|
|
8
|
+
ParseToObjects,
|
|
9
|
+
unpack_visit_error,
|
|
10
|
+
)
|
|
6
11
|
|
|
7
12
|
TEXT = """
|
|
8
13
|
const a <- 1;
|
|
@@ -29,3 +34,20 @@ def test_parser():
|
|
|
29
34
|
with raises(UndefinedConceptException):
|
|
30
35
|
unpack_visit_error(e)
|
|
31
36
|
assert failed
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
TEXT2 = """
|
|
40
|
+
const a <- 1;
|
|
41
|
+
|
|
42
|
+
select
|
|
43
|
+
a,
|
|
44
|
+
FROM a
|
|
45
|
+
;
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_from_error():
|
|
50
|
+
env = Environment()
|
|
51
|
+
|
|
52
|
+
with raises(InvalidSyntaxException):
|
|
53
|
+
env.parse(TEXT2)
|
|
@@ -92,7 +92,12 @@ def test_arg_to_datatype():
|
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
def test_argument_to_purpose(test_environment: Environment):
|
|
95
|
-
assert
|
|
95
|
+
assert (
|
|
96
|
+
argument_to_purpose(
|
|
97
|
+
1.00,
|
|
98
|
+
)
|
|
99
|
+
== Purpose.CONSTANT
|
|
100
|
+
)
|
|
96
101
|
assert argument_to_purpose("test") == Purpose.CONSTANT
|
|
97
102
|
assert argument_to_purpose(test_environment.concepts["order_id"]) == Purpose.KEY
|
|
98
103
|
assert (
|
|
@@ -100,19 +105,22 @@ def test_argument_to_purpose(test_environment: Environment):
|
|
|
100
105
|
[
|
|
101
106
|
"test",
|
|
102
107
|
1.00,
|
|
103
|
-
]
|
|
108
|
+
],
|
|
109
|
+
test_environment,
|
|
104
110
|
)
|
|
105
111
|
== Purpose.CONSTANT
|
|
106
112
|
)
|
|
107
113
|
assert (
|
|
108
114
|
function_args_to_output_purpose(
|
|
109
|
-
["test", 1.00, test_environment.concepts["order_id"]]
|
|
115
|
+
["test", 1.00, test_environment.concepts["order_id"]], test_environment
|
|
110
116
|
)
|
|
111
117
|
== Purpose.PROPERTY
|
|
112
118
|
)
|
|
113
119
|
unnest_env, parsed = parse_text("const random <- unnest([1,2,3,4]);")
|
|
114
120
|
assert (
|
|
115
|
-
function_args_to_output_purpose(
|
|
121
|
+
function_args_to_output_purpose(
|
|
122
|
+
[unnest_env.concepts["random"]], test_environment
|
|
123
|
+
)
|
|
116
124
|
== Purpose.PROPERTY
|
|
117
125
|
)
|
|
118
126
|
|
|
@@ -131,6 +131,7 @@ class FunctionType(Enum):
|
|
|
131
131
|
CONSTANT = "constant"
|
|
132
132
|
COALESCE = "coalesce"
|
|
133
133
|
IS_NULL = "isnull"
|
|
134
|
+
NULLIF = "nullif"
|
|
134
135
|
BOOL = "bool"
|
|
135
136
|
|
|
136
137
|
# COMPLEX
|
|
@@ -156,6 +157,8 @@ class FunctionType(Enum):
|
|
|
156
157
|
ABS = "abs"
|
|
157
158
|
SQRT = "sqrt"
|
|
158
159
|
RANDOM = "random"
|
|
160
|
+
FLOOR = "floor"
|
|
161
|
+
CEIL = "ceil"
|
|
159
162
|
|
|
160
163
|
# Aggregates
|
|
161
164
|
## group is not a real aggregate - it just means group by this + some other set of fields
|
|
@@ -279,6 +279,12 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
279
279
|
output_purpose=Purpose.PROPERTY,
|
|
280
280
|
arg_count=1,
|
|
281
281
|
),
|
|
282
|
+
FunctionType.NULLIF: FunctionConfig(
|
|
283
|
+
valid_inputs={*DataType},
|
|
284
|
+
output_purpose=Purpose.PROPERTY,
|
|
285
|
+
output_type_function=lambda args: get_output_type_at_index(args, 0),
|
|
286
|
+
arg_count=2,
|
|
287
|
+
),
|
|
282
288
|
FunctionType.COALESCE: FunctionConfig(
|
|
283
289
|
valid_inputs={*DataType},
|
|
284
290
|
output_purpose=Purpose.PROPERTY,
|
|
@@ -637,6 +643,22 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
637
643
|
output_type_function=lambda args: get_output_type_at_index(args, 0),
|
|
638
644
|
arg_count=2,
|
|
639
645
|
),
|
|
646
|
+
FunctionType.FLOOR: FunctionConfig(
|
|
647
|
+
valid_inputs=[
|
|
648
|
+
{DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
|
|
649
|
+
],
|
|
650
|
+
output_purpose=Purpose.PROPERTY,
|
|
651
|
+
output_type=DataType.INTEGER,
|
|
652
|
+
arg_count=1,
|
|
653
|
+
),
|
|
654
|
+
FunctionType.CEIL: FunctionConfig(
|
|
655
|
+
valid_inputs=[
|
|
656
|
+
{DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
|
|
657
|
+
],
|
|
658
|
+
output_purpose=Purpose.PROPERTY,
|
|
659
|
+
output_type=DataType.INTEGER,
|
|
660
|
+
arg_count=1,
|
|
661
|
+
),
|
|
640
662
|
FunctionType.CUSTOM: FunctionConfig(
|
|
641
663
|
output_purpose=Purpose.PROPERTY,
|
|
642
664
|
arg_count=InfiniteFunctionArgs,
|
|
@@ -787,13 +809,14 @@ def create_function_derived_concept(
|
|
|
787
809
|
namespace: str,
|
|
788
810
|
operator: FunctionType,
|
|
789
811
|
arguments: list[Concept],
|
|
812
|
+
environment: Environment,
|
|
790
813
|
output_type: Optional[
|
|
791
814
|
DataType | ListType | StructType | MapType | NumericType | TraitDataType
|
|
792
815
|
] = None,
|
|
793
816
|
output_purpose: Optional[Purpose] = None,
|
|
794
817
|
) -> Concept:
|
|
795
818
|
purpose = (
|
|
796
|
-
function_args_to_output_purpose(arguments)
|
|
819
|
+
function_args_to_output_purpose(arguments, environment=environment)
|
|
797
820
|
if output_purpose is None
|
|
798
821
|
else output_purpose
|
|
799
822
|
)
|
|
@@ -846,13 +869,15 @@ def argument_to_purpose(arg) -> Purpose:
|
|
|
846
869
|
raise ValueError(f"Cannot parse arg purpose for {arg} of type {type(arg)}")
|
|
847
870
|
|
|
848
871
|
|
|
849
|
-
def function_args_to_output_purpose(args) -> Purpose:
|
|
872
|
+
def function_args_to_output_purpose(args, environment: Environment) -> Purpose:
|
|
850
873
|
has_metric = False
|
|
851
874
|
has_non_constant = False
|
|
852
875
|
has_non_single_row_constant = False
|
|
853
876
|
if not args:
|
|
854
877
|
return Purpose.CONSTANT
|
|
855
878
|
for arg in args:
|
|
879
|
+
if isinstance(arg, ConceptRef):
|
|
880
|
+
arg = environment.concepts[arg.address]
|
|
856
881
|
purpose = argument_to_purpose(arg)
|
|
857
882
|
if purpose == Purpose.METRIC:
|
|
858
883
|
has_metric = True
|
|
@@ -25,6 +25,7 @@ from pydantic import (
|
|
|
25
25
|
ValidationInfo,
|
|
26
26
|
computed_field,
|
|
27
27
|
field_validator,
|
|
28
|
+
model_validator,
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
from trilogy.constants import DEFAULT_NAMESPACE, MagicConstants
|
|
@@ -621,8 +622,8 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
|
|
|
621
622
|
return v.reference
|
|
622
623
|
return v
|
|
623
624
|
|
|
624
|
-
|
|
625
|
-
|
|
625
|
+
@model_validator(mode="after")
|
|
626
|
+
def validate_comparison(self):
|
|
626
627
|
if self.operator in (ComparisonOperator.IS, ComparisonOperator.IS_NOT):
|
|
627
628
|
if self.right != MagicConstants.NULL and DataType.BOOL != arg_to_datatype(
|
|
628
629
|
self.right
|
|
@@ -632,7 +633,6 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
|
|
|
632
633
|
)
|
|
633
634
|
elif self.operator in (ComparisonOperator.IN, ComparisonOperator.NOT_IN):
|
|
634
635
|
right_type = arg_to_datatype(self.right)
|
|
635
|
-
|
|
636
636
|
if isinstance(right_type, ListType) and not is_compatible_datatype(
|
|
637
637
|
arg_to_datatype(self.left), right_type.value_data_type
|
|
638
638
|
):
|
|
@@ -653,6 +653,8 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
|
|
|
653
653
|
f"Cannot compare {arg_to_datatype(self.left)} and {arg_to_datatype(self.right)} of different types with operator {self.operator} in {str(self)}"
|
|
654
654
|
)
|
|
655
655
|
|
|
656
|
+
return self
|
|
657
|
+
|
|
656
658
|
def __add__(self, other):
|
|
657
659
|
if other is None:
|
|
658
660
|
return self
|
|
@@ -1022,7 +1024,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1022
1024
|
keys = self.keys
|
|
1023
1025
|
|
|
1024
1026
|
if self.is_aggregate and isinstance(new_lineage, Function) and grain.components:
|
|
1025
|
-
grain_components = [
|
|
1027
|
+
grain_components: list[ConceptRef | Concept] = [
|
|
1026
1028
|
environment.concepts[c].reference for c in grain.components
|
|
1027
1029
|
]
|
|
1028
1030
|
new_lineage = AggregateWrapper(function=new_lineage, by=grain_components)
|
|
@@ -1847,9 +1849,6 @@ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel)
|
|
|
1847
1849
|
function: Function
|
|
1848
1850
|
by: List[ConceptRef | Concept] = Field(default_factory=list)
|
|
1849
1851
|
|
|
1850
|
-
def __init__(self, **kwargs):
|
|
1851
|
-
super().__init__(**kwargs)
|
|
1852
|
-
|
|
1853
1852
|
@field_validator("by", mode="before")
|
|
1854
1853
|
@classmethod
|
|
1855
1854
|
def enforce_concept_ref(cls, v):
|
|
@@ -1945,11 +1944,15 @@ class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
|
|
|
1945
1944
|
|
|
1946
1945
|
@property
|
|
1947
1946
|
def output_datatype(self):
|
|
1948
|
-
return self.content
|
|
1947
|
+
return arg_to_datatype(self.content)
|
|
1949
1948
|
|
|
1950
1949
|
@property
|
|
1951
1950
|
def concept_arguments(self):
|
|
1952
|
-
|
|
1951
|
+
if isinstance(self.content, ConceptRef):
|
|
1952
|
+
return [self.content] + self.where.concept_arguments
|
|
1953
|
+
elif isinstance(self.content, ConceptArgs):
|
|
1954
|
+
return self.content.concept_arguments + self.where.concept_arguments
|
|
1955
|
+
return self.where.concept_arguments
|
|
1953
1956
|
|
|
1954
1957
|
|
|
1955
1958
|
class RowsetLineage(Namespaced, Mergeable, BaseModel):
|
|
@@ -262,11 +262,8 @@ class BuildGrain(BaseModel):
|
|
|
262
262
|
components: set[str] = Field(default_factory=set)
|
|
263
263
|
where_clause: Optional[BuildWhereClause] = None
|
|
264
264
|
|
|
265
|
-
def __init__(self, **kwargs):
|
|
266
|
-
super().__init__(**kwargs)
|
|
267
|
-
|
|
268
265
|
def without_condition(self):
|
|
269
|
-
return BuildGrain(components=self.components)
|
|
266
|
+
return BuildGrain.model_construct(components=self.components)
|
|
270
267
|
|
|
271
268
|
@classmethod
|
|
272
269
|
def from_concepts(
|
|
@@ -321,12 +318,12 @@ class BuildGrain(BaseModel):
|
|
|
321
318
|
# raise NotImplementedError(
|
|
322
319
|
# f"Cannot merge grains with where clauses, self {self.where_clause} other {other.where_clause}"
|
|
323
320
|
# )
|
|
324
|
-
return BuildGrain(
|
|
321
|
+
return BuildGrain.model_construct(
|
|
325
322
|
components=self.components.union(other.components), where_clause=where
|
|
326
323
|
)
|
|
327
324
|
|
|
328
325
|
def __sub__(self, other: "BuildGrain") -> "BuildGrain":
|
|
329
|
-
return BuildGrain(
|
|
326
|
+
return BuildGrain.model_construct(
|
|
330
327
|
components=self.components.difference(other.components),
|
|
331
328
|
where_clause=self.where_clause,
|
|
332
329
|
)
|
|
@@ -637,9 +634,6 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
|
|
|
637
634
|
]
|
|
638
635
|
operator: ComparisonOperator
|
|
639
636
|
|
|
640
|
-
def __init__(self, *args, **kwargs) -> None:
|
|
641
|
-
super().__init__(*args, **kwargs)
|
|
642
|
-
|
|
643
637
|
def __add__(self, other):
|
|
644
638
|
if other is None:
|
|
645
639
|
return self
|
|
@@ -1173,7 +1167,7 @@ class BuildAggregateWrapper(BuildConceptArgs, DataTyped, BaseModel):
|
|
|
1173
1167
|
|
|
1174
1168
|
|
|
1175
1169
|
class BuildFilterItem(BuildConceptArgs, BaseModel):
|
|
1176
|
-
content:
|
|
1170
|
+
content: "BuildExpr"
|
|
1177
1171
|
where: BuildWhereClause
|
|
1178
1172
|
|
|
1179
1173
|
def __str__(self):
|
|
@@ -1181,15 +1175,27 @@ class BuildFilterItem(BuildConceptArgs, BaseModel):
|
|
|
1181
1175
|
|
|
1182
1176
|
@property
|
|
1183
1177
|
def output_datatype(self):
|
|
1184
|
-
return self.content
|
|
1178
|
+
return arg_to_datatype(self.content)
|
|
1185
1179
|
|
|
1186
1180
|
@property
|
|
1187
1181
|
def output_purpose(self):
|
|
1188
1182
|
return self.content.purpose
|
|
1189
1183
|
|
|
1184
|
+
@property
|
|
1185
|
+
def content_concept_arguments(self):
|
|
1186
|
+
if isinstance(self.content, BuildConcept):
|
|
1187
|
+
return [self.content]
|
|
1188
|
+
elif isinstance(self.content, BuildConceptArgs):
|
|
1189
|
+
return self.content.concept_arguments
|
|
1190
|
+
return []
|
|
1191
|
+
|
|
1190
1192
|
@property
|
|
1191
1193
|
def concept_arguments(self):
|
|
1192
|
-
|
|
1194
|
+
if isinstance(self.content, BuildConcept):
|
|
1195
|
+
return [self.content] + self.where.concept_arguments
|
|
1196
|
+
elif isinstance(self.content, BuildConceptArgs):
|
|
1197
|
+
return self.content.concept_arguments + self.where.concept_arguments
|
|
1198
|
+
return self.where.concept_arguments
|
|
1193
1199
|
|
|
1194
1200
|
|
|
1195
1201
|
class BuildRowsetLineage(BuildConceptArgs, BaseModel):
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections import defaultdict
|
|
4
|
-
from typing import
|
|
5
|
-
|
|
6
|
-
from pydantic import
|
|
4
|
+
from typing import Dict, List, Optional, Set, Union
|
|
5
|
+
|
|
6
|
+
from pydantic import (
|
|
7
|
+
BaseModel,
|
|
8
|
+
Field,
|
|
9
|
+
ValidationInfo,
|
|
10
|
+
computed_field,
|
|
11
|
+
field_validator,
|
|
12
|
+
model_validator,
|
|
13
|
+
)
|
|
7
14
|
|
|
8
15
|
from trilogy.constants import CONFIG, logger
|
|
9
16
|
from trilogy.core.constants import CONSTANT_DATASET
|
|
@@ -473,8 +480,8 @@ class BaseJoin(BaseModel):
|
|
|
473
480
|
left_datasource: Optional[Union[BuildDatasource, "QueryDatasource"]] = None
|
|
474
481
|
concept_pairs: list[ConceptPair] | None = None
|
|
475
482
|
|
|
476
|
-
|
|
477
|
-
|
|
483
|
+
@model_validator(mode="after")
|
|
484
|
+
def validate_join(self) -> "BaseJoin":
|
|
478
485
|
if (
|
|
479
486
|
self.left_datasource
|
|
480
487
|
and self.left_datasource.identifier == self.right_datasource.identifier
|
|
@@ -483,14 +490,18 @@ class BaseJoin(BaseModel):
|
|
|
483
490
|
f"Cannot join a dataself to itself, joining {self.left_datasource} and"
|
|
484
491
|
f" {self.right_datasource}"
|
|
485
492
|
)
|
|
486
|
-
final_concepts = []
|
|
487
493
|
|
|
488
|
-
#
|
|
494
|
+
# Early returns maintained as in original code
|
|
489
495
|
if self.concept_pairs:
|
|
490
|
-
return
|
|
496
|
+
return self
|
|
497
|
+
|
|
491
498
|
if self.concepts == []:
|
|
492
|
-
return
|
|
499
|
+
return self
|
|
500
|
+
|
|
501
|
+
# Validation logic
|
|
502
|
+
final_concepts = []
|
|
493
503
|
assert self.left_datasource and self.right_datasource
|
|
504
|
+
|
|
494
505
|
for concept in self.concepts or []:
|
|
495
506
|
include = True
|
|
496
507
|
for ds in [self.left_datasource, self.right_datasource]:
|
|
@@ -507,6 +518,7 @@ class BaseJoin(BaseModel):
|
|
|
507
518
|
)
|
|
508
519
|
if include:
|
|
509
520
|
final_concepts.append(concept)
|
|
521
|
+
|
|
510
522
|
if not final_concepts and self.concepts:
|
|
511
523
|
# if one datasource only has constants
|
|
512
524
|
# we can join on 1=1
|
|
@@ -519,11 +531,11 @@ class BaseJoin(BaseModel):
|
|
|
519
531
|
]
|
|
520
532
|
):
|
|
521
533
|
self.concepts = []
|
|
522
|
-
return
|
|
534
|
+
return self
|
|
523
535
|
# if everything is at abstract grain, we can skip joins
|
|
524
536
|
if all([c.grain.abstract for c in ds.output_concepts]):
|
|
525
537
|
self.concepts = []
|
|
526
|
-
return
|
|
538
|
+
return self
|
|
527
539
|
|
|
528
540
|
left_keys = [c.address for c in self.left_datasource.output_concepts]
|
|
529
541
|
right_keys = [c.address for c in self.right_datasource.output_concepts]
|
|
@@ -535,7 +547,9 @@ class BaseJoin(BaseModel):
|
|
|
535
547
|
f" right_keys {right_keys},"
|
|
536
548
|
f" provided join concepts {match_concepts}"
|
|
537
549
|
)
|
|
550
|
+
|
|
538
551
|
self.concepts = final_concepts
|
|
552
|
+
return self
|
|
539
553
|
|
|
540
554
|
@property
|
|
541
555
|
def unique_id(self) -> str:
|
|
@@ -695,7 +709,7 @@ class QueryDatasource(BaseModel):
|
|
|
695
709
|
"can only merge two datasources if the force_group flag is the same"
|
|
696
710
|
)
|
|
697
711
|
logger.debug(
|
|
698
|
-
f"
|
|
712
|
+
f"[Query Datasource] merging {self.name} with"
|
|
699
713
|
f" {[c.address for c in self.output_concepts]} concepts and"
|
|
700
714
|
f" {other.name} with {[c.address for c in other.output_concepts]} concepts"
|
|
701
715
|
)
|
|
@@ -759,7 +773,9 @@ class QueryDatasource(BaseModel):
|
|
|
759
773
|
hidden_concepts=hidden,
|
|
760
774
|
ordering=self.ordering,
|
|
761
775
|
)
|
|
762
|
-
|
|
776
|
+
logger.debug(
|
|
777
|
+
f"[Query Datasource] merged with {[c.address for c in qds.output_concepts]} concepts"
|
|
778
|
+
)
|
|
763
779
|
return qds
|
|
764
780
|
|
|
765
781
|
@property
|
|
@@ -319,7 +319,7 @@ def generate_node(
|
|
|
319
319
|
]
|
|
320
320
|
|
|
321
321
|
logger.info(
|
|
322
|
-
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} for {concept.address}, generating aggregate node with {[x
|
|
322
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} for {concept.address}, generating aggregate node with {[x for x in agg_optional]}"
|
|
323
323
|
)
|
|
324
324
|
return gen_group_node(
|
|
325
325
|
concept,
|
|
@@ -67,14 +67,14 @@ def resolve_condition_parent_concepts(
|
|
|
67
67
|
def resolve_filter_parent_concepts(
|
|
68
68
|
concept: BuildConcept,
|
|
69
69
|
environment: BuildEnvironment,
|
|
70
|
-
) -> Tuple[
|
|
70
|
+
) -> Tuple[List[BuildConcept], List[Tuple[BuildConcept, ...]]]:
|
|
71
71
|
if not isinstance(concept.lineage, (BuildFilterItem,)):
|
|
72
72
|
raise ValueError(
|
|
73
73
|
f"Concept {concept} lineage is not filter item, is {type(concept.lineage)}"
|
|
74
74
|
)
|
|
75
75
|
direct_parent = concept.lineage.content
|
|
76
76
|
base_existence = []
|
|
77
|
-
base_rows = [direct_parent]
|
|
77
|
+
base_rows = [direct_parent] if isinstance(direct_parent, BuildConcept) else []
|
|
78
78
|
condition_rows, condition_existence = resolve_condition_parent_concepts(
|
|
79
79
|
concept.lineage.where
|
|
80
80
|
)
|
|
@@ -90,11 +90,10 @@ def resolve_filter_parent_concepts(
|
|
|
90
90
|
|
|
91
91
|
if concept.lineage.where.existence_arguments:
|
|
92
92
|
return (
|
|
93
|
-
concept.lineage.content,
|
|
94
93
|
unique(base_rows, "address"),
|
|
95
94
|
base_existence,
|
|
96
95
|
)
|
|
97
|
-
return
|
|
96
|
+
return unique(base_rows, "address"), []
|
|
98
97
|
|
|
99
98
|
|
|
100
99
|
def gen_property_enrichment_node(
|