pytrilogy 0.0.3.65__tar.gz → 0.0.3.67__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.65/pytrilogy.egg-info → pytrilogy-0.0.3.67}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_parsing.py +44 -1
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/exceptions.py +4 -4
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/models/author.py +17 -4
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/models/build.py +42 -4
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/models/environment.py +5 -6
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/concept_strategies_v3.py +15 -6
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/discovery_node_factory.py +4 -1
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/discovery_utility.py +1 -9
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/discovery_validation.py +17 -4
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/filter_node.py +25 -5
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/group_node.py +1 -1
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/node_merge_node.py +46 -31
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/select_merge_node.py +26 -24
- pytrilogy-0.0.3.67/trilogy/core/processing/node_generators/select_node.py +94 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/synonym_node.py +11 -7
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/base_node.py +12 -3
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/group_node.py +1 -1
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/query_processor.py +4 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/base.py +1 -1
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parsing/common.py +28 -3
- pytrilogy-0.0.3.67/trilogy/std/display.preql +6 -0
- pytrilogy-0.0.3.65/trilogy/core/processing/node_generators/select_node.py +0 -56
- pytrilogy-0.0.3.65/trilogy/std/display.preql +0 -3
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/README.md +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/setup.cfg +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/setup.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_failure.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_parsing_failures.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_user_functions.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/authoring/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/models/core.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/models/execute.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/discovery_loop.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/recursive_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/statements/author.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/utility.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parsing/parse_engine.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/parsing/trilogy.lark +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/std/net.preql +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/std/ranking.preql +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/std/report.preql +0 -0
- {pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/utility.py +0 -0
|
@@ -2,7 +2,7 @@ from trilogy import Dialects
|
|
|
2
2
|
from trilogy.constants import MagicConstants
|
|
3
3
|
from trilogy.core.enums import BooleanOperator, ComparisonOperator, Purpose
|
|
4
4
|
from trilogy.core.functions import argument_to_purpose, function_args_to_output_purpose
|
|
5
|
-
from trilogy.core.models.author import Comparison
|
|
5
|
+
from trilogy.core.models.author import Comparison, Conditional, SubselectComparison
|
|
6
6
|
from trilogy.core.models.core import (
|
|
7
7
|
DataType,
|
|
8
8
|
TupleWrapper,
|
|
@@ -16,6 +16,7 @@ from trilogy.core.models.environment import (
|
|
|
16
16
|
from trilogy.core.statements.author import SelectStatement, ShowStatement
|
|
17
17
|
from trilogy.core.statements.execute import ProcessedQuery
|
|
18
18
|
from trilogy.dialect.base import BaseDialect
|
|
19
|
+
from trilogy.parsing.common import atom_is_relevant
|
|
19
20
|
from trilogy.parsing.parse_engine import (
|
|
20
21
|
arg_to_datatype,
|
|
21
22
|
parse_text,
|
|
@@ -685,3 +686,45 @@ select x % 10 -> x_mod_10;
|
|
|
685
686
|
|
|
686
687
|
"""
|
|
687
688
|
)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def test_is_atom():
|
|
692
|
+
env = Environment()
|
|
693
|
+
|
|
694
|
+
env.parse(
|
|
695
|
+
"""
|
|
696
|
+
key x int;
|
|
697
|
+
auto x_sum <- sum(x);
|
|
698
|
+
"""
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
assert not atom_is_relevant(
|
|
702
|
+
Comparison(left=env.concepts["x_sum"], right=0, operator=ComparisonOperator.GT),
|
|
703
|
+
[env.concepts["x"]],
|
|
704
|
+
env,
|
|
705
|
+
)
|
|
706
|
+
assert atom_is_relevant(
|
|
707
|
+
Comparison(left=env.concepts["x"], right=0, operator=ComparisonOperator.GT),
|
|
708
|
+
[env.concepts["x_sum"]],
|
|
709
|
+
env,
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
assert not atom_is_relevant(
|
|
713
|
+
Conditional(left=env.concepts["x_sum"], right=0, operator=BooleanOperator.AND),
|
|
714
|
+
[env.concepts["x"]],
|
|
715
|
+
env,
|
|
716
|
+
)
|
|
717
|
+
assert atom_is_relevant(
|
|
718
|
+
Conditional(left=env.concepts["x"], right=0, operator=BooleanOperator.AND),
|
|
719
|
+
[env.concepts["x_sum"]],
|
|
720
|
+
env,
|
|
721
|
+
)
|
|
722
|
+
assert not atom_is_relevant(
|
|
723
|
+
SubselectComparison(
|
|
724
|
+
left=env.concepts["x_sum"],
|
|
725
|
+
right=env.concepts["x"],
|
|
726
|
+
operator=ComparisonOperator.IN,
|
|
727
|
+
),
|
|
728
|
+
[env.concepts["x_sum"]],
|
|
729
|
+
env,
|
|
730
|
+
)
|
|
@@ -8,7 +8,7 @@ class UndefinedConceptException(Exception):
|
|
|
8
8
|
self.suggestions = suggestions
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class FrozenEnvironmentException(Exception):
|
|
12
12
|
pass
|
|
13
13
|
|
|
14
14
|
|
|
@@ -16,15 +16,15 @@ class InvalidSyntaxException(Exception):
|
|
|
16
16
|
pass
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
class
|
|
19
|
+
class UnresolvableQueryException(Exception):
|
|
20
20
|
pass
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class
|
|
23
|
+
class NoDatasourceException(UnresolvableQueryException):
|
|
24
24
|
pass
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class AmbiguousRelationshipResolutionException(
|
|
27
|
+
class AmbiguousRelationshipResolutionException(UnresolvableQueryException):
|
|
28
28
|
def __init__(self, message, parents: List[set[str]]):
|
|
29
29
|
super().__init__(self, message)
|
|
30
30
|
self.message = message
|
|
@@ -271,6 +271,20 @@ class Conditional(Mergeable, ConceptArgs, Namespaced, DataTyped, BaseModel):
|
|
|
271
271
|
right: Expr
|
|
272
272
|
operator: BooleanOperator
|
|
273
273
|
|
|
274
|
+
@field_validator("left", mode="before")
|
|
275
|
+
@classmethod
|
|
276
|
+
def left_validator(cls, v, info: ValidationInfo):
|
|
277
|
+
if isinstance(v, Concept):
|
|
278
|
+
return v.reference
|
|
279
|
+
return v
|
|
280
|
+
|
|
281
|
+
@field_validator("right", mode="before")
|
|
282
|
+
@classmethod
|
|
283
|
+
def right_validator(cls, v, info: ValidationInfo):
|
|
284
|
+
if isinstance(v, Concept):
|
|
285
|
+
return v.reference
|
|
286
|
+
return v
|
|
287
|
+
|
|
274
288
|
def __add__(self, other) -> "Conditional":
|
|
275
289
|
if other is None:
|
|
276
290
|
return self
|
|
@@ -346,7 +360,6 @@ class Conditional(Mergeable, ConceptArgs, Namespaced, DataTyped, BaseModel):
|
|
|
346
360
|
|
|
347
361
|
@property
|
|
348
362
|
def concept_arguments(self) -> Sequence[ConceptRef]:
|
|
349
|
-
"""Return concepts directly referenced in where clause"""
|
|
350
363
|
output = []
|
|
351
364
|
output += get_concept_arguments(self.left)
|
|
352
365
|
output += get_concept_arguments(self.right)
|
|
@@ -575,11 +588,11 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
|
|
|
575
588
|
date,
|
|
576
589
|
Function,
|
|
577
590
|
ConceptRef,
|
|
578
|
-
|
|
591
|
+
Conditional,
|
|
579
592
|
DataType,
|
|
580
|
-
|
|
593
|
+
Comparison,
|
|
581
594
|
FunctionCallWrapper,
|
|
582
|
-
|
|
595
|
+
Parenthetical,
|
|
583
596
|
MagicConstants,
|
|
584
597
|
WindowItem,
|
|
585
598
|
AggregateWrapper,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC
|
|
4
|
+
from collections import defaultdict
|
|
4
5
|
from datetime import date, datetime
|
|
5
6
|
from functools import cached_property, singledispatchmethod
|
|
6
7
|
from typing import (
|
|
@@ -1466,6 +1467,20 @@ BuildExpr = (
|
|
|
1466
1467
|
BuildConcept.model_rebuild()
|
|
1467
1468
|
|
|
1468
1469
|
|
|
1470
|
+
def get_canonical_pseudonyms(environment: Environment) -> dict[str, set[str]]:
|
|
1471
|
+
roots: dict[str, set[str]] = defaultdict(set)
|
|
1472
|
+
for k, v in environment.concepts.items():
|
|
1473
|
+
roots[v.address].add(k)
|
|
1474
|
+
for x in v.pseudonyms:
|
|
1475
|
+
roots[v.address].add(x)
|
|
1476
|
+
for k, v in environment.alias_origin_lookup.items():
|
|
1477
|
+
lookup = environment.concepts[k].address
|
|
1478
|
+
roots[lookup].add(v.address)
|
|
1479
|
+
for x2 in v.pseudonyms:
|
|
1480
|
+
roots[lookup].add(x2)
|
|
1481
|
+
return roots
|
|
1482
|
+
|
|
1483
|
+
|
|
1469
1484
|
class Factory:
|
|
1470
1485
|
|
|
1471
1486
|
def __init__(
|
|
@@ -1479,6 +1494,7 @@ class Factory:
|
|
|
1479
1494
|
self.local_concepts: dict[str, BuildConcept] = (
|
|
1480
1495
|
{} if local_concepts is None else local_concepts
|
|
1481
1496
|
)
|
|
1497
|
+
self.pseudonym_map = get_canonical_pseudonyms(environment)
|
|
1482
1498
|
|
|
1483
1499
|
def instantiate_concept(
|
|
1484
1500
|
self,
|
|
@@ -1631,7 +1647,10 @@ class Factory:
|
|
|
1631
1647
|
|
|
1632
1648
|
@build.register
|
|
1633
1649
|
def _(self, base: CaseElse) -> BuildCaseElse:
|
|
1634
|
-
|
|
1650
|
+
expr: Concept | FuncArgs = base.expr
|
|
1651
|
+
if isinstance(expr, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1652
|
+
expr, _ = self.instantiate_concept(expr)
|
|
1653
|
+
return BuildCaseElse.model_construct(expr=self.build(expr))
|
|
1635
1654
|
|
|
1636
1655
|
@build.register
|
|
1637
1656
|
def _(self, base: Concept) -> BuildConcept:
|
|
@@ -1653,6 +1672,17 @@ class Factory:
|
|
|
1653
1672
|
)
|
|
1654
1673
|
is_aggregate = Concept.calculate_is_aggregate(build_lineage)
|
|
1655
1674
|
|
|
1675
|
+
# if this is a pseudonym, we need to look up the base address
|
|
1676
|
+
if base.address in self.environment.alias_origin_lookup:
|
|
1677
|
+
lookup_address = self.environment.concepts[base.address].address
|
|
1678
|
+
# map only to the canonical concept, not to other merged concepts
|
|
1679
|
+
base_pseudonyms = {lookup_address}
|
|
1680
|
+
else:
|
|
1681
|
+
base_pseudonyms = {
|
|
1682
|
+
x
|
|
1683
|
+
for x in self.pseudonym_map.get(base.address, set())
|
|
1684
|
+
if x != base.address
|
|
1685
|
+
}
|
|
1656
1686
|
rval = BuildConcept.model_construct(
|
|
1657
1687
|
name=base.name,
|
|
1658
1688
|
datatype=base.datatype,
|
|
@@ -1663,7 +1693,7 @@ class Factory:
|
|
|
1663
1693
|
namespace=base.namespace,
|
|
1664
1694
|
keys=base.keys,
|
|
1665
1695
|
modifiers=base.modifiers,
|
|
1666
|
-
pseudonyms=
|
|
1696
|
+
pseudonyms=base_pseudonyms,
|
|
1667
1697
|
## instantiated values
|
|
1668
1698
|
derivation=derivation,
|
|
1669
1699
|
granularity=granularity,
|
|
@@ -1685,14 +1715,22 @@ class Factory:
|
|
|
1685
1715
|
|
|
1686
1716
|
@build.register
|
|
1687
1717
|
def _(self, base: ColumnAssignment) -> BuildColumnAssignment:
|
|
1688
|
-
|
|
1718
|
+
address = base.concept.address
|
|
1719
|
+
fetched = (
|
|
1720
|
+
self.build(
|
|
1721
|
+
self.environment.alias_origin_lookup[address].with_grain(self.grain)
|
|
1722
|
+
)
|
|
1723
|
+
if address in self.environment.alias_origin_lookup
|
|
1724
|
+
else self.build(self.environment.concepts[address].with_grain(self.grain))
|
|
1725
|
+
)
|
|
1726
|
+
|
|
1689
1727
|
return BuildColumnAssignment.model_construct(
|
|
1690
1728
|
alias=(
|
|
1691
1729
|
self.build(base.alias)
|
|
1692
1730
|
if isinstance(base.alias, Function)
|
|
1693
1731
|
else base.alias
|
|
1694
1732
|
),
|
|
1695
|
-
concept=
|
|
1733
|
+
concept=fetched,
|
|
1696
1734
|
modifiers=base.modifiers,
|
|
1697
1735
|
)
|
|
1698
1736
|
|
|
@@ -362,9 +362,6 @@ class Environment(BaseModel):
|
|
|
362
362
|
and x.concept.address != deriv_lookup
|
|
363
363
|
]
|
|
364
364
|
assert len(datasource.columns) < clen
|
|
365
|
-
for x in datasource.columns:
|
|
366
|
-
logger.info(x)
|
|
367
|
-
|
|
368
365
|
return None
|
|
369
366
|
|
|
370
367
|
if existing and self.config.allow_duplicate_declaration:
|
|
@@ -607,15 +604,15 @@ class Environment(BaseModel):
|
|
|
607
604
|
)
|
|
608
605
|
persisted = f"{PERSISTED_CONCEPT_PREFIX}_" + new_persisted_concept.name
|
|
609
606
|
# override the current concept source to reflect that it's now coming from a datasource
|
|
607
|
+
base_pseudonyms = new_persisted_concept.pseudonyms or set()
|
|
608
|
+
original_pseudonyms = {*base_pseudonyms, new_persisted_concept.address}
|
|
610
609
|
if (
|
|
611
610
|
new_persisted_concept.metadata.concept_source
|
|
612
611
|
!= ConceptSource.PERSIST_STATEMENT
|
|
613
612
|
):
|
|
614
613
|
original_concept = new_persisted_concept.model_copy(
|
|
615
614
|
deep=True,
|
|
616
|
-
update={
|
|
617
|
-
"name": persisted,
|
|
618
|
-
},
|
|
615
|
+
update={"name": persisted, "pseudonyms": original_pseudonyms},
|
|
619
616
|
)
|
|
620
617
|
self.add_concept(
|
|
621
618
|
original_concept,
|
|
@@ -629,6 +626,7 @@ class Environment(BaseModel):
|
|
|
629
626
|
),
|
|
630
627
|
"derivation": Derivation.ROOT,
|
|
631
628
|
"purpose": new_persisted_concept.purpose,
|
|
629
|
+
"pseudonyms": {*original_pseudonyms, original_concept.address},
|
|
632
630
|
}
|
|
633
631
|
# purpose is used in derivation calculation
|
|
634
632
|
# which should be fixed, but we'll do in a followup
|
|
@@ -650,6 +648,7 @@ class Environment(BaseModel):
|
|
|
650
648
|
new_persisted_concept,
|
|
651
649
|
meta=meta,
|
|
652
650
|
)
|
|
651
|
+
|
|
653
652
|
return datasource
|
|
654
653
|
|
|
655
654
|
def delete_datasource(
|
|
@@ -224,6 +224,9 @@ def initialize_loop_context(
|
|
|
224
224
|
else:
|
|
225
225
|
|
|
226
226
|
completion_mandatory = mandatory_list
|
|
227
|
+
logger.info(
|
|
228
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Initialized loop context with mandatory list {[c.address for c in mandatory_list]} and completion mandatory {[c.address for c in completion_mandatory]}"
|
|
229
|
+
)
|
|
227
230
|
return LoopContext(
|
|
228
231
|
mandatory_list=mandatory_list,
|
|
229
232
|
environment=environment,
|
|
@@ -330,7 +333,7 @@ def check_for_early_exit(
|
|
|
330
333
|
return False
|
|
331
334
|
|
|
332
335
|
|
|
333
|
-
def generate_loop_completion(context: LoopContext, virtual) -> StrategyNode:
|
|
336
|
+
def generate_loop_completion(context: LoopContext, virtual: set[str]) -> StrategyNode:
|
|
334
337
|
condition_required = True
|
|
335
338
|
non_virtual = [c for c in context.completion_mandatory if c.address not in virtual]
|
|
336
339
|
non_virtual_output = [
|
|
@@ -367,10 +370,15 @@ def generate_loop_completion(context: LoopContext, virtual) -> StrategyNode:
|
|
|
367
370
|
output: StrategyNode = context.stack[0]
|
|
368
371
|
if non_virtual_different:
|
|
369
372
|
logger.info(
|
|
370
|
-
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Found different non-virtual output concepts ({non_virtual_difference_values}), removing condition injected values"
|
|
373
|
+
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Found different non-virtual output concepts ({non_virtual_difference_values}), removing condition injected values by setting outputs to {[x.address for x in output.output_concepts if x.address in non_virtual_output]}"
|
|
371
374
|
)
|
|
372
375
|
output.set_output_concepts(
|
|
373
|
-
[
|
|
376
|
+
[
|
|
377
|
+
x
|
|
378
|
+
for x in output.output_concepts
|
|
379
|
+
if x.address in non_virtual_output
|
|
380
|
+
or any(c in non_virtual_output for c in x.pseudonyms)
|
|
381
|
+
],
|
|
374
382
|
rebuild=False,
|
|
375
383
|
)
|
|
376
384
|
|
|
@@ -404,7 +412,7 @@ def generate_loop_completion(context: LoopContext, virtual) -> StrategyNode:
|
|
|
404
412
|
elif context.conditions:
|
|
405
413
|
output.preexisting_conditions = context.conditions.conditional
|
|
406
414
|
logger.info(
|
|
407
|
-
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Graph is connected, returning {type(output)} node partial {[c.address for c in output.partial_concepts]} with {context.conditions}"
|
|
415
|
+
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Graph is connected, returning {type(output)} node output {[x.address for x in output.usable_outputs]} partial {[c.address for c in output.partial_concepts]} with {context.conditions}"
|
|
408
416
|
)
|
|
409
417
|
if condition_required and context.conditions and non_virtual_different:
|
|
410
418
|
logger.info(
|
|
@@ -419,7 +427,7 @@ def generate_loop_completion(context: LoopContext, virtual) -> StrategyNode:
|
|
|
419
427
|
)
|
|
420
428
|
if result.required:
|
|
421
429
|
logger.info(
|
|
422
|
-
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Adding group node"
|
|
430
|
+
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Adding group node with outputs {[x.address for x in context.original_mandatory]}"
|
|
423
431
|
)
|
|
424
432
|
return GroupNode(
|
|
425
433
|
output_concepts=context.original_mandatory,
|
|
@@ -466,6 +474,7 @@ def _search_concepts(
|
|
|
466
474
|
)
|
|
467
475
|
|
|
468
476
|
while context.incomplete:
|
|
477
|
+
|
|
469
478
|
priority_concept = get_priority_concept(
|
|
470
479
|
context.mandatory_list,
|
|
471
480
|
context.attempted,
|
|
@@ -478,7 +487,7 @@ def _search_concepts(
|
|
|
478
487
|
candidates = [
|
|
479
488
|
c for c in context.mandatory_list if c.address != priority_concept.address
|
|
480
489
|
]
|
|
481
|
-
# the local conditions list may be
|
|
490
|
+
# the local conditions list may be overriden if we end up injecting conditions
|
|
482
491
|
candidate_list, local_conditions = generate_candidates_restrictive(
|
|
483
492
|
priority_concept,
|
|
484
493
|
candidates,
|
|
@@ -351,7 +351,10 @@ class RootNodeHandler:
|
|
|
351
351
|
extra = restrict_node_outputs_targets(expanded, root_targets, self.ctx.depth)
|
|
352
352
|
|
|
353
353
|
pseudonyms = [
|
|
354
|
-
x
|
|
354
|
+
x
|
|
355
|
+
for x in extra
|
|
356
|
+
if any(x.address in y.pseudonyms for y in root_targets)
|
|
357
|
+
and x.address not in root_targets
|
|
355
358
|
]
|
|
356
359
|
|
|
357
360
|
if pseudonyms:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.enums import Derivation
|
|
4
|
+
from trilogy.core.enums import Derivation
|
|
5
5
|
from trilogy.core.models.build import (
|
|
6
6
|
BuildConcept,
|
|
7
7
|
BuildRowsetItem,
|
|
@@ -52,14 +52,6 @@ def get_priority_concept(
|
|
|
52
52
|
# pass_two = [c for c in all_concepts+filter_only if c.address not in attempted_addresses]
|
|
53
53
|
for remaining_concept in (pass_one,):
|
|
54
54
|
priority = (
|
|
55
|
-
# find anything that needs no joins first, so we can exit early
|
|
56
|
-
[
|
|
57
|
-
c
|
|
58
|
-
for c in remaining_concept
|
|
59
|
-
if c.derivation == Derivation.CONSTANT
|
|
60
|
-
and c.granularity == Granularity.SINGLE_ROW
|
|
61
|
-
]
|
|
62
|
-
+
|
|
63
55
|
# then multiselects to remove them from scope
|
|
64
56
|
[c for c in remaining_concept if c.derivation == Derivation.MULTISELECT]
|
|
65
57
|
+
|
|
@@ -34,6 +34,9 @@ def validate_concept(
|
|
|
34
34
|
seen: set[str],
|
|
35
35
|
environment: BuildEnvironment,
|
|
36
36
|
):
|
|
37
|
+
# logger.debug(
|
|
38
|
+
# f"Validating concept {concept.address} with accept_partial={accept_partial}"
|
|
39
|
+
# )
|
|
37
40
|
found_map[str(node)].add(concept)
|
|
38
41
|
seen.add(concept.address)
|
|
39
42
|
if concept not in node.partial_concepts:
|
|
@@ -53,12 +56,21 @@ def validate_concept(
|
|
|
53
56
|
found_map[str(node)].add(concept)
|
|
54
57
|
for v_address in concept.pseudonyms:
|
|
55
58
|
if v_address in seen:
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
continue
|
|
60
|
+
if v_address in environment.alias_origin_lookup:
|
|
61
|
+
# logger.debug(
|
|
62
|
+
# f"Found alias origin for {v_address}: {environment.alias_origin_lookup[v_address]} mapped to {environment.concepts[v_address]}")
|
|
63
|
+
v = environment.alias_origin_lookup[v_address]
|
|
64
|
+
else:
|
|
65
|
+
v = environment.concepts[v_address]
|
|
66
|
+
|
|
58
67
|
if v.address in seen:
|
|
59
|
-
|
|
68
|
+
|
|
69
|
+
continue
|
|
70
|
+
|
|
60
71
|
if v.address == concept.address:
|
|
61
|
-
|
|
72
|
+
|
|
73
|
+
continue
|
|
62
74
|
validate_concept(
|
|
63
75
|
v,
|
|
64
76
|
node,
|
|
@@ -93,6 +105,7 @@ def validate_stack(
|
|
|
93
105
|
|
|
94
106
|
for concept in resolved.output_concepts:
|
|
95
107
|
if concept.address in resolved.hidden_concepts:
|
|
108
|
+
|
|
96
109
|
continue
|
|
97
110
|
|
|
98
111
|
validate_concept(
|
{pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
@@ -57,7 +57,13 @@ def build_parent_concepts(
|
|
|
57
57
|
local_optional: List[BuildConcept],
|
|
58
58
|
conditions: BuildWhereClause | None = None,
|
|
59
59
|
depth: int = 0,
|
|
60
|
-
)
|
|
60
|
+
) -> tuple[
|
|
61
|
+
list[BuildConcept],
|
|
62
|
+
list[tuple[BuildConcept, ...]],
|
|
63
|
+
list[BuildConcept],
|
|
64
|
+
bool,
|
|
65
|
+
bool,
|
|
66
|
+
]:
|
|
61
67
|
parent_row_concepts, parent_existence_concepts = resolve_filter_parent_concepts(
|
|
62
68
|
concept, environment
|
|
63
69
|
)
|
|
@@ -66,6 +72,10 @@ def build_parent_concepts(
|
|
|
66
72
|
filter_where = concept.lineage.where
|
|
67
73
|
|
|
68
74
|
same_filter_optional: list[BuildConcept] = []
|
|
75
|
+
# mypy struggled here? we shouldn't need explicit bools
|
|
76
|
+
global_filter_is_local_filter: bool = (
|
|
77
|
+
True if (conditions and conditions == filter_where) else False
|
|
78
|
+
)
|
|
69
79
|
|
|
70
80
|
for x in local_optional:
|
|
71
81
|
if isinstance(x.lineage, FILTER_TYPES):
|
|
@@ -79,7 +89,7 @@ def build_parent_concepts(
|
|
|
79
89
|
parent_row_concepts.append(arg)
|
|
80
90
|
same_filter_optional.append(x)
|
|
81
91
|
continue
|
|
82
|
-
elif
|
|
92
|
+
elif global_filter_is_local_filter:
|
|
83
93
|
same_filter_optional.append(x)
|
|
84
94
|
|
|
85
95
|
# sometimes, it's okay to include other local optional above the filter
|
|
@@ -100,6 +110,7 @@ def build_parent_concepts(
|
|
|
100
110
|
parent_existence_concepts,
|
|
101
111
|
same_filter_optional,
|
|
102
112
|
is_optimized_pushdown,
|
|
113
|
+
global_filter_is_local_filter,
|
|
103
114
|
)
|
|
104
115
|
|
|
105
116
|
|
|
@@ -152,6 +163,7 @@ def gen_filter_node(
|
|
|
152
163
|
parent_existence_concepts,
|
|
153
164
|
same_filter_optional,
|
|
154
165
|
optimized_pushdown,
|
|
166
|
+
global_filter_is_local_filter,
|
|
155
167
|
) = build_parent_concepts(
|
|
156
168
|
concept,
|
|
157
169
|
environment=environment,
|
|
@@ -187,7 +199,13 @@ def gen_filter_node(
|
|
|
187
199
|
f"{padding(depth)}{LOGGER_PREFIX} filter node row parents {[x.address for x in parent_row_concepts]} could not be found"
|
|
188
200
|
)
|
|
189
201
|
return None
|
|
190
|
-
|
|
202
|
+
if global_filter_is_local_filter:
|
|
203
|
+
logger.info(
|
|
204
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter node conditions match global conditions adding row parent {row_parent.output_concepts} with condition {where.conditional}"
|
|
205
|
+
)
|
|
206
|
+
row_parent.add_parents(core_parent_nodes)
|
|
207
|
+
row_parent.set_output_concepts([concept] + local_optional)
|
|
208
|
+
return row_parent
|
|
191
209
|
if optimized_pushdown:
|
|
192
210
|
logger.info(
|
|
193
211
|
f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional} across {[concept] + same_filter_optional + row_parent.output_concepts} "
|
|
@@ -211,7 +229,8 @@ def gen_filter_node(
|
|
|
211
229
|
parent = row_parent
|
|
212
230
|
parent.add_output_concepts([concept] + same_filter_optional)
|
|
213
231
|
parent.add_parents(core_parent_nodes)
|
|
214
|
-
parent.
|
|
232
|
+
if not parent.preexisting_conditions == where.conditional:
|
|
233
|
+
parent.add_condition(where.conditional)
|
|
215
234
|
parent.add_existence_concepts(flattened_existence, False)
|
|
216
235
|
parent.grain = BuildGrain.from_concepts(
|
|
217
236
|
parent.output_concepts,
|
|
@@ -225,7 +244,8 @@ def gen_filter_node(
|
|
|
225
244
|
parents_for_grain = [
|
|
226
245
|
x.lineage.content
|
|
227
246
|
for x in filters
|
|
228
|
-
if isinstance(x.lineage
|
|
247
|
+
if isinstance(x.lineage, BuildFilterItem)
|
|
248
|
+
and isinstance(x.lineage.content, BuildConcept)
|
|
229
249
|
]
|
|
230
250
|
filter_node = FilterNode(
|
|
231
251
|
input_concepts=unique(
|
{pytrilogy-0.0.3.65 → pytrilogy-0.0.3.67}/trilogy/core/processing/node_generators/group_node.py
RENAMED
|
@@ -133,7 +133,7 @@ def gen_group_node(
|
|
|
133
133
|
)
|
|
134
134
|
return group_node
|
|
135
135
|
missing_optional = [
|
|
136
|
-
x.address for x in local_optional if x.address not in group_node.
|
|
136
|
+
x.address for x in local_optional if x.address not in group_node.usable_outputs
|
|
137
137
|
]
|
|
138
138
|
if not missing_optional:
|
|
139
139
|
logger.info(
|