pytrilogy 0.0.2.56__tar.gz → 0.0.2.58__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.56/pytrilogy.egg-info → pytrilogy-0.0.2.58}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_models.py +4 -2
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_select.py +1 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/functions.py +2 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/models.py +38 -30
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/optimization.py +3 -6
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/concept_strategies_v3.py +21 -15
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/basic_node.py +4 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/common.py +1 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/group_to_node.py +10 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/multiselect_node.py +16 -18
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/rowset_node.py +5 -2
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/select_merge_node.py +8 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/base_node.py +21 -11
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/group_node.py +42 -45
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/merge_node.py +3 -2
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/select_node_v2.py +1 -1
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/utility.py +6 -7
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/query_processor.py +2 -2
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/base.py +9 -6
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/utility.py +5 -2
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/README.md +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/pyproject.toml +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/setup.cfg +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/setup.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_show.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parsing/common.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parsing/parse_engine.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/parsing/trilogy.lark +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/scripts/trilogy.py +0 -0
|
@@ -147,12 +147,14 @@ def test_select(test_environment: Environment):
|
|
|
147
147
|
pid = test_environment.concepts["product_id"]
|
|
148
148
|
cid = test_environment.concepts["category_id"]
|
|
149
149
|
cname = test_environment.concepts["category_name"]
|
|
150
|
-
x = SelectStatement(
|
|
150
|
+
x = SelectStatement(
|
|
151
|
+
selection=[oid, pid, cid, cname], grain=Grain(components=[oid, pid, cid])
|
|
152
|
+
)
|
|
151
153
|
ds = x.to_datasource(
|
|
152
154
|
test_environment.namespace, "test", address=Address(location="test")
|
|
153
155
|
)
|
|
154
156
|
|
|
155
|
-
assert ds.grain == Grain(components=[oid, pid, cid])
|
|
157
|
+
assert ds.grain.components == Grain(components=[oid, pid, cid]).components
|
|
156
158
|
|
|
157
159
|
|
|
158
160
|
def test_undefined(test_environment: Environment):
|
|
@@ -123,7 +123,7 @@ def test_modifiers():
|
|
|
123
123
|
;"""
|
|
124
124
|
env, parsed = parse(q1)
|
|
125
125
|
select: SelectStatement = parsed[-1]
|
|
126
|
-
assert select.hidden_components == [env.concepts["b"]]
|
|
126
|
+
assert select.hidden_components == set([env.concepts["b"].address])
|
|
127
127
|
assert select.output_components == [env.concepts["a"], env.concepts["b"]]
|
|
128
128
|
query = process_query(statement=select, environment=env)
|
|
129
129
|
|
|
@@ -127,10 +127,11 @@ def Unnest(args: list[Concept]) -> Function:
|
|
|
127
127
|
|
|
128
128
|
def Group(args: list[Concept]) -> Function:
|
|
129
129
|
output = args[0]
|
|
130
|
+
datatype = arg_to_datatype(output)
|
|
130
131
|
return Function(
|
|
131
132
|
operator=FunctionType.GROUP,
|
|
132
133
|
arguments=args,
|
|
133
|
-
output_datatype=
|
|
134
|
+
output_datatype=datatype,
|
|
134
135
|
output_purpose=Purpose.PROPERTY,
|
|
135
136
|
arg_count=-1,
|
|
136
137
|
)
|
|
@@ -923,9 +923,16 @@ class Grain(Namespaced, BaseModel):
|
|
|
923
923
|
if not self.where_clause:
|
|
924
924
|
where = other.where_clause
|
|
925
925
|
elif not other.where_clause == self.where_clause:
|
|
926
|
-
|
|
927
|
-
|
|
926
|
+
where = WhereClause(
|
|
927
|
+
conditional=Conditional(
|
|
928
|
+
left=self.where_clause.conditional,
|
|
929
|
+
right=other.where_clause.conditional,
|
|
930
|
+
operator=BooleanOperator.AND,
|
|
931
|
+
)
|
|
928
932
|
)
|
|
933
|
+
# raise NotImplementedError(
|
|
934
|
+
# f"Cannot merge grains with where clauses, self {self.where_clause} other {other.where_clause}"
|
|
935
|
+
# )
|
|
929
936
|
return Grain(
|
|
930
937
|
components=self.components.union(other.components), where_clause=where
|
|
931
938
|
)
|
|
@@ -1863,11 +1870,11 @@ class SelectStatement(HasUUID, Mergeable, Namespaced, SelectTypeMixin, BaseModel
|
|
|
1863
1870
|
return output
|
|
1864
1871
|
|
|
1865
1872
|
@property
|
|
1866
|
-
def hidden_components(self) ->
|
|
1867
|
-
output =
|
|
1873
|
+
def hidden_components(self) -> set[str]:
|
|
1874
|
+
output = set()
|
|
1868
1875
|
for item in self.selection:
|
|
1869
1876
|
if isinstance(item, SelectItem) and Modifier.HIDDEN in item.modifiers:
|
|
1870
|
-
output.
|
|
1877
|
+
output.add(item.output.address)
|
|
1871
1878
|
return output
|
|
1872
1879
|
|
|
1873
1880
|
@property
|
|
@@ -2097,10 +2104,10 @@ class MultiSelectStatement(HasUUID, SelectTypeMixin, Mergeable, Namespaced, Base
|
|
|
2097
2104
|
|
|
2098
2105
|
@computed_field # type: ignore
|
|
2099
2106
|
@cached_property
|
|
2100
|
-
def hidden_components(self) ->
|
|
2101
|
-
output =
|
|
2107
|
+
def hidden_components(self) -> set[str]:
|
|
2108
|
+
output: set[str] = set()
|
|
2102
2109
|
for select in self.selects:
|
|
2103
|
-
output
|
|
2110
|
+
output = output.union(select.hidden_components)
|
|
2104
2111
|
return output
|
|
2105
2112
|
|
|
2106
2113
|
|
|
@@ -2182,6 +2189,10 @@ class Datasource(HasUUID, Namespaced, BaseModel):
|
|
|
2182
2189
|
def duplicate(self) -> Datasource:
|
|
2183
2190
|
return self.model_copy(deep=True)
|
|
2184
2191
|
|
|
2192
|
+
@property
|
|
2193
|
+
def hidden_concepts(self) -> List[Concept]:
|
|
2194
|
+
return []
|
|
2195
|
+
|
|
2185
2196
|
def merge_concept(
|
|
2186
2197
|
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
2187
2198
|
):
|
|
@@ -2254,17 +2265,7 @@ class Datasource(HasUUID, Namespaced, BaseModel):
|
|
|
2254
2265
|
@field_validator("grain", mode="before")
|
|
2255
2266
|
@classmethod
|
|
2256
2267
|
def grain_enforcement(cls, v: Grain, info: ValidationInfo):
|
|
2257
|
-
values = info.data
|
|
2258
2268
|
grain: Grain = safe_grain(v)
|
|
2259
|
-
if not grain.components:
|
|
2260
|
-
columns: List[ColumnAssignment] = values.get("columns", [])
|
|
2261
|
-
grain = Grain.from_concepts(
|
|
2262
|
-
[
|
|
2263
|
-
c.concept.with_grain(Grain())
|
|
2264
|
-
for c in columns
|
|
2265
|
-
if c.concept.purpose == Purpose.KEY
|
|
2266
|
-
]
|
|
2267
|
-
)
|
|
2268
2269
|
return grain
|
|
2269
2270
|
|
|
2270
2271
|
def add_column(
|
|
@@ -2507,7 +2508,7 @@ class QueryDatasource(BaseModel):
|
|
|
2507
2508
|
filter_concepts: List[Concept] = Field(default_factory=list)
|
|
2508
2509
|
source_type: SourceType = SourceType.SELECT
|
|
2509
2510
|
partial_concepts: List[Concept] = Field(default_factory=list)
|
|
2510
|
-
hidden_concepts:
|
|
2511
|
+
hidden_concepts: set[str] = Field(default_factory=set)
|
|
2511
2512
|
nullable_concepts: List[Concept] = Field(default_factory=list)
|
|
2512
2513
|
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
2513
2514
|
force_group: bool | None = None
|
|
@@ -2659,10 +2660,10 @@ class QueryDatasource(BaseModel):
|
|
|
2659
2660
|
final_source_map[k] = set(
|
|
2660
2661
|
merged_datasources.get(x.safe_identifier, x) for x in list(v)
|
|
2661
2662
|
)
|
|
2662
|
-
self_hidden = self.hidden_concepts or
|
|
2663
|
-
other_hidden = other.hidden_concepts or
|
|
2663
|
+
self_hidden: set[str] = self.hidden_concepts or set()
|
|
2664
|
+
other_hidden: set[str] = other.hidden_concepts or set()
|
|
2664
2665
|
# hidden is the minimum overlapping set
|
|
2665
|
-
hidden =
|
|
2666
|
+
hidden = self_hidden.intersection(other_hidden)
|
|
2666
2667
|
qds = QueryDatasource(
|
|
2667
2668
|
input_concepts=unique(
|
|
2668
2669
|
self.input_concepts + other.input_concepts, "address"
|
|
@@ -2760,7 +2761,7 @@ class CTE(BaseModel):
|
|
|
2760
2761
|
partial_concepts: List[Concept] = Field(default_factory=list)
|
|
2761
2762
|
nullable_concepts: List[Concept] = Field(default_factory=list)
|
|
2762
2763
|
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
2763
|
-
hidden_concepts:
|
|
2764
|
+
hidden_concepts: set[str] = Field(default_factory=set)
|
|
2764
2765
|
order_by: Optional[OrderBy] = None
|
|
2765
2766
|
limit: Optional[int] = None
|
|
2766
2767
|
base_name_override: Optional[str] = None
|
|
@@ -2946,10 +2947,10 @@ class CTE(BaseModel):
|
|
|
2946
2947
|
f" {self.name} {other.name} conditions {self.condition} {other.condition}"
|
|
2947
2948
|
)
|
|
2948
2949
|
raise ValueError(error)
|
|
2949
|
-
mutually_hidden =
|
|
2950
|
+
mutually_hidden = set()
|
|
2950
2951
|
for concept in self.hidden_concepts:
|
|
2951
|
-
if concept
|
|
2952
|
-
mutually_hidden.
|
|
2952
|
+
if concept in other.hidden_concepts:
|
|
2953
|
+
mutually_hidden.add(concept)
|
|
2953
2954
|
self.partial_concepts = unique(
|
|
2954
2955
|
self.partial_concepts + other.partial_concepts, "address"
|
|
2955
2956
|
)
|
|
@@ -3073,12 +3074,18 @@ class CTE(BaseModel):
|
|
|
3073
3074
|
assert isinstance(c.lineage, RowsetItem)
|
|
3074
3075
|
return check_is_not_in_group(c.lineage.content)
|
|
3075
3076
|
if c.derivation == PurposeLineage.CONSTANT:
|
|
3076
|
-
return
|
|
3077
|
+
return True
|
|
3077
3078
|
if c.purpose == Purpose.METRIC:
|
|
3078
3079
|
return True
|
|
3079
|
-
|
|
3080
|
+
|
|
3081
|
+
if c.derivation == PurposeLineage.BASIC and c.lineage:
|
|
3080
3082
|
if all([check_is_not_in_group(x) for x in c.lineage.concept_arguments]):
|
|
3081
3083
|
return True
|
|
3084
|
+
if (
|
|
3085
|
+
isinstance(c.lineage, Function)
|
|
3086
|
+
and c.lineage.operator == FunctionType.GROUP
|
|
3087
|
+
):
|
|
3088
|
+
return check_is_not_in_group(c.lineage.concept_arguments[0])
|
|
3082
3089
|
return False
|
|
3083
3090
|
|
|
3084
3091
|
return (
|
|
@@ -3125,7 +3132,7 @@ class UnionCTE(BaseModel):
|
|
|
3125
3132
|
operator: str = "UNION ALL"
|
|
3126
3133
|
order_by: Optional[OrderBy] = None
|
|
3127
3134
|
limit: Optional[int] = None
|
|
3128
|
-
hidden_concepts:
|
|
3135
|
+
hidden_concepts: set[str] = Field(default_factory=set)
|
|
3129
3136
|
partial_concepts: list[Concept] = Field(default_factory=list)
|
|
3130
3137
|
existence_source_map: Dict[str, list[str]] = Field(default_factory=dict)
|
|
3131
3138
|
|
|
@@ -3756,6 +3763,7 @@ class Environment(BaseModel):
|
|
|
3756
3763
|
for k, v in self.concepts.items():
|
|
3757
3764
|
if v.address == target.address:
|
|
3758
3765
|
v.pseudonyms.add(source.address)
|
|
3766
|
+
|
|
3759
3767
|
if v.address == source.address:
|
|
3760
3768
|
replacements[k] = target
|
|
3761
3769
|
v.pseudonyms.add(target.address)
|
|
@@ -4496,7 +4504,7 @@ class ProcessedQuery(BaseModel):
|
|
|
4496
4504
|
base: CTE | UnionCTE
|
|
4497
4505
|
joins: List[Join]
|
|
4498
4506
|
grain: Grain
|
|
4499
|
-
hidden_columns:
|
|
4507
|
+
hidden_columns: set[str] = Field(default_factory=set)
|
|
4500
4508
|
limit: Optional[int] = None
|
|
4501
4509
|
where_clause: Optional[WhereClause] = None
|
|
4502
4510
|
having_clause: Optional[HavingClause] = None
|
|
@@ -136,15 +136,12 @@ def is_direct_return_eligible(cte: CTE | UnionCTE) -> CTE | UnionCTE | None:
|
|
|
136
136
|
|
|
137
137
|
assert isinstance(cte, CTE)
|
|
138
138
|
derived_concepts = [
|
|
139
|
-
c
|
|
140
|
-
for c in cte.source.output_concepts + cte.source.hidden_concepts
|
|
141
|
-
if c not in cte.source.input_concepts
|
|
139
|
+
c for c in cte.source.output_concepts if c not in cte.source.input_concepts
|
|
142
140
|
]
|
|
143
141
|
|
|
144
142
|
parent_derived_concepts = [
|
|
145
143
|
c
|
|
146
144
|
for c in direct_parent.source.output_concepts
|
|
147
|
-
+ direct_parent.source.hidden_concepts
|
|
148
145
|
if c not in direct_parent.source.input_concepts
|
|
149
146
|
]
|
|
150
147
|
condition_arguments = cte.condition.row_arguments if cte.condition else []
|
|
@@ -180,8 +177,8 @@ def optimize_ctes(
|
|
|
180
177
|
):
|
|
181
178
|
direct_parent.order_by = root_cte.order_by
|
|
182
179
|
direct_parent.limit = root_cte.limit
|
|
183
|
-
direct_parent.hidden_concepts = (
|
|
184
|
-
|
|
180
|
+
direct_parent.hidden_concepts = root_cte.hidden_concepts.union(
|
|
181
|
+
direct_parent.hidden_concepts
|
|
185
182
|
)
|
|
186
183
|
if root_cte.condition:
|
|
187
184
|
if direct_parent.condition:
|
|
@@ -539,11 +539,14 @@ def validate_concept(
|
|
|
539
539
|
found_addresses.add(concept.address)
|
|
540
540
|
found_map[str(node)].add(concept)
|
|
541
541
|
for v_address in concept.pseudonyms:
|
|
542
|
+
if v_address in seen:
|
|
543
|
+
return
|
|
542
544
|
v = environment.concepts[v_address]
|
|
543
|
-
if v
|
|
545
|
+
if v.address in seen:
|
|
544
546
|
return
|
|
545
|
-
if v
|
|
547
|
+
if v.address == concept.address:
|
|
546
548
|
return
|
|
549
|
+
|
|
547
550
|
validate_concept(
|
|
548
551
|
v,
|
|
549
552
|
node,
|
|
@@ -577,7 +580,7 @@ def validate_stack(
|
|
|
577
580
|
resolved = node.resolve()
|
|
578
581
|
|
|
579
582
|
for concept in resolved.output_concepts:
|
|
580
|
-
if concept in resolved.hidden_concepts:
|
|
583
|
+
if concept.address in resolved.hidden_concepts:
|
|
581
584
|
continue
|
|
582
585
|
validate_concept(
|
|
583
586
|
concept,
|
|
@@ -988,17 +991,20 @@ def source_query_concepts(
|
|
|
988
991
|
raise ValueError(
|
|
989
992
|
f"Could not resolve conections between {error_strings} from environment graph."
|
|
990
993
|
)
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
],
|
|
995
|
-
input_concepts=[
|
|
996
|
-
x for x in root.output_concepts if x.address not in root.hidden_concepts
|
|
997
|
-
],
|
|
994
|
+
final = [x for x in root.output_concepts if x.address not in root.hidden_concepts]
|
|
995
|
+
if GroupNode.check_if_required(
|
|
996
|
+
downstream_concepts=final,
|
|
997
|
+
parents=[root.resolve()],
|
|
998
998
|
environment=environment,
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
999
|
+
).required:
|
|
1000
|
+
candidate: StrategyNode = GroupNode(
|
|
1001
|
+
output_concepts=final,
|
|
1002
|
+
input_concepts=final,
|
|
1003
|
+
environment=environment,
|
|
1004
|
+
parents=[root],
|
|
1005
|
+
partial_concepts=root.partial_concepts,
|
|
1006
|
+
)
|
|
1007
|
+
else:
|
|
1008
|
+
candidate = root
|
|
1009
|
+
|
|
1004
1010
|
return candidate
|
{pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/basic_node.py
RENAMED
|
@@ -13,6 +13,7 @@ from trilogy.core.processing.node_generators.common import (
|
|
|
13
13
|
resolve_function_parent_concepts,
|
|
14
14
|
)
|
|
15
15
|
from trilogy.core.processing.nodes import History, StrategyNode
|
|
16
|
+
from trilogy.utility import unique
|
|
16
17
|
|
|
17
18
|
LOGGER_PREFIX = "[GEN_BASIC_NODE]"
|
|
18
19
|
|
|
@@ -65,7 +66,9 @@ def gen_basic_node(
|
|
|
65
66
|
non_equivalent_optional = [
|
|
66
67
|
x for x in local_optional if x not in equivalent_optional
|
|
67
68
|
]
|
|
68
|
-
all_parents =
|
|
69
|
+
all_parents: list[Concept] = unique(
|
|
70
|
+
parent_concepts + non_equivalent_optional, "address"
|
|
71
|
+
)
|
|
69
72
|
logger.info(
|
|
70
73
|
f"{depth_prefix}{LOGGER_PREFIX} Fetching parents {[x.address for x in all_parents]}"
|
|
71
74
|
)
|
|
@@ -208,7 +208,7 @@ def gen_enrichment_node(
|
|
|
208
208
|
non_hidden = [
|
|
209
209
|
x
|
|
210
210
|
for x in base_node.output_concepts
|
|
211
|
-
if x.address not in
|
|
211
|
+
if x.address not in base_node.hidden_concepts
|
|
212
212
|
]
|
|
213
213
|
return MergeNode(
|
|
214
214
|
input_concepts=unique(join_keys + extra_required + non_hidden, "address"),
|
{pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/group_to_node.py
RENAMED
|
@@ -26,6 +26,7 @@ def gen_group_to_node(
|
|
|
26
26
|
# aggregates MUST always group to the proper grain
|
|
27
27
|
if not isinstance(concept.lineage, Function):
|
|
28
28
|
raise SyntaxError("Group to should have function lineage")
|
|
29
|
+
group_arg = concept.lineage.arguments[0]
|
|
29
30
|
parent_concepts: List[Concept] = concept.lineage.concept_arguments
|
|
30
31
|
logger.info(
|
|
31
32
|
f"{padding(depth)}{LOGGER_PREFIX} group by node has required parents {[x.address for x in parent_concepts]}"
|
|
@@ -47,6 +48,13 @@ def gen_group_to_node(
|
|
|
47
48
|
environment=environment,
|
|
48
49
|
parents=parents,
|
|
49
50
|
depth=depth,
|
|
51
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
52
|
+
hidden_concepts=set(
|
|
53
|
+
[group_arg.address]
|
|
54
|
+
if isinstance(group_arg, Concept)
|
|
55
|
+
and group_arg.address not in local_optional
|
|
56
|
+
else []
|
|
57
|
+
),
|
|
50
58
|
)
|
|
51
59
|
|
|
52
60
|
# early exit if no optional
|
|
@@ -62,6 +70,7 @@ def gen_group_to_node(
|
|
|
62
70
|
g=g,
|
|
63
71
|
depth=depth + 1,
|
|
64
72
|
history=history,
|
|
73
|
+
conditions=conditions,
|
|
65
74
|
)
|
|
66
75
|
if not enrich_node:
|
|
67
76
|
logger.info(
|
|
@@ -83,4 +92,5 @@ def gen_group_to_node(
|
|
|
83
92
|
],
|
|
84
93
|
whole_grain=True,
|
|
85
94
|
depth=depth,
|
|
95
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
86
96
|
)
|
|
@@ -69,6 +69,7 @@ def gen_multiselect_node(
|
|
|
69
69
|
lineage: MultiSelectStatement = concept.lineage
|
|
70
70
|
|
|
71
71
|
base_parents: List[StrategyNode] = []
|
|
72
|
+
partial = []
|
|
72
73
|
for select in lineage.selects:
|
|
73
74
|
snode: StrategyNode = source_concepts(
|
|
74
75
|
mandatory_list=select.output_components,
|
|
@@ -103,6 +104,9 @@ def gen_multiselect_node(
|
|
|
103
104
|
for mc in merge_concepts:
|
|
104
105
|
assert mc in snode.resolve().output_concepts
|
|
105
106
|
base_parents.append(snode)
|
|
107
|
+
if select.where_clause:
|
|
108
|
+
for item in select.output_components:
|
|
109
|
+
partial.append(item)
|
|
106
110
|
|
|
107
111
|
node_joins = extra_align_joins(lineage, base_parents)
|
|
108
112
|
node = MergeNode(
|
|
@@ -112,35 +116,28 @@ def gen_multiselect_node(
|
|
|
112
116
|
depth=depth,
|
|
113
117
|
parents=base_parents,
|
|
114
118
|
node_joins=node_joins,
|
|
119
|
+
hidden_concepts=set([x for y in base_parents for x in y.hidden_concepts]),
|
|
115
120
|
)
|
|
116
121
|
|
|
117
122
|
enrichment = set([x.address for x in local_optional])
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
multiselect_relevant = [
|
|
120
125
|
x
|
|
121
126
|
for x in lineage.derived_concepts
|
|
122
127
|
if x.address == concept.address or x.address in enrichment
|
|
123
128
|
]
|
|
124
|
-
additional_relevant = [
|
|
125
|
-
x for x in select.output_components if x.address in enrichment
|
|
126
|
-
]
|
|
129
|
+
additional_relevant = [x for x in node.output_concepts if x.address in enrichment]
|
|
127
130
|
# add in other other concepts
|
|
128
|
-
for item in rowset_relevant:
|
|
129
|
-
node.output_concepts.append(item)
|
|
130
|
-
for item in additional_relevant:
|
|
131
|
-
node.output_concepts.append(item)
|
|
132
|
-
if select.where_clause:
|
|
133
|
-
for item in additional_relevant:
|
|
134
|
-
node.partial_concepts.append(item)
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
node.resolution_cache = node._resolve()
|
|
132
|
+
node.set_output_concepts(multiselect_relevant + additional_relevant)
|
|
138
133
|
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
134
|
+
# node.add_partial_concepts(partial)
|
|
135
|
+
# if select.where_clause:
|
|
136
|
+
# for item in additional_relevant:
|
|
137
|
+
# node.partial_concepts.append(item)
|
|
138
|
+
node.grain = Grain.from_concepts(node.output_concepts, environment=environment)
|
|
139
|
+
node.rebuild_cache()
|
|
140
|
+
# we need a better API for refreshing a nodes QDS
|
|
144
141
|
possible_joins = concept_to_relevant_joins(additional_relevant)
|
|
145
142
|
if not local_optional:
|
|
146
143
|
logger.info(
|
|
@@ -159,6 +156,7 @@ def gen_multiselect_node(
|
|
|
159
156
|
f"{padding(depth)}{LOGGER_PREFIX} all enriched concepts returned from base rowset node; exiting early"
|
|
160
157
|
)
|
|
161
158
|
return node
|
|
159
|
+
|
|
162
160
|
enrich_node: MergeNode = source_concepts( # this fetches the parent + join keys
|
|
163
161
|
# to then connect to the rest of the query
|
|
164
162
|
mandatory_list=additional_relevant + local_optional,
|
{pytrilogy-0.0.2.56 → pytrilogy-0.0.2.58}/trilogy/core/processing/node_generators/rowset_node.py
RENAMED
|
@@ -47,7 +47,7 @@ def gen_rowset_node(
|
|
|
47
47
|
return None
|
|
48
48
|
enrichment = set([x.address for x in local_optional])
|
|
49
49
|
rowset_relevant = [x for x in rowset.derived_concepts]
|
|
50
|
-
select_hidden =
|
|
50
|
+
select_hidden = select.hidden_components
|
|
51
51
|
rowset_hidden = [
|
|
52
52
|
x
|
|
53
53
|
for x in rowset.derived_concepts
|
|
@@ -80,7 +80,9 @@ def gen_rowset_node(
|
|
|
80
80
|
for x in node.output_concepts
|
|
81
81
|
if x.address
|
|
82
82
|
not in [
|
|
83
|
-
y
|
|
83
|
+
y
|
|
84
|
+
for y in node.hidden_concepts
|
|
85
|
+
if environment.concepts[y].derivation != PurposeLineage.ROWSET
|
|
84
86
|
]
|
|
85
87
|
],
|
|
86
88
|
)
|
|
@@ -103,6 +105,7 @@ def gen_rowset_node(
|
|
|
103
105
|
)
|
|
104
106
|
return node
|
|
105
107
|
if any(x.derivation == PurposeLineage.ROWSET for x in possible_joins):
|
|
108
|
+
|
|
106
109
|
logger.info(
|
|
107
110
|
f"{padding(depth)}{LOGGER_PREFIX} cannot enrich rowset node with rowset concepts; exiting early"
|
|
108
111
|
)
|
|
@@ -240,10 +240,12 @@ def create_datasource_node(
|
|
|
240
240
|
depth: int,
|
|
241
241
|
conditions: WhereClause | None = None,
|
|
242
242
|
) -> tuple[StrategyNode, bool]:
|
|
243
|
-
target_grain = Grain.from_concepts(all_concepts)
|
|
243
|
+
target_grain = Grain.from_concepts(all_concepts, environment=environment)
|
|
244
244
|
force_group = False
|
|
245
245
|
if not datasource.grain.issubset(target_grain):
|
|
246
246
|
force_group = True
|
|
247
|
+
if not datasource.grain.components:
|
|
248
|
+
force_group = True
|
|
247
249
|
partial_concepts = [
|
|
248
250
|
c.concept
|
|
249
251
|
for c in datasource.columns
|
|
@@ -350,6 +352,9 @@ def create_select_node(
|
|
|
350
352
|
|
|
351
353
|
# we need to nest the group node one further
|
|
352
354
|
if force_group is True:
|
|
355
|
+
logger.info(
|
|
356
|
+
f"{padding(depth)}{LOGGER_PREFIX} source requires group before consumption."
|
|
357
|
+
)
|
|
353
358
|
candidate: StrategyNode = GroupNode(
|
|
354
359
|
output_concepts=all_concepts,
|
|
355
360
|
input_concepts=all_concepts,
|
|
@@ -359,8 +364,10 @@ def create_select_node(
|
|
|
359
364
|
partial_concepts=bcandidate.partial_concepts,
|
|
360
365
|
nullable_concepts=bcandidate.nullable_concepts,
|
|
361
366
|
preexisting_conditions=bcandidate.preexisting_conditions,
|
|
367
|
+
force_group=force_group,
|
|
362
368
|
)
|
|
363
369
|
else:
|
|
370
|
+
|
|
364
371
|
candidate = bcandidate
|
|
365
372
|
return candidate
|
|
366
373
|
|
|
@@ -40,9 +40,10 @@ def resolve_concept_map(
|
|
|
40
40
|
for concept in input.output_concepts:
|
|
41
41
|
if concept.address not in input.non_partial_concept_addresses:
|
|
42
42
|
continue
|
|
43
|
-
if
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (
|
|
44
|
+
isinstance(input, QueryDatasource)
|
|
45
|
+
and concept.address in input.hidden_concepts
|
|
46
|
+
):
|
|
46
47
|
continue
|
|
47
48
|
if concept.address in full_addresses:
|
|
48
49
|
concept_map[concept.address].add(input)
|
|
@@ -138,7 +139,7 @@ class StrategyNode:
|
|
|
138
139
|
preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
139
140
|
force_group: bool | None = None,
|
|
140
141
|
grain: Optional[Grain] = None,
|
|
141
|
-
hidden_concepts:
|
|
142
|
+
hidden_concepts: set[str] | None = None,
|
|
142
143
|
existence_concepts: List[Concept] | None = None,
|
|
143
144
|
virtual_output_concepts: List[Concept] | None = None,
|
|
144
145
|
):
|
|
@@ -165,7 +166,7 @@ class StrategyNode:
|
|
|
165
166
|
self.grain = grain
|
|
166
167
|
self.force_group = force_group
|
|
167
168
|
self.tainted = False
|
|
168
|
-
self.hidden_concepts = hidden_concepts or
|
|
169
|
+
self.hidden_concepts = hidden_concepts or set()
|
|
169
170
|
self.existence_concepts = existence_concepts or []
|
|
170
171
|
self.virtual_output_concepts = virtual_output_concepts or []
|
|
171
172
|
self.preexisting_conditions = preexisting_conditions
|
|
@@ -192,6 +193,8 @@ class StrategyNode:
|
|
|
192
193
|
for x in self.parents:
|
|
193
194
|
for z in x.usable_outputs:
|
|
194
195
|
non_hidden.add(z.address)
|
|
196
|
+
for psd in z.pseudonyms:
|
|
197
|
+
non_hidden.add(psd)
|
|
195
198
|
if not all([x.address in non_hidden for x in self.input_concepts]):
|
|
196
199
|
missing = [x for x in self.input_concepts if x.address not in non_hidden]
|
|
197
200
|
raise ValueError(
|
|
@@ -246,6 +249,15 @@ class StrategyNode:
|
|
|
246
249
|
self.rebuild_cache()
|
|
247
250
|
return self
|
|
248
251
|
|
|
252
|
+
def add_partial_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
253
|
+
for concept in concepts:
|
|
254
|
+
if concept.address not in self.partial_lcl.addresses:
|
|
255
|
+
self.partial_concepts.append(concept)
|
|
256
|
+
self.partial_lcl = LooseConceptList(concepts=self.partial_concepts)
|
|
257
|
+
if rebuild:
|
|
258
|
+
self.rebuild_cache()
|
|
259
|
+
return self
|
|
260
|
+
|
|
249
261
|
def add_existence_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
250
262
|
for concept in concepts:
|
|
251
263
|
if concept.address not in self.output_concepts:
|
|
@@ -270,22 +282,20 @@ class StrategyNode:
|
|
|
270
282
|
|
|
271
283
|
def hide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
272
284
|
for x in concepts:
|
|
273
|
-
self.hidden_concepts.
|
|
285
|
+
self.hidden_concepts.add(x.address)
|
|
274
286
|
if rebuild:
|
|
275
287
|
self.rebuild_cache()
|
|
276
288
|
return self
|
|
277
289
|
|
|
278
290
|
def unhide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
279
|
-
self.hidden_concepts =
|
|
280
|
-
x for x in self.hidden_concepts if x.address not in concepts
|
|
281
|
-
]
|
|
291
|
+
self.hidden_concepts = set(x for x in self.hidden_concepts if x not in concepts)
|
|
282
292
|
if rebuild:
|
|
283
293
|
self.rebuild_cache()
|
|
284
294
|
return self
|
|
285
295
|
|
|
286
296
|
def remove_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
287
297
|
for x in concepts:
|
|
288
|
-
self.hidden_concepts.
|
|
298
|
+
self.hidden_concepts.add(x.address)
|
|
289
299
|
addresses = [x.address for x in concepts]
|
|
290
300
|
self.output_concepts = [
|
|
291
301
|
x for x in self.output_concepts if x.address not in addresses
|
|
@@ -377,7 +387,7 @@ class StrategyNode:
|
|
|
377
387
|
preexisting_conditions=self.preexisting_conditions,
|
|
378
388
|
force_group=self.force_group,
|
|
379
389
|
grain=self.grain,
|
|
380
|
-
hidden_concepts=
|
|
390
|
+
hidden_concepts=set(self.hidden_concepts),
|
|
381
391
|
existence_concepts=list(self.existence_concepts),
|
|
382
392
|
virtual_output_concepts=list(self.virtual_output_concepts),
|
|
383
393
|
)
|