pytrilogy 0.0.2.7__tar.gz → 0.0.2.9__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.7/pytrilogy.egg-info → pytrilogy-0.0.2.9}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/constants.py +1 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/enums.py +1 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/models.py +154 -56
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/optimization.py +44 -5
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/optimizations/inline_datasource.py +14 -8
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/optimizations/predicate_pushdown.py +73 -44
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/concept_strategies_v3.py +69 -28
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/common.py +42 -16
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/filter_node.py +89 -48
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/group_node.py +3 -1
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/rowset_node.py +13 -54
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/select_node.py +10 -13
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/unnest_node.py +5 -3
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/window_node.py +23 -2
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/nodes/__init__.py +34 -6
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/nodes/base_node.py +67 -13
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/nodes/filter_node.py +3 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/nodes/group_node.py +3 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/nodes/merge_node.py +1 -11
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/nodes/select_node_v2.py +1 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/utility.py +29 -10
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/query_processor.py +47 -20
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/base.py +47 -14
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/common.py +15 -3
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/presto.py +2 -1
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parsing/parse_engine.py +20 -1
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parsing/trilogy.lark +3 -1
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/README.md +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/pyproject.toml +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/setup.cfg +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/setup.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_models.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_select.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parsing/common.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.2.7 → pytrilogy-0.0.2.9}/trilogy/utility.py +0 -0
|
@@ -154,7 +154,10 @@ class ConceptArgs(ABC):
|
|
|
154
154
|
class SelectContext(ABC):
|
|
155
155
|
|
|
156
156
|
def with_select_context(
|
|
157
|
-
self,
|
|
157
|
+
self,
|
|
158
|
+
grain: Grain,
|
|
159
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
160
|
+
environment: Environment | None = None,
|
|
158
161
|
):
|
|
159
162
|
raise NotImplementedError
|
|
160
163
|
|
|
@@ -166,6 +169,7 @@ class ConstantInlineable(ABC):
|
|
|
166
169
|
|
|
167
170
|
class SelectTypeMixin(BaseModel):
|
|
168
171
|
where_clause: Union["WhereClause", None] = Field(default=None)
|
|
172
|
+
having_clause: Union["HavingClause", None] = Field(default=None)
|
|
169
173
|
|
|
170
174
|
@property
|
|
171
175
|
def output_components(self) -> List[Concept]:
|
|
@@ -595,13 +599,16 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
595
599
|
self,
|
|
596
600
|
grain: Optional["Grain"] = None,
|
|
597
601
|
conditional: Conditional | Comparison | Parenthetical | None = None,
|
|
602
|
+
environment: Environment | None = None,
|
|
598
603
|
) -> "Concept":
|
|
599
604
|
if not all([isinstance(x, Concept) for x in self.keys or []]):
|
|
600
605
|
raise ValueError(f"Invalid keys {self.keys} for concept {self.address}")
|
|
601
606
|
new_grain = grain or self.grain
|
|
602
607
|
new_lineage = self.lineage
|
|
603
608
|
if isinstance(self.lineage, SelectContext):
|
|
604
|
-
new_lineage = self.lineage.with_select_context(
|
|
609
|
+
new_lineage = self.lineage.with_select_context(
|
|
610
|
+
new_grain, conditional, environment=environment
|
|
611
|
+
)
|
|
605
612
|
return self.__class__(
|
|
606
613
|
name=self.name,
|
|
607
614
|
datatype=self.datatype,
|
|
@@ -788,7 +795,9 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
788
795
|
return Granularity.MULTI_ROW
|
|
789
796
|
|
|
790
797
|
def with_filter(
|
|
791
|
-
self,
|
|
798
|
+
self,
|
|
799
|
+
condition: "Conditional | Comparison | Parenthetical",
|
|
800
|
+
environment: Environment | None = None,
|
|
792
801
|
) -> "Concept":
|
|
793
802
|
from trilogy.utility import string_to_hash
|
|
794
803
|
|
|
@@ -805,12 +814,15 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
805
814
|
modifiers=self.modifiers,
|
|
806
815
|
pseudonyms=self.pseudonyms,
|
|
807
816
|
)
|
|
817
|
+
if environment:
|
|
818
|
+
environment.add_concept(new)
|
|
808
819
|
return new
|
|
809
820
|
|
|
810
821
|
|
|
811
822
|
class Grain(Mergeable, BaseModel):
|
|
812
823
|
nested: bool = False
|
|
813
824
|
components: List[Concept] = Field(default_factory=list, validate_default=True)
|
|
825
|
+
where_clause: Optional[WhereClause] = Field(default=None)
|
|
814
826
|
|
|
815
827
|
@field_validator("components")
|
|
816
828
|
def component_validator(cls, v, info: ValidationInfo):
|
|
@@ -836,10 +848,12 @@ class Grain(Mergeable, BaseModel):
|
|
|
836
848
|
|
|
837
849
|
def __str__(self):
|
|
838
850
|
if self.abstract:
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
)
|
|
842
|
-
|
|
851
|
+
base = "Grain<Abstract>"
|
|
852
|
+
else:
|
|
853
|
+
base = "Grain<" + ",".join([c.address for c in self.components]) + ">"
|
|
854
|
+
if self.where_clause:
|
|
855
|
+
base += f"|{str(self.where_clause)}"
|
|
856
|
+
return base
|
|
843
857
|
|
|
844
858
|
def with_namespace(self, namespace: str) -> "Grain":
|
|
845
859
|
return Grain(
|
|
@@ -1046,12 +1060,15 @@ class Function(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
1046
1060
|
return self.output_datatype
|
|
1047
1061
|
|
|
1048
1062
|
def with_select_context(
|
|
1049
|
-
self,
|
|
1063
|
+
self,
|
|
1064
|
+
grain: Grain,
|
|
1065
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
1066
|
+
environment: Environment | None = None,
|
|
1050
1067
|
) -> Function:
|
|
1051
1068
|
if self.operator in FunctionClass.AGGREGATE_FUNCTIONS.value and conditional:
|
|
1052
1069
|
base = [
|
|
1053
1070
|
(
|
|
1054
|
-
c.with_select_context(grain, conditional)
|
|
1071
|
+
c.with_select_context(grain, conditional, environment)
|
|
1055
1072
|
if isinstance(
|
|
1056
1073
|
c,
|
|
1057
1074
|
SelectContext,
|
|
@@ -1061,7 +1078,7 @@ class Function(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
1061
1078
|
for c in self.arguments
|
|
1062
1079
|
]
|
|
1063
1080
|
final = [
|
|
1064
|
-
c.with_filter(conditional) if isinstance(c, Concept) else c
|
|
1081
|
+
c.with_filter(conditional, environment) if isinstance(c, Concept) else c
|
|
1065
1082
|
for c in base
|
|
1066
1083
|
]
|
|
1067
1084
|
return Function(
|
|
@@ -1077,7 +1094,7 @@ class Function(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
1077
1094
|
operator=self.operator,
|
|
1078
1095
|
arguments=[
|
|
1079
1096
|
(
|
|
1080
|
-
c.with_select_context(grain, conditional)
|
|
1097
|
+
c.with_select_context(grain, conditional, environment)
|
|
1081
1098
|
if isinstance(
|
|
1082
1099
|
c,
|
|
1083
1100
|
SelectContext,
|
|
@@ -1293,13 +1310,22 @@ class WindowItem(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
1293
1310
|
)
|
|
1294
1311
|
|
|
1295
1312
|
def with_select_context(
|
|
1296
|
-
self,
|
|
1313
|
+
self,
|
|
1314
|
+
grain: Grain,
|
|
1315
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
1316
|
+
environment: Environment | None = None,
|
|
1297
1317
|
) -> "WindowItem":
|
|
1298
1318
|
return WindowItem(
|
|
1299
1319
|
type=self.type,
|
|
1300
|
-
content=self.content.with_select_context(grain, conditional),
|
|
1301
|
-
over=[
|
|
1302
|
-
|
|
1320
|
+
content=self.content.with_select_context(grain, conditional, environment),
|
|
1321
|
+
over=[
|
|
1322
|
+
x.with_select_context(grain, conditional, environment)
|
|
1323
|
+
for x in self.over
|
|
1324
|
+
],
|
|
1325
|
+
order_by=[
|
|
1326
|
+
x.with_select_context(grain, conditional, environment)
|
|
1327
|
+
for x in self.order_by
|
|
1328
|
+
],
|
|
1303
1329
|
)
|
|
1304
1330
|
|
|
1305
1331
|
@property
|
|
@@ -1368,11 +1394,14 @@ class FilterItem(Namespaced, SelectContext, BaseModel):
|
|
|
1368
1394
|
)
|
|
1369
1395
|
|
|
1370
1396
|
def with_select_context(
|
|
1371
|
-
self,
|
|
1397
|
+
self,
|
|
1398
|
+
grain: Grain,
|
|
1399
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
1400
|
+
environment: Environment | None = None,
|
|
1372
1401
|
) -> FilterItem:
|
|
1373
1402
|
return FilterItem(
|
|
1374
|
-
content=self.content.with_select_context(grain, conditional),
|
|
1375
|
-
where=self.where.with_select_context(grain, conditional),
|
|
1403
|
+
content=self.content.with_select_context(grain, conditional, environment),
|
|
1404
|
+
where=self.where.with_select_context(grain, conditional, environment),
|
|
1376
1405
|
)
|
|
1377
1406
|
|
|
1378
1407
|
@property
|
|
@@ -1452,9 +1481,17 @@ class OrderItem(Mergeable, SelectContext, Namespaced, BaseModel):
|
|
|
1452
1481
|
return OrderItem(expr=self.expr.with_namespace(namespace), order=self.order)
|
|
1453
1482
|
|
|
1454
1483
|
def with_select_context(
|
|
1455
|
-
self,
|
|
1484
|
+
self,
|
|
1485
|
+
grain: Grain,
|
|
1486
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
1487
|
+
environment: Environment | None = None,
|
|
1456
1488
|
) -> "OrderItem":
|
|
1457
|
-
return OrderItem(
|
|
1489
|
+
return OrderItem(
|
|
1490
|
+
expr=self.expr.with_select_context(
|
|
1491
|
+
grain, conditional=conditional, environment=environment
|
|
1492
|
+
),
|
|
1493
|
+
order=self.order,
|
|
1494
|
+
)
|
|
1458
1495
|
|
|
1459
1496
|
def with_merge(
|
|
1460
1497
|
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
@@ -1643,7 +1680,9 @@ class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
|
|
|
1643
1680
|
)
|
|
1644
1681
|
):
|
|
1645
1682
|
output.append(item)
|
|
1646
|
-
return Grain(
|
|
1683
|
+
return Grain(
|
|
1684
|
+
components=unique(output, "address"), where_clause=self.where_clause
|
|
1685
|
+
)
|
|
1647
1686
|
|
|
1648
1687
|
def with_namespace(self, namespace: str) -> "SelectStatement":
|
|
1649
1688
|
return SelectStatement(
|
|
@@ -2066,6 +2105,7 @@ class Datasource(Namespaced, BaseModel):
|
|
|
2066
2105
|
class UnnestJoin(BaseModel):
|
|
2067
2106
|
concept: Concept
|
|
2068
2107
|
alias: str = "unnest"
|
|
2108
|
+
rendering_required: bool = True
|
|
2069
2109
|
|
|
2070
2110
|
def __hash__(self):
|
|
2071
2111
|
return (self.alias + self.concept.address).__hash__()
|
|
@@ -2228,17 +2268,14 @@ class QueryDatasource(BaseModel):
|
|
|
2228
2268
|
@classmethod
|
|
2229
2269
|
def validate_source_map(cls, v, info: ValidationInfo):
|
|
2230
2270
|
values = info.data
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
raise SyntaxError(
|
|
2240
|
-
f"source map missing {x} on (expected {expected}, have {seen})"
|
|
2241
|
-
)
|
|
2271
|
+
for key in ("input_concepts", "output_concepts"):
|
|
2272
|
+
if not values.get(key):
|
|
2273
|
+
continue
|
|
2274
|
+
for concept in values[key]:
|
|
2275
|
+
if concept.address not in v and CONFIG.validate_missing:
|
|
2276
|
+
raise SyntaxError(
|
|
2277
|
+
f"Missing source map for {concept.address} on {key}, have {v}"
|
|
2278
|
+
)
|
|
2242
2279
|
return v
|
|
2243
2280
|
|
|
2244
2281
|
def __str__(self):
|
|
@@ -2460,10 +2497,17 @@ class CTE(BaseModel):
|
|
|
2460
2497
|
for join in self.joins
|
|
2461
2498
|
if not isinstance(join, Join)
|
|
2462
2499
|
or (
|
|
2463
|
-
join
|
|
2464
|
-
and
|
|
2500
|
+
isinstance(join, Join)
|
|
2501
|
+
and (
|
|
2502
|
+
join.right_cte.name != removed_cte
|
|
2503
|
+
and join.left_cte.name != removed_cte
|
|
2504
|
+
)
|
|
2465
2505
|
)
|
|
2466
2506
|
]
|
|
2507
|
+
for join in self.joins:
|
|
2508
|
+
if isinstance(join, UnnestJoin) and join.concept == concept:
|
|
2509
|
+
join.rendering_required = False
|
|
2510
|
+
|
|
2467
2511
|
self.parent_ctes = [
|
|
2468
2512
|
x for x in self.parent_ctes if x.name != removed_cte
|
|
2469
2513
|
]
|
|
@@ -2841,6 +2885,7 @@ class UndefinedConcept(Concept, Mergeable, Namespaced):
|
|
|
2841
2885
|
self,
|
|
2842
2886
|
grain: Optional["Grain"] = None,
|
|
2843
2887
|
conditional: Conditional | Comparison | Parenthetical | None = None,
|
|
2888
|
+
environment: Environment | None = None,
|
|
2844
2889
|
) -> "UndefinedConcept":
|
|
2845
2890
|
if not all([isinstance(x, Concept) for x in self.keys or []]):
|
|
2846
2891
|
raise ValueError(f"Invalid keys {self.keys} for concept {self.address}")
|
|
@@ -2848,7 +2893,9 @@ class UndefinedConcept(Concept, Mergeable, Namespaced):
|
|
|
2848
2893
|
if self.lineage:
|
|
2849
2894
|
new_lineage = self.lineage
|
|
2850
2895
|
if isinstance(self.lineage, SelectContext):
|
|
2851
|
-
new_lineage = self.lineage.with_select_context(
|
|
2896
|
+
new_lineage = self.lineage.with_select_context(
|
|
2897
|
+
new_grain, conditional, environment
|
|
2898
|
+
)
|
|
2852
2899
|
else:
|
|
2853
2900
|
new_lineage = None
|
|
2854
2901
|
return self.__class__(
|
|
@@ -3291,7 +3338,9 @@ class LazyEnvironment(Environment):
|
|
|
3291
3338
|
) or name.startswith("_"):
|
|
3292
3339
|
return super().__getattribute__(name)
|
|
3293
3340
|
if not self.loaded:
|
|
3294
|
-
|
|
3341
|
+
logger.info(
|
|
3342
|
+
f"lazily evaluating load path {self.load_path} to access {name}"
|
|
3343
|
+
)
|
|
3295
3344
|
from trilogy import parse
|
|
3296
3345
|
|
|
3297
3346
|
env = Environment(working_path=str(self.working_path))
|
|
@@ -3433,16 +3482,23 @@ class Comparison(
|
|
|
3433
3482
|
)
|
|
3434
3483
|
|
|
3435
3484
|
def with_select_context(
|
|
3436
|
-
self,
|
|
3485
|
+
self,
|
|
3486
|
+
grain: Grain,
|
|
3487
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
3488
|
+
environment: Environment | None = None,
|
|
3437
3489
|
):
|
|
3438
3490
|
return self.__class__(
|
|
3439
3491
|
left=(
|
|
3440
|
-
self.left.with_select_context(grain, conditional)
|
|
3492
|
+
self.left.with_select_context(grain, conditional, environment)
|
|
3441
3493
|
if isinstance(self.left, SelectContext)
|
|
3442
3494
|
else self.left
|
|
3443
3495
|
),
|
|
3444
3496
|
# the right side does NOT need to inherit select grain
|
|
3445
|
-
right=
|
|
3497
|
+
right=(
|
|
3498
|
+
self.right.with_select_context(grain, conditional, environment)
|
|
3499
|
+
if isinstance(self.right, SelectContext)
|
|
3500
|
+
else self.right
|
|
3501
|
+
),
|
|
3446
3502
|
operator=self.operator,
|
|
3447
3503
|
)
|
|
3448
3504
|
|
|
@@ -3526,12 +3582,15 @@ class SubselectComparison(Comparison):
|
|
|
3526
3582
|
return [tuple(get_concept_arguments(self.right))]
|
|
3527
3583
|
|
|
3528
3584
|
def with_select_context(
|
|
3529
|
-
self,
|
|
3585
|
+
self,
|
|
3586
|
+
grain: Grain,
|
|
3587
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
3588
|
+
environment: Environment | None = None,
|
|
3530
3589
|
):
|
|
3531
|
-
# there's no need to pass the select grain through to a subselect comparison
|
|
3590
|
+
# there's no need to pass the select grain through to a subselect comparison on the right
|
|
3532
3591
|
return self.__class__(
|
|
3533
3592
|
left=(
|
|
3534
|
-
self.left.with_select_context(grain, conditional)
|
|
3593
|
+
self.left.with_select_context(grain, conditional, environment)
|
|
3535
3594
|
if isinstance(self.left, SelectContext)
|
|
3536
3595
|
else self.left
|
|
3537
3596
|
),
|
|
@@ -3562,12 +3621,17 @@ class CaseWhen(Namespaced, SelectContext, BaseModel):
|
|
|
3562
3621
|
)
|
|
3563
3622
|
|
|
3564
3623
|
def with_select_context(
|
|
3565
|
-
self,
|
|
3624
|
+
self,
|
|
3625
|
+
grain: Grain,
|
|
3626
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
3627
|
+
environment: Environment | None = None,
|
|
3566
3628
|
) -> CaseWhen:
|
|
3567
3629
|
return CaseWhen(
|
|
3568
|
-
comparison=self.comparison.with_select_context(
|
|
3630
|
+
comparison=self.comparison.with_select_context(
|
|
3631
|
+
grain, conditional, environment
|
|
3632
|
+
),
|
|
3569
3633
|
expr=(
|
|
3570
|
-
(self.expr.with_select_context(grain, conditional))
|
|
3634
|
+
(self.expr.with_select_context(grain, conditional, environment))
|
|
3571
3635
|
if isinstance(self.expr, SelectContext)
|
|
3572
3636
|
else self.expr
|
|
3573
3637
|
),
|
|
@@ -3584,12 +3648,15 @@ class CaseElse(Namespaced, SelectContext, BaseModel):
|
|
|
3584
3648
|
return get_concept_arguments(self.expr)
|
|
3585
3649
|
|
|
3586
3650
|
def with_select_context(
|
|
3587
|
-
self,
|
|
3651
|
+
self,
|
|
3652
|
+
grain: Grain,
|
|
3653
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
3654
|
+
environment: Environment | None = None,
|
|
3588
3655
|
) -> CaseElse:
|
|
3589
3656
|
return CaseElse(
|
|
3590
3657
|
discriminant=self.discriminant,
|
|
3591
3658
|
expr=(
|
|
3592
|
-
self.expr.with_select_context(grain, conditional)
|
|
3659
|
+
self.expr.with_select_context(grain, conditional, environment)
|
|
3593
3660
|
if isinstance(
|
|
3594
3661
|
self.expr,
|
|
3595
3662
|
SelectContext,
|
|
@@ -3729,16 +3796,19 @@ class Conditional(
|
|
|
3729
3796
|
)
|
|
3730
3797
|
|
|
3731
3798
|
def with_select_context(
|
|
3732
|
-
self,
|
|
3799
|
+
self,
|
|
3800
|
+
grain: Grain,
|
|
3801
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
3802
|
+
environment: Environment | None = None,
|
|
3733
3803
|
):
|
|
3734
3804
|
return Conditional(
|
|
3735
3805
|
left=(
|
|
3736
|
-
self.left.with_select_context(grain, conditional)
|
|
3806
|
+
self.left.with_select_context(grain, conditional, environment)
|
|
3737
3807
|
if isinstance(self.left, SelectContext)
|
|
3738
3808
|
else self.left
|
|
3739
3809
|
),
|
|
3740
3810
|
right=(
|
|
3741
|
-
self.right.with_select_context(grain, conditional)
|
|
3811
|
+
self.right.with_select_context(grain, conditional, environment)
|
|
3742
3812
|
if isinstance(self.right, SelectContext)
|
|
3743
3813
|
else self.right
|
|
3744
3814
|
),
|
|
@@ -3847,13 +3917,16 @@ class AggregateWrapper(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
3847
3917
|
)
|
|
3848
3918
|
|
|
3849
3919
|
def with_select_context(
|
|
3850
|
-
self,
|
|
3920
|
+
self,
|
|
3921
|
+
grain: Grain,
|
|
3922
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
3923
|
+
environment: Environment | None = None,
|
|
3851
3924
|
) -> AggregateWrapper:
|
|
3852
3925
|
if not self.by:
|
|
3853
3926
|
by = grain.components_copy
|
|
3854
3927
|
else:
|
|
3855
3928
|
by = self.by
|
|
3856
|
-
parent = self.function.with_select_context(grain, conditional)
|
|
3929
|
+
parent = self.function.with_select_context(grain, conditional, environment)
|
|
3857
3930
|
return AggregateWrapper(function=parent, by=by)
|
|
3858
3931
|
|
|
3859
3932
|
|
|
@@ -3885,10 +3958,15 @@ class WhereClause(Mergeable, ConceptArgs, Namespaced, SelectContext, BaseModel):
|
|
|
3885
3958
|
return WhereClause(conditional=self.conditional.with_namespace(namespace))
|
|
3886
3959
|
|
|
3887
3960
|
def with_select_context(
|
|
3888
|
-
self,
|
|
3961
|
+
self,
|
|
3962
|
+
grain: Grain,
|
|
3963
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
3964
|
+
environment: Environment | None = None,
|
|
3889
3965
|
) -> WhereClause:
|
|
3890
3966
|
return WhereClause(
|
|
3891
|
-
conditional=self.conditional.with_select_context(
|
|
3967
|
+
conditional=self.conditional.with_select_context(
|
|
3968
|
+
grain, conditional, environment
|
|
3969
|
+
)
|
|
3892
3970
|
)
|
|
3893
3971
|
|
|
3894
3972
|
@property
|
|
@@ -3901,6 +3979,22 @@ class WhereClause(Mergeable, ConceptArgs, Namespaced, SelectContext, BaseModel):
|
|
|
3901
3979
|
output += item.grain.components if item.grain else []
|
|
3902
3980
|
return Grain(components=list(set(output)))
|
|
3903
3981
|
|
|
3982
|
+
@property
|
|
3983
|
+
def components(self):
|
|
3984
|
+
from trilogy.core.processing.utility import decompose_condition
|
|
3985
|
+
|
|
3986
|
+
return decompose_condition(self.conditional)
|
|
3987
|
+
|
|
3988
|
+
@property
|
|
3989
|
+
def is_scalar(self):
|
|
3990
|
+
from trilogy.core.processing.utility import is_scalar_condition
|
|
3991
|
+
|
|
3992
|
+
return is_scalar_condition(self.conditional)
|
|
3993
|
+
|
|
3994
|
+
|
|
3995
|
+
class HavingClause(WhereClause):
|
|
3996
|
+
pass
|
|
3997
|
+
|
|
3904
3998
|
|
|
3905
3999
|
class MaterializedDataset(BaseModel):
|
|
3906
4000
|
address: Address
|
|
@@ -3920,6 +4014,7 @@ class ProcessedQuery(BaseModel):
|
|
|
3920
4014
|
hidden_columns: List[Concept] = Field(default_factory=list)
|
|
3921
4015
|
limit: Optional[int] = None
|
|
3922
4016
|
where_clause: Optional[WhereClause] = None
|
|
4017
|
+
having_clause: Optional[HavingClause] = None
|
|
3923
4018
|
order_by: Optional[OrderBy] = None
|
|
3924
4019
|
|
|
3925
4020
|
|
|
@@ -4122,11 +4217,14 @@ class Parenthetical(
|
|
|
4122
4217
|
)
|
|
4123
4218
|
|
|
4124
4219
|
def with_select_context(
|
|
4125
|
-
self,
|
|
4220
|
+
self,
|
|
4221
|
+
grain: Grain,
|
|
4222
|
+
conditional: Conditional | Comparison | Parenthetical | None,
|
|
4223
|
+
environment: Environment | None = None,
|
|
4126
4224
|
):
|
|
4127
4225
|
return Parenthetical(
|
|
4128
4226
|
content=(
|
|
4129
|
-
self.content.with_select_context(grain, conditional)
|
|
4227
|
+
self.content.with_select_context(grain, conditional, environment)
|
|
4130
4228
|
if isinstance(self.content, SelectContext)
|
|
4131
4229
|
else self.content
|
|
4132
4230
|
)
|
|
@@ -17,19 +17,58 @@ from trilogy.core.optimizations import (
|
|
|
17
17
|
MAX_OPTIMIZATION_LOOPS = 100
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
# other optimizations may make a CTE a pure passthrough
|
|
21
|
+
# remove those
|
|
22
|
+
# def is_locally_irrelevant(cte: CTE) -> CTE | bool:
|
|
23
|
+
# if not len(cte.parent_ctes) == 1:
|
|
24
|
+
# return False
|
|
25
|
+
# parent = cte.parent_ctes[0]
|
|
26
|
+
# if not parent.output_columns == cte.output_columns:
|
|
27
|
+
# return False
|
|
28
|
+
# if cte.condition is not None:
|
|
29
|
+
# return False
|
|
30
|
+
# if cte.group_to_grain:
|
|
31
|
+
# return False
|
|
32
|
+
# if len(cte.joins)>1:
|
|
33
|
+
# return False
|
|
34
|
+
# return parent
|
|
35
|
+
|
|
36
|
+
|
|
20
37
|
def filter_irrelevant_ctes(
|
|
21
38
|
input: list[CTE],
|
|
22
39
|
root_cte: CTE,
|
|
23
40
|
):
|
|
24
41
|
relevant_ctes = set()
|
|
25
42
|
|
|
26
|
-
def recurse(cte: CTE):
|
|
43
|
+
def recurse(cte: CTE, inverse_map: dict[str, list[CTE]]):
|
|
44
|
+
# TODO: revisit this
|
|
45
|
+
# if parent := is_locally_irrelevant(cte):
|
|
46
|
+
# logger.info(
|
|
47
|
+
# f"[Optimization][Irrelevent CTE filtering] Removing redundant CTE {cte.name} and replacing with {parent.name}"
|
|
48
|
+
# )
|
|
49
|
+
# for child in inverse_map.get(cte.name, []):
|
|
50
|
+
# child.parent_ctes = [
|
|
51
|
+
# x for x in child.parent_ctes if x.name != cte.name
|
|
52
|
+
# ] + [parent]
|
|
53
|
+
# for x in child.source_map:
|
|
54
|
+
# if cte.name in child.source_map[x]:
|
|
55
|
+
# child.source_map[x].remove(cte.name)
|
|
56
|
+
# child.source_map[x].append(parent.name)
|
|
57
|
+
# for x2 in child.existence_source_map:
|
|
58
|
+
# if cte.name in child.existence_source_map[x2]:
|
|
59
|
+
# child.existence_source_map[x2].remove(cte.name)
|
|
60
|
+
# child.existence_source_map[x2].append(parent.name)
|
|
61
|
+
# else:
|
|
27
62
|
relevant_ctes.add(cte.name)
|
|
28
63
|
for cte in cte.parent_ctes:
|
|
29
|
-
recurse(cte)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
64
|
+
recurse(cte, inverse_map)
|
|
65
|
+
|
|
66
|
+
inverse_map = gen_inverse_map(input)
|
|
67
|
+
recurse(root_cte, inverse_map)
|
|
68
|
+
final = [cte for cte in input if cte.name in relevant_ctes]
|
|
69
|
+
if len(final) == len(input):
|
|
70
|
+
return input
|
|
71
|
+
return filter_irrelevant_ctes(final, root_cte)
|
|
33
72
|
|
|
34
73
|
|
|
35
74
|
def gen_inverse_map(input: list[CTE]) -> dict[str, list[CTE]]:
|
|
@@ -5,6 +5,7 @@ from trilogy.core.models import (
|
|
|
5
5
|
|
|
6
6
|
from trilogy.core.optimizations.base_optimization import OptimizationRule
|
|
7
7
|
from collections import defaultdict
|
|
8
|
+
from trilogy.constants import CONFIG
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class InlineDatasource(OptimizationRule):
|
|
@@ -18,28 +19,28 @@ class InlineDatasource(OptimizationRule):
|
|
|
18
19
|
if not cte.parent_ctes:
|
|
19
20
|
return False
|
|
20
21
|
|
|
21
|
-
self.
|
|
22
|
+
self.debug(
|
|
22
23
|
f"Checking {cte.name} for consolidating inline tables with {len(cte.parent_ctes)} parents"
|
|
23
24
|
)
|
|
24
25
|
to_inline: list[CTE] = []
|
|
25
26
|
force_group = False
|
|
26
27
|
for parent_cte in cte.parent_ctes:
|
|
27
28
|
if not parent_cte.is_root_datasource:
|
|
28
|
-
self.
|
|
29
|
+
self.debug(f"parent {parent_cte.name} is not root")
|
|
29
30
|
continue
|
|
30
31
|
if parent_cte.parent_ctes:
|
|
31
|
-
self.
|
|
32
|
+
self.debug(f"parent {parent_cte.name} has parents")
|
|
32
33
|
continue
|
|
33
34
|
if parent_cte.condition:
|
|
34
|
-
self.
|
|
35
|
+
self.debug(f"parent {parent_cte.name} has condition, cannot be inlined")
|
|
35
36
|
continue
|
|
36
37
|
raw_root = parent_cte.source.datasources[0]
|
|
37
38
|
if not isinstance(raw_root, Datasource):
|
|
38
|
-
self.
|
|
39
|
+
self.debug(f"Parent {parent_cte.name} is not datasource")
|
|
39
40
|
continue
|
|
40
41
|
root: Datasource = raw_root
|
|
41
42
|
if not root.can_be_inlined:
|
|
42
|
-
self.
|
|
43
|
+
self.debug(f"Parent {parent_cte.name} datasource is not inlineable")
|
|
43
44
|
continue
|
|
44
45
|
root_outputs = {x.address for x in root.output_concepts}
|
|
45
46
|
inherited = {
|
|
@@ -52,7 +53,9 @@ class InlineDatasource(OptimizationRule):
|
|
|
52
53
|
)
|
|
53
54
|
continue
|
|
54
55
|
if not root.grain.issubset(parent_cte.grain):
|
|
55
|
-
self.log(
|
|
56
|
+
self.log(
|
|
57
|
+
f"{parent_cte.name} is at wrong grain to inline ({root.grain} vs {parent_cte.grain})"
|
|
58
|
+
)
|
|
56
59
|
continue
|
|
57
60
|
to_inline.append(parent_cte)
|
|
58
61
|
|
|
@@ -62,7 +65,10 @@ class InlineDatasource(OptimizationRule):
|
|
|
62
65
|
self.candidates[cte.name].add(replaceable.name)
|
|
63
66
|
self.count[replaceable.source.name] += 1
|
|
64
67
|
return True
|
|
65
|
-
if
|
|
68
|
+
if (
|
|
69
|
+
self.count[replaceable.source.name]
|
|
70
|
+
> CONFIG.optimizations.constant_inline_cutoff
|
|
71
|
+
):
|
|
66
72
|
self.log(
|
|
67
73
|
f"Skipping inlining raw datasource {replaceable.source.name} ({replaceable.name}) due to multiple references"
|
|
68
74
|
)
|