pytrilogy 0.0.2.49__tar.gz → 0.0.2.50__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.2.49/pytrilogy.egg-info → pytrilogy-0.0.2.50}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_discovery_nodes.py +0 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_functions.py +32 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/enums.py +11 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/functions.py +4 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/models.py +11 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/concept_strategies_v3.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/common.py +0 -2
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/filter_node.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/group_node.py +0 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/group_to_node.py +0 -2
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/multiselect_node.py +0 -2
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/node_merge_node.py +0 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/rowset_node.py +0 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/select_merge_node.py +138 -59
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/union_node.py +0 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/unnest_node.py +0 -2
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/window_node.py +0 -2
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/base_node.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/filter_node.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/group_node.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/merge_node.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/select_node_v2.py +0 -4
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/union_node.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/unnest_node.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/window_node.py +0 -3
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/utility.py +3 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/query_processor.py +0 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/base.py +14 -2
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/duckdb.py +7 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/hooks/graph_hook.py +14 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parsing/common.py +14 -5
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parsing/parse_engine.py +31 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parsing/trilogy.lark +3 -1
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/README.md +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/pyproject.toml +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/setup.cfg +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/setup.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_models.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_select.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_show.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/utility.py +0 -0
|
@@ -15,7 +15,6 @@ def test_group_node(test_environment, test_environment_graph):
|
|
|
15
15
|
output_concepts=[total_revenue, category],
|
|
16
16
|
input_concepts=[category, revenue],
|
|
17
17
|
environment=test_environment,
|
|
18
|
-
g=test_environment_graph,
|
|
19
18
|
parents=[
|
|
20
19
|
search_concepts(
|
|
21
20
|
[category, revenue],
|
|
@@ -14,6 +14,8 @@ from trilogy.dialect.duckdb import DuckDBDialect
|
|
|
14
14
|
from trilogy.dialect.snowflake import SnowflakeDialect
|
|
15
15
|
from trilogy.dialect.sql_server import SqlServerDialect
|
|
16
16
|
from trilogy.parser import parse
|
|
17
|
+
from trilogy import Dialects
|
|
18
|
+
from datetime import date, datetime
|
|
17
19
|
|
|
18
20
|
logger.setLevel(INFO)
|
|
19
21
|
|
|
@@ -154,6 +156,36 @@ def test_explicit_cast(test_environment):
|
|
|
154
156
|
dialect.compile_statement(process_query(test_environment, select))
|
|
155
157
|
|
|
156
158
|
|
|
159
|
+
def test_literal_cast(test_environment):
|
|
160
|
+
declarations = """
|
|
161
|
+
select
|
|
162
|
+
'1'::int -> one,
|
|
163
|
+
'1'::float -> one_float,
|
|
164
|
+
'1'::string -> one_string,
|
|
165
|
+
'2024-01-01'::date -> one_date,
|
|
166
|
+
'2024-01-01 01:01:01'::datetime -> one_datetime,
|
|
167
|
+
'true'::bool -> one_bool,
|
|
168
|
+
;"""
|
|
169
|
+
env, parsed = parse(declarations, environment=test_environment)
|
|
170
|
+
|
|
171
|
+
select: SelectStatement = parsed[-1]
|
|
172
|
+
z = (
|
|
173
|
+
Dialects.DUCK_DB.default_executor(environment=test_environment)
|
|
174
|
+
.execute_query(parsed[-1])
|
|
175
|
+
.fetchall()
|
|
176
|
+
)
|
|
177
|
+
assert z[0].one == 1
|
|
178
|
+
assert z[0].one_float == 1.0
|
|
179
|
+
assert z[0].one_string == "1"
|
|
180
|
+
assert z[0].one_date == date(year=2024, month=1, day=1)
|
|
181
|
+
assert z[0].one_datetime == datetime(
|
|
182
|
+
year=2024, month=1, day=1, hour=1, minute=1, second=1
|
|
183
|
+
)
|
|
184
|
+
assert z[0].one_bool == True
|
|
185
|
+
for dialect in TEST_DIALECTS:
|
|
186
|
+
dialect.compile_statement(process_query(test_environment, select))
|
|
187
|
+
|
|
188
|
+
|
|
157
189
|
def test_math_functions(test_environment):
|
|
158
190
|
declarations = """
|
|
159
191
|
|
|
@@ -120,6 +120,8 @@ class FunctionType(Enum):
|
|
|
120
120
|
|
|
121
121
|
ALIAS = "alias"
|
|
122
122
|
|
|
123
|
+
PARENTHETICAL = "parenthetical"
|
|
124
|
+
|
|
123
125
|
# Generic
|
|
124
126
|
CASE = "case"
|
|
125
127
|
CAST = "cast"
|
|
@@ -135,6 +137,8 @@ class FunctionType(Enum):
|
|
|
135
137
|
ATTR_ACCESS = "attr_access"
|
|
136
138
|
STRUCT = "struct"
|
|
137
139
|
ARRAY = "array"
|
|
140
|
+
DATE_LITERAL = "date_literal"
|
|
141
|
+
DATETIME_LITERAL = "datetime_literal"
|
|
138
142
|
|
|
139
143
|
# TEXT AND MAYBE MORE
|
|
140
144
|
SPLIT = "split"
|
|
@@ -260,6 +264,13 @@ class ComparisonOperator(Enum):
|
|
|
260
264
|
CONTAINS = "contains"
|
|
261
265
|
ELSE = "else"
|
|
262
266
|
|
|
267
|
+
def __eq__(self, other):
|
|
268
|
+
if isinstance(other, str):
|
|
269
|
+
return self.value == other
|
|
270
|
+
if not isinstance(other, ComparisonOperator):
|
|
271
|
+
return False
|
|
272
|
+
return self.value == other.value
|
|
273
|
+
|
|
263
274
|
@classmethod
|
|
264
275
|
def _missing_(cls, value):
|
|
265
276
|
if not isinstance(value, list) and " " in str(value):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from datetime import date, datetime
|
|
1
2
|
from typing import Optional
|
|
2
3
|
|
|
3
4
|
from trilogy.constants import MagicConstants
|
|
@@ -17,6 +18,8 @@ from trilogy.core.models import (
|
|
|
17
18
|
arg_to_datatype,
|
|
18
19
|
)
|
|
19
20
|
|
|
21
|
+
GENERIC_ARGS = Concept | Function | str | int | float | date | datetime
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
def create_function_derived_concept(
|
|
22
25
|
name: str,
|
|
@@ -262,7 +265,7 @@ def get_attr_datatype(
|
|
|
262
265
|
return arg.datatype
|
|
263
266
|
|
|
264
267
|
|
|
265
|
-
def AttrAccess(args: list[
|
|
268
|
+
def AttrAccess(args: list[GENERIC_ARGS]):
|
|
266
269
|
return Function(
|
|
267
270
|
operator=FunctionType.ATTR_ACCESS,
|
|
268
271
|
arguments=args,
|
|
@@ -5,6 +5,7 @@ import hashlib
|
|
|
5
5
|
import os
|
|
6
6
|
from abc import ABC
|
|
7
7
|
from collections import UserDict, UserList, defaultdict
|
|
8
|
+
from datetime import date, datetime
|
|
8
9
|
from enum import Enum
|
|
9
10
|
from functools import cached_property
|
|
10
11
|
from pathlib import Path
|
|
@@ -1264,6 +1265,8 @@ class Function(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
1264
1265
|
int,
|
|
1265
1266
|
float,
|
|
1266
1267
|
str,
|
|
1268
|
+
date,
|
|
1269
|
+
datetime,
|
|
1267
1270
|
MapWrapper[Any, Any],
|
|
1268
1271
|
DataType,
|
|
1269
1272
|
ListType,
|
|
@@ -3868,6 +3871,8 @@ class Comparison(
|
|
|
3868
3871
|
float,
|
|
3869
3872
|
list,
|
|
3870
3873
|
bool,
|
|
3874
|
+
datetime,
|
|
3875
|
+
date,
|
|
3871
3876
|
Function,
|
|
3872
3877
|
Concept,
|
|
3873
3878
|
"Conditional",
|
|
@@ -3884,6 +3889,8 @@ class Comparison(
|
|
|
3884
3889
|
float,
|
|
3885
3890
|
list,
|
|
3886
3891
|
bool,
|
|
3892
|
+
date,
|
|
3893
|
+
datetime,
|
|
3887
3894
|
Concept,
|
|
3888
3895
|
Function,
|
|
3889
3896
|
"Conditional",
|
|
@@ -5008,5 +5015,9 @@ def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType | Numeric
|
|
|
5008
5015
|
return ListType(type=wrapper.type)
|
|
5009
5016
|
elif isinstance(arg, MapWrapper):
|
|
5010
5017
|
return MapType(key_type=arg.key_type, value_type=arg.value_type)
|
|
5018
|
+
elif isinstance(arg, datetime):
|
|
5019
|
+
return DataType.DATETIME
|
|
5020
|
+
elif isinstance(arg, date):
|
|
5021
|
+
return DataType.DATE
|
|
5011
5022
|
else:
|
|
5012
5023
|
raise ValueError(f"Cannot parse arg datatype for arg of raw type {type(arg)}")
|
|
@@ -359,7 +359,6 @@ def generate_node(
|
|
|
359
359
|
input_concepts=[],
|
|
360
360
|
output_concepts=constant_targets,
|
|
361
361
|
environment=environment,
|
|
362
|
-
g=g,
|
|
363
362
|
parents=[],
|
|
364
363
|
depth=depth + 1,
|
|
365
364
|
)
|
|
@@ -906,7 +905,6 @@ def _search_concepts(
|
|
|
906
905
|
input_concepts=non_virtual,
|
|
907
906
|
output_concepts=non_virtual,
|
|
908
907
|
environment=environment,
|
|
909
|
-
g=g,
|
|
910
908
|
parents=stack,
|
|
911
909
|
depth=depth,
|
|
912
910
|
)
|
|
@@ -987,7 +985,6 @@ def source_query_concepts(
|
|
|
987
985
|
x for x in root.output_concepts if x.address not in root.hidden_concepts
|
|
988
986
|
],
|
|
989
987
|
environment=environment,
|
|
990
|
-
g=g,
|
|
991
988
|
parents=[root],
|
|
992
989
|
partial_concepts=root.partial_concepts,
|
|
993
990
|
)
|
|
@@ -130,7 +130,6 @@ def gen_property_enrichment_node(
|
|
|
130
130
|
),
|
|
131
131
|
output_concepts=base_node.output_concepts + extra_properties,
|
|
132
132
|
environment=environment,
|
|
133
|
-
g=g,
|
|
134
133
|
parents=[
|
|
135
134
|
base_node,
|
|
136
135
|
]
|
|
@@ -209,7 +208,6 @@ def gen_enrichment_node(
|
|
|
209
208
|
input_concepts=unique(join_keys + extra_required + non_hidden, "address"),
|
|
210
209
|
output_concepts=unique(join_keys + extra_required + non_hidden, "address"),
|
|
211
210
|
environment=environment,
|
|
212
|
-
g=g,
|
|
213
211
|
parents=[enrich_node, base_node],
|
|
214
212
|
force_group=False,
|
|
215
213
|
preexisting_conditions=conditions.conditional if conditions else None,
|
{pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
@@ -117,7 +117,6 @@ def gen_filter_node(
|
|
|
117
117
|
input_concepts=row_parent.output_concepts,
|
|
118
118
|
output_concepts=[concept] + row_parent.output_concepts,
|
|
119
119
|
environment=row_parent.environment,
|
|
120
|
-
g=row_parent.g,
|
|
121
120
|
parents=[row_parent],
|
|
122
121
|
depth=row_parent.depth,
|
|
123
122
|
partial_concepts=row_parent.partial_concepts,
|
|
@@ -161,7 +160,6 @@ def gen_filter_node(
|
|
|
161
160
|
),
|
|
162
161
|
output_concepts=[concept, immediate_parent] + parent_row_concepts,
|
|
163
162
|
environment=environment,
|
|
164
|
-
g=g,
|
|
165
163
|
parents=core_parents,
|
|
166
164
|
grain=Grain(
|
|
167
165
|
components=[immediate_parent] + parent_row_concepts,
|
|
@@ -202,7 +200,6 @@ def gen_filter_node(
|
|
|
202
200
|
]
|
|
203
201
|
+ local_optional,
|
|
204
202
|
environment=environment,
|
|
205
|
-
g=g,
|
|
206
203
|
parents=[
|
|
207
204
|
# this node fetches only what we need to filter
|
|
208
205
|
filter_node,
|
{pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/group_to_node.py
RENAMED
|
@@ -45,7 +45,6 @@ def gen_group_to_node(
|
|
|
45
45
|
output_concepts=parent_concepts + [concept],
|
|
46
46
|
input_concepts=parent_concepts,
|
|
47
47
|
environment=environment,
|
|
48
|
-
g=g,
|
|
49
48
|
parents=parents,
|
|
50
49
|
depth=depth,
|
|
51
50
|
)
|
|
@@ -76,7 +75,6 @@ def gen_group_to_node(
|
|
|
76
75
|
+ [x for x in parent_concepts if x.address != concept.address],
|
|
77
76
|
output_concepts=[concept] + local_optional,
|
|
78
77
|
environment=environment,
|
|
79
|
-
g=g,
|
|
80
78
|
parents=[
|
|
81
79
|
# this node gets the group
|
|
82
80
|
group_node,
|
|
@@ -108,7 +108,6 @@ def gen_multiselect_node(
|
|
|
108
108
|
input_concepts=[x for y in base_parents for x in y.output_concepts],
|
|
109
109
|
output_concepts=[x for y in base_parents for x in y.output_concepts],
|
|
110
110
|
environment=environment,
|
|
111
|
-
g=g,
|
|
112
111
|
depth=depth,
|
|
113
112
|
parents=base_parents,
|
|
114
113
|
node_joins=node_joins,
|
|
@@ -178,7 +177,6 @@ def gen_multiselect_node(
|
|
|
178
177
|
input_concepts=enrich_node.output_concepts + node.output_concepts,
|
|
179
178
|
output_concepts=node.output_concepts + local_optional,
|
|
180
179
|
environment=environment,
|
|
181
|
-
g=g,
|
|
182
180
|
depth=depth,
|
|
183
181
|
parents=[
|
|
184
182
|
# this node gets the multiselect
|
|
@@ -13,6 +13,9 @@ from trilogy.core.models import (
|
|
|
13
13
|
LooseConceptList,
|
|
14
14
|
WhereClause,
|
|
15
15
|
)
|
|
16
|
+
from trilogy.core.processing.node_generators.select_helpers.datasource_injection import (
|
|
17
|
+
get_union_sources,
|
|
18
|
+
)
|
|
16
19
|
from trilogy.core.processing.nodes import (
|
|
17
20
|
ConstantNode,
|
|
18
21
|
GroupNode,
|
|
@@ -35,38 +38,66 @@ def extract_address(node: str):
|
|
|
35
38
|
def get_graph_partial_nodes(
|
|
36
39
|
g: nx.DiGraph, conditions: WhereClause | None
|
|
37
40
|
) -> dict[str, list[str]]:
|
|
38
|
-
datasources: dict[str, Datasource] = nx.get_node_attributes(
|
|
41
|
+
datasources: dict[str, Datasource | list[Datasource]] = nx.get_node_attributes(
|
|
42
|
+
g, "datasource"
|
|
43
|
+
)
|
|
39
44
|
partial: dict[str, list[str]] = {}
|
|
40
45
|
for node in g.nodes:
|
|
41
46
|
if node in datasources:
|
|
42
47
|
ds = datasources[node]
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
if not isinstance(ds, list):
|
|
49
|
+
if ds.non_partial_for and conditions == ds.non_partial_for:
|
|
50
|
+
partial[node] = []
|
|
51
|
+
continue
|
|
52
|
+
partial[node] = [concept_to_node(c) for c in ds.partial_concepts]
|
|
53
|
+
ds = [ds]
|
|
54
|
+
# assume union sources have no partial
|
|
55
|
+
else:
|
|
45
56
|
partial[node] = []
|
|
46
57
|
|
|
47
58
|
return partial
|
|
48
59
|
|
|
49
60
|
|
|
50
61
|
def get_graph_grain_length(g: nx.DiGraph) -> dict[str, int]:
|
|
51
|
-
datasources: dict[str, Datasource] = nx.get_node_attributes(
|
|
52
|
-
|
|
62
|
+
datasources: dict[str, Datasource | list[Datasource]] = nx.get_node_attributes(
|
|
63
|
+
g, "datasource"
|
|
64
|
+
)
|
|
65
|
+
grain_length: dict[str, int] = {}
|
|
53
66
|
for node in g.nodes:
|
|
54
67
|
if node in datasources:
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
lookup = datasources[node]
|
|
69
|
+
if not isinstance(lookup, list):
|
|
70
|
+
lookup = [lookup]
|
|
71
|
+
assert isinstance(lookup, list)
|
|
72
|
+
grain_length[node] = sum(len(x.grain.components) for x in lookup)
|
|
73
|
+
return grain_length
|
|
57
74
|
|
|
58
75
|
|
|
59
76
|
def create_pruned_concept_graph(
|
|
60
77
|
g: nx.DiGraph,
|
|
61
78
|
all_concepts: List[Concept],
|
|
79
|
+
datasources: list[Datasource],
|
|
62
80
|
accept_partial: bool = False,
|
|
63
81
|
conditions: WhereClause | None = None,
|
|
64
82
|
) -> nx.DiGraph:
|
|
65
83
|
orig_g = g
|
|
66
84
|
g = g.copy()
|
|
85
|
+
|
|
86
|
+
union_options = get_union_sources(datasources, all_concepts)
|
|
87
|
+
for ds_list in union_options:
|
|
88
|
+
node_address = "ds~" + "-".join([x.name for x in ds_list])
|
|
89
|
+
common: set[Concept] = set.intersection(
|
|
90
|
+
*[set(x.output_concepts) for x in ds_list]
|
|
91
|
+
)
|
|
92
|
+
g.add_node(node_address, datasource=ds_list)
|
|
93
|
+
for c in common:
|
|
94
|
+
g.add_edge(node_address, concept_to_node(c))
|
|
95
|
+
|
|
67
96
|
target_addresses = set([c.address for c in all_concepts])
|
|
68
97
|
concepts: dict[str, Concept] = nx.get_node_attributes(orig_g, "concept")
|
|
69
|
-
|
|
98
|
+
datasource_map: dict[str, Datasource | list[Datasource]] = nx.get_node_attributes(
|
|
99
|
+
orig_g, "datasource"
|
|
100
|
+
)
|
|
70
101
|
relevant_concepts_pre = {
|
|
71
102
|
n: x.address
|
|
72
103
|
for n in g.nodes()
|
|
@@ -81,13 +112,13 @@ def create_pruned_concept_graph(
|
|
|
81
112
|
to_remove = []
|
|
82
113
|
for edge in g.edges:
|
|
83
114
|
if (
|
|
84
|
-
edge[0] in
|
|
115
|
+
edge[0] in datasource_map
|
|
85
116
|
and (pnodes := partial.get(edge[0], []))
|
|
86
117
|
and edge[1] in pnodes
|
|
87
118
|
):
|
|
88
119
|
to_remove.append(edge)
|
|
89
120
|
if (
|
|
90
|
-
edge[1] in
|
|
121
|
+
edge[1] in datasource_map
|
|
91
122
|
and (pnodes := partial.get(edge[1], []))
|
|
92
123
|
and edge[0] in pnodes
|
|
93
124
|
):
|
|
@@ -136,7 +167,9 @@ def create_pruned_concept_graph(
|
|
|
136
167
|
for edge in orig_g.edges():
|
|
137
168
|
if edge[0] in relevant and edge[1] in relevant:
|
|
138
169
|
g.add_edge(edge[0], edge[1])
|
|
139
|
-
|
|
170
|
+
# if we have no ds nodes at all, for non constant, we can't find it
|
|
171
|
+
if not any([n.startswith("ds~") for n in g.nodes]):
|
|
172
|
+
return None
|
|
140
173
|
return g
|
|
141
174
|
|
|
142
175
|
|
|
@@ -190,6 +223,54 @@ def resolve_subgraphs(
|
|
|
190
223
|
return pruned_subgraphs
|
|
191
224
|
|
|
192
225
|
|
|
226
|
+
def create_datasource_node(
|
|
227
|
+
datasource: Datasource,
|
|
228
|
+
all_concepts: List[Concept],
|
|
229
|
+
accept_partial: bool,
|
|
230
|
+
environment: Environment,
|
|
231
|
+
depth: int,
|
|
232
|
+
conditions: WhereClause | None = None,
|
|
233
|
+
) -> tuple[StrategyNode, bool]:
|
|
234
|
+
target_grain = Grain(components=all_concepts)
|
|
235
|
+
force_group = False
|
|
236
|
+
if not datasource.grain.issubset(target_grain):
|
|
237
|
+
force_group = True
|
|
238
|
+
partial_concepts = [
|
|
239
|
+
c.concept
|
|
240
|
+
for c in datasource.columns
|
|
241
|
+
if not c.is_complete and c.concept.address in all_concepts
|
|
242
|
+
]
|
|
243
|
+
partial_lcl = LooseConceptList(concepts=partial_concepts)
|
|
244
|
+
nullable_concepts = [
|
|
245
|
+
c.concept
|
|
246
|
+
for c in datasource.columns
|
|
247
|
+
if c.is_nullable and c.concept.address in all_concepts
|
|
248
|
+
]
|
|
249
|
+
nullable_lcl = LooseConceptList(concepts=nullable_concepts)
|
|
250
|
+
partial_is_full = conditions and (conditions == datasource.non_partial_for)
|
|
251
|
+
return (
|
|
252
|
+
SelectNode(
|
|
253
|
+
input_concepts=[c.concept for c in datasource.columns],
|
|
254
|
+
output_concepts=all_concepts,
|
|
255
|
+
environment=environment,
|
|
256
|
+
parents=[],
|
|
257
|
+
depth=depth,
|
|
258
|
+
partial_concepts=(
|
|
259
|
+
[] if partial_is_full else [c for c in all_concepts if c in partial_lcl]
|
|
260
|
+
),
|
|
261
|
+
nullable_concepts=[c for c in all_concepts if c in nullable_lcl],
|
|
262
|
+
accept_partial=accept_partial,
|
|
263
|
+
datasource=datasource,
|
|
264
|
+
grain=Grain(components=all_concepts),
|
|
265
|
+
conditions=datasource.where.conditional if datasource.where else None,
|
|
266
|
+
preexisting_conditions=(
|
|
267
|
+
conditions.conditional if partial_is_full and conditions else None
|
|
268
|
+
),
|
|
269
|
+
),
|
|
270
|
+
force_group,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
193
274
|
def create_select_node(
|
|
194
275
|
ds_name: str,
|
|
195
276
|
subgraph: list[str],
|
|
@@ -199,12 +280,11 @@ def create_select_node(
|
|
|
199
280
|
depth: int,
|
|
200
281
|
conditions: WhereClause | None = None,
|
|
201
282
|
) -> StrategyNode:
|
|
202
|
-
|
|
283
|
+
|
|
203
284
|
all_concepts = [
|
|
204
285
|
environment.concepts[extract_address(c)] for c in subgraph if c.startswith("c~")
|
|
205
286
|
]
|
|
206
287
|
|
|
207
|
-
all_lcl = LooseConceptList(concepts=all_concepts)
|
|
208
288
|
if all([c.derivation == PurposeLineage.CONSTANT for c in all_concepts]):
|
|
209
289
|
logger.info(
|
|
210
290
|
f"{padding(depth)}{LOGGER_PREFIX} All concepts {[x.address for x in all_concepts]} are constants, returning constant node"
|
|
@@ -213,7 +293,6 @@ def create_select_node(
|
|
|
213
293
|
output_concepts=all_concepts,
|
|
214
294
|
input_concepts=[],
|
|
215
295
|
environment=environment,
|
|
216
|
-
g=g,
|
|
217
296
|
parents=[],
|
|
218
297
|
depth=depth,
|
|
219
298
|
# no partial for constants
|
|
@@ -221,41 +300,44 @@ def create_select_node(
|
|
|
221
300
|
force_group=False,
|
|
222
301
|
)
|
|
223
302
|
|
|
224
|
-
datasource =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if
|
|
228
|
-
force_group =
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
303
|
+
datasource: dict[str, Datasource | list[Datasource]] = nx.get_node_attributes(
|
|
304
|
+
g, "datasource"
|
|
305
|
+
)[ds_name]
|
|
306
|
+
if isinstance(datasource, Datasource):
|
|
307
|
+
bcandidate, force_group = create_datasource_node(
|
|
308
|
+
datasource,
|
|
309
|
+
all_concepts,
|
|
310
|
+
accept_partial,
|
|
311
|
+
environment,
|
|
312
|
+
depth,
|
|
313
|
+
conditions=conditions,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
elif isinstance(datasource, list):
|
|
317
|
+
from trilogy.core.processing.nodes.union_node import UnionNode
|
|
318
|
+
|
|
319
|
+
force_group = False
|
|
320
|
+
parents = []
|
|
321
|
+
for x in datasource:
|
|
322
|
+
subnode, fg = create_datasource_node(
|
|
323
|
+
x,
|
|
324
|
+
all_concepts,
|
|
325
|
+
accept_partial,
|
|
326
|
+
environment,
|
|
327
|
+
depth,
|
|
328
|
+
conditions=conditions,
|
|
329
|
+
)
|
|
330
|
+
parents.append(subnode)
|
|
331
|
+
force_group = force_group or fg
|
|
332
|
+
bcandidate = UnionNode(
|
|
333
|
+
output_concepts=all_concepts,
|
|
334
|
+
input_concepts=all_concepts,
|
|
335
|
+
environment=environment,
|
|
336
|
+
parents=parents,
|
|
337
|
+
depth=depth,
|
|
338
|
+
)
|
|
339
|
+
else:
|
|
340
|
+
raise ValueError(f"Unknown datasource type {datasource}")
|
|
259
341
|
|
|
260
342
|
# we need to nest the group node one further
|
|
261
343
|
if force_group is True:
|
|
@@ -263,14 +345,11 @@ def create_select_node(
|
|
|
263
345
|
output_concepts=all_concepts,
|
|
264
346
|
input_concepts=all_concepts,
|
|
265
347
|
environment=environment,
|
|
266
|
-
g=g,
|
|
267
348
|
parents=[bcandidate],
|
|
268
349
|
depth=depth,
|
|
269
350
|
partial_concepts=bcandidate.partial_concepts,
|
|
270
351
|
nullable_concepts=bcandidate.nullable_concepts,
|
|
271
|
-
preexisting_conditions=
|
|
272
|
-
conditions.conditional if partial_is_full and conditions else None
|
|
273
|
-
),
|
|
352
|
+
preexisting_conditions=bcandidate.preexisting_conditions,
|
|
274
353
|
)
|
|
275
354
|
else:
|
|
276
355
|
candidate = bcandidate
|
|
@@ -292,7 +371,6 @@ def gen_select_merge_node(
|
|
|
292
371
|
output_concepts=constants,
|
|
293
372
|
input_concepts=[],
|
|
294
373
|
environment=environment,
|
|
295
|
-
g=g,
|
|
296
374
|
parents=[],
|
|
297
375
|
depth=depth,
|
|
298
376
|
partial_concepts=[],
|
|
@@ -300,7 +378,11 @@ def gen_select_merge_node(
|
|
|
300
378
|
)
|
|
301
379
|
for attempt in [False, True]:
|
|
302
380
|
pruned_concept_graph = create_pruned_concept_graph(
|
|
303
|
-
g,
|
|
381
|
+
g,
|
|
382
|
+
non_constant,
|
|
383
|
+
accept_partial=attempt,
|
|
384
|
+
conditions=conditions,
|
|
385
|
+
datasources=list(environment.datasources.values()),
|
|
304
386
|
)
|
|
305
387
|
if pruned_concept_graph:
|
|
306
388
|
logger.info(
|
|
@@ -321,7 +403,7 @@ def gen_select_merge_node(
|
|
|
321
403
|
create_select_node(
|
|
322
404
|
k,
|
|
323
405
|
subgraph,
|
|
324
|
-
g=
|
|
406
|
+
g=pruned_concept_graph,
|
|
325
407
|
accept_partial=accept_partial,
|
|
326
408
|
environment=environment,
|
|
327
409
|
depth=depth,
|
|
@@ -338,7 +420,6 @@ def gen_select_merge_node(
|
|
|
338
420
|
output_concepts=constants,
|
|
339
421
|
input_concepts=[],
|
|
340
422
|
environment=environment,
|
|
341
|
-
g=g,
|
|
342
423
|
parents=[],
|
|
343
424
|
depth=depth,
|
|
344
425
|
partial_concepts=[],
|
|
@@ -361,7 +442,6 @@ def gen_select_merge_node(
|
|
|
361
442
|
output_concepts=all_concepts,
|
|
362
443
|
input_concepts=non_constant,
|
|
363
444
|
environment=environment,
|
|
364
|
-
g=g,
|
|
365
445
|
depth=depth,
|
|
366
446
|
parents=parents,
|
|
367
447
|
preexisting_conditions=preexisting_conditions,
|
|
@@ -372,7 +452,6 @@ def gen_select_merge_node(
|
|
|
372
452
|
output_concepts=all_concepts,
|
|
373
453
|
input_concepts=all_concepts,
|
|
374
454
|
environment=environment,
|
|
375
|
-
g=g,
|
|
376
455
|
parents=[base],
|
|
377
456
|
depth=depth,
|
|
378
457
|
preexisting_conditions=preexisting_conditions,
|
{pytrilogy-0.0.2.49 → pytrilogy-0.0.2.50}/trilogy/core/processing/node_generators/unnest_node.py
RENAMED
|
@@ -46,7 +46,6 @@ def gen_unnest_node(
|
|
|
46
46
|
input_concepts=arguments + non_equivalent_optional,
|
|
47
47
|
output_concepts=[concept] + local_optional,
|
|
48
48
|
environment=environment,
|
|
49
|
-
g=g,
|
|
50
49
|
parents=([parent] if (arguments or local_optional) else []),
|
|
51
50
|
)
|
|
52
51
|
# we need to sometimes nest an unnest node,
|
|
@@ -56,7 +55,6 @@ def gen_unnest_node(
|
|
|
56
55
|
input_concepts=base.output_concepts,
|
|
57
56
|
output_concepts=base.output_concepts,
|
|
58
57
|
environment=environment,
|
|
59
|
-
g=g,
|
|
60
58
|
parents=[base],
|
|
61
59
|
preexisting_conditions=conditions.conditional if conditions else None,
|
|
62
60
|
)
|