pytrilogy 0.0.1.103__tar.gz → 0.0.1.105__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.1.103/pytrilogy.egg-info → pytrilogy-0.0.1.105}/PKG-INFO +1 -1
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/models.py +176 -45
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/concept_strategies_v3.py +6 -3
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/common.py +19 -7
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/filter_node.py +39 -10
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/merge_node.py +11 -1
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/select_node.py +275 -53
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/__init__.py +54 -1
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/base.py +12 -3
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/parsing/common.py +30 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/parsing/parse_engine.py +65 -94
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/parsing/render.py +0 -122
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/LICENSE.md +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/README.md +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/pyproject.toml +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/setup.cfg +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/setup.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_models.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_select.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/concept_merge.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/docs/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/utility.py +0 -0
|
@@ -113,15 +113,27 @@ NAMESPACED_TYPES = Union[
|
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
class Namespaced(ABC):
|
|
116
|
-
pass
|
|
117
116
|
|
|
118
117
|
def with_namespace(self, namespace: str):
|
|
119
118
|
raise NotImplementedError
|
|
120
119
|
|
|
121
120
|
|
|
122
|
-
class
|
|
123
|
-
|
|
121
|
+
class ConceptArgs(ABC):
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def concept_arguments(self) -> List["Concept"]:
|
|
125
|
+
raise NotImplementedError
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def existence_arguments(self) -> List["Concept"]:
|
|
129
|
+
return []
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def row_arguments(self) -> List["Concept"]:
|
|
133
|
+
return self.concept_arguments
|
|
124
134
|
|
|
135
|
+
|
|
136
|
+
class SelectGrain(ABC):
|
|
125
137
|
def with_select_grain(self, grain: Grain):
|
|
126
138
|
raise NotImplementedError
|
|
127
139
|
|
|
@@ -355,7 +367,7 @@ class Concept(Namespaced, SelectGrain, BaseModel):
|
|
|
355
367
|
grain = ",".join([str(c.address) for c in self.grain.components])
|
|
356
368
|
return f"{self.namespace}.{self.name}<{grain}>"
|
|
357
369
|
|
|
358
|
-
@
|
|
370
|
+
@cached_property
|
|
359
371
|
def address(self) -> str:
|
|
360
372
|
return f"{self.namespace}.{self.name}"
|
|
361
373
|
|
|
@@ -436,7 +448,8 @@ class Concept(Namespaced, SelectGrain, BaseModel):
|
|
|
436
448
|
modifiers=self.modifiers,
|
|
437
449
|
)
|
|
438
450
|
|
|
439
|
-
|
|
451
|
+
@cached_property
|
|
452
|
+
def _with_default_grain(self) -> "Concept":
|
|
440
453
|
if self.purpose == Purpose.KEY:
|
|
441
454
|
# we need to make this abstract
|
|
442
455
|
grain = Grain(components=[self.with_grain(Grain())], nested=True)
|
|
@@ -473,6 +486,9 @@ class Concept(Namespaced, SelectGrain, BaseModel):
|
|
|
473
486
|
modifiers=self.modifiers,
|
|
474
487
|
)
|
|
475
488
|
|
|
489
|
+
def with_default_grain(self) -> "Concept":
|
|
490
|
+
return self._with_default_grain
|
|
491
|
+
|
|
476
492
|
@property
|
|
477
493
|
def sources(self) -> List["Concept"]:
|
|
478
494
|
if self.lineage:
|
|
@@ -610,7 +626,7 @@ class Grain(BaseModel):
|
|
|
610
626
|
[c.name == ALL_ROWS_CONCEPT for c in self.components]
|
|
611
627
|
)
|
|
612
628
|
|
|
613
|
-
@
|
|
629
|
+
@cached_property
|
|
614
630
|
def set(self):
|
|
615
631
|
return set([c.address for c in self.components_copy])
|
|
616
632
|
|
|
@@ -1391,16 +1407,11 @@ class MultiSelectStatement(Namespaced, BaseModel):
|
|
|
1391
1407
|
return output
|
|
1392
1408
|
|
|
1393
1409
|
def find_source(self, concept: Concept, cte: CTE) -> Concept:
|
|
1394
|
-
all = []
|
|
1395
1410
|
for x in self.align.items:
|
|
1396
1411
|
if concept.name == x.alias:
|
|
1397
1412
|
for c in x.concepts:
|
|
1398
1413
|
if c.address in cte.output_lcl:
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
if len(all) == 1:
|
|
1402
|
-
return all[0]
|
|
1403
|
-
|
|
1414
|
+
return c
|
|
1404
1415
|
raise SyntaxError(
|
|
1405
1416
|
f"Could not find upstream map for multiselect {str(concept)} on cte ({cte})"
|
|
1406
1417
|
)
|
|
@@ -1585,7 +1596,7 @@ class Datasource(Namespaced, BaseModel):
|
|
|
1585
1596
|
columns=[c.with_namespace(namespace) for c in self.columns],
|
|
1586
1597
|
)
|
|
1587
1598
|
|
|
1588
|
-
@
|
|
1599
|
+
@cached_property
|
|
1589
1600
|
def concepts(self) -> List[Concept]:
|
|
1590
1601
|
return [c.concept for c in self.columns]
|
|
1591
1602
|
|
|
@@ -1780,7 +1791,7 @@ class QueryDatasource(BaseModel):
|
|
|
1780
1791
|
|
|
1781
1792
|
@field_validator("source_map")
|
|
1782
1793
|
@classmethod
|
|
1783
|
-
def validate_source_map(cls, v, info
|
|
1794
|
+
def validate_source_map(cls, v, info: ValidationInfo):
|
|
1784
1795
|
values = info.data
|
|
1785
1796
|
expected = {c.address for c in values["output_concepts"]}.union(
|
|
1786
1797
|
c.address for c in values["input_concepts"]
|
|
@@ -2288,8 +2299,8 @@ class EnvironmentConceptDict(dict):
|
|
|
2288
2299
|
|
|
2289
2300
|
class ImportStatement(BaseModel):
|
|
2290
2301
|
alias: str
|
|
2291
|
-
path:
|
|
2292
|
-
|
|
2302
|
+
path: Path
|
|
2303
|
+
environment: Union["Environment", None] = None
|
|
2293
2304
|
# TODO: this might result in a lot of duplication
|
|
2294
2305
|
# environment:"Environment"
|
|
2295
2306
|
|
|
@@ -2324,6 +2335,9 @@ class Environment(BaseModel):
|
|
|
2324
2335
|
version: str = Field(default_factory=get_version)
|
|
2325
2336
|
cte_name_map: Dict[str, str] = Field(default_factory=dict)
|
|
2326
2337
|
|
|
2338
|
+
materialized_concepts: List[Concept] = Field(default_factory=list)
|
|
2339
|
+
_parse_count: int = 0
|
|
2340
|
+
|
|
2327
2341
|
@classmethod
|
|
2328
2342
|
def from_file(cls, path: str | Path) -> "Environment":
|
|
2329
2343
|
with open(path, "r") as f:
|
|
@@ -2349,20 +2363,14 @@ class Environment(BaseModel):
|
|
|
2349
2363
|
f.write(self.model_dump_json())
|
|
2350
2364
|
return ppath
|
|
2351
2365
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
if concept.address in [x.address for x in datasource.output_concepts]:
|
|
2361
|
-
found = True
|
|
2362
|
-
break
|
|
2363
|
-
if found:
|
|
2364
|
-
output.append(concept)
|
|
2365
|
-
return output
|
|
2366
|
+
def gen_materialized_concepts(self) -> None:
|
|
2367
|
+
concrete_addresses = set()
|
|
2368
|
+
for datasource in self.datasources.values():
|
|
2369
|
+
for concept in datasource.output_concepts:
|
|
2370
|
+
concrete_addresses.add(concept.address)
|
|
2371
|
+
self.materialized_concepts = [
|
|
2372
|
+
c for c in self.concepts.values() if c.address in concrete_addresses
|
|
2373
|
+
]
|
|
2366
2374
|
|
|
2367
2375
|
def validate_concept(self, lookup: str, meta: Meta | None = None):
|
|
2368
2376
|
existing: Concept = self.concepts.get(lookup) # type: ignore
|
|
@@ -2392,12 +2400,61 @@ class Environment(BaseModel):
|
|
|
2392
2400
|
|
|
2393
2401
|
def add_import(self, alias: str, environment: Environment):
|
|
2394
2402
|
self.imports[alias] = ImportStatement(
|
|
2395
|
-
alias=alias, path=
|
|
2403
|
+
alias=alias, path=Path(environment.working_path)
|
|
2396
2404
|
)
|
|
2397
2405
|
for key, concept in environment.concepts.items():
|
|
2398
2406
|
self.concepts[f"{alias}.{key}"] = concept.with_namespace(alias)
|
|
2399
2407
|
for key, datasource in environment.datasources.items():
|
|
2400
2408
|
self.datasources[f"{alias}.{key}"] = datasource.with_namespace(alias)
|
|
2409
|
+
self.gen_materialized_concepts()
|
|
2410
|
+
return self
|
|
2411
|
+
|
|
2412
|
+
def add_file_import(self, path: str, alias: str, env: Environment | None = None):
|
|
2413
|
+
from trilogy.parsing.parse_engine import ParseToObjects, PARSER
|
|
2414
|
+
|
|
2415
|
+
apath = path.split(".")
|
|
2416
|
+
apath[-1] = apath[-1] + ".preql"
|
|
2417
|
+
|
|
2418
|
+
target: Path = Path(self.working_path, *apath)
|
|
2419
|
+
if env:
|
|
2420
|
+
self.imports[alias] = ImportStatement(
|
|
2421
|
+
alias=alias, path=target, environment=env
|
|
2422
|
+
)
|
|
2423
|
+
|
|
2424
|
+
elif alias in self.imports:
|
|
2425
|
+
current = self.imports[alias]
|
|
2426
|
+
env = self.imports[alias].environment
|
|
2427
|
+
if current.path != target:
|
|
2428
|
+
raise ImportError(
|
|
2429
|
+
f"Attempted to import {target} with alias {alias} but {alias} is already imported from {current.path}"
|
|
2430
|
+
)
|
|
2431
|
+
else:
|
|
2432
|
+
try:
|
|
2433
|
+
with open(target, "r", encoding="utf-8") as f:
|
|
2434
|
+
text = f.read()
|
|
2435
|
+
nparser = ParseToObjects(
|
|
2436
|
+
visit_tokens=True,
|
|
2437
|
+
text=text,
|
|
2438
|
+
environment=Environment(
|
|
2439
|
+
working_path=target.parent,
|
|
2440
|
+
),
|
|
2441
|
+
parse_address=str(target),
|
|
2442
|
+
)
|
|
2443
|
+
nparser.transform(PARSER.parse(text))
|
|
2444
|
+
except Exception as e:
|
|
2445
|
+
raise ImportError(
|
|
2446
|
+
f"Unable to import file {target.parent}, parsing error: {e}"
|
|
2447
|
+
)
|
|
2448
|
+
env = nparser.environment
|
|
2449
|
+
if env:
|
|
2450
|
+
for _, concept in env.concepts.items():
|
|
2451
|
+
self.add_concept(concept.with_namespace(alias))
|
|
2452
|
+
|
|
2453
|
+
for _, datasource in env.datasources.items():
|
|
2454
|
+
self.add_datasource(datasource.with_namespace(alias))
|
|
2455
|
+
imps = ImportStatement(alias=alias, path=target, environment=env)
|
|
2456
|
+
self.imports[alias] = imps
|
|
2457
|
+
return imps
|
|
2401
2458
|
|
|
2402
2459
|
def parse(
|
|
2403
2460
|
self, input: str, namespace: str | None = None, persist: bool = False
|
|
@@ -2448,21 +2505,22 @@ class Environment(BaseModel):
|
|
|
2448
2505
|
from trilogy.core.environment_helpers import generate_related_concepts
|
|
2449
2506
|
|
|
2450
2507
|
generate_related_concepts(concept, self)
|
|
2508
|
+
self.gen_materialized_concepts()
|
|
2451
2509
|
return concept
|
|
2452
2510
|
|
|
2453
2511
|
def add_datasource(
|
|
2454
2512
|
self,
|
|
2455
2513
|
datasource: Datasource,
|
|
2514
|
+
meta: Meta | None = None,
|
|
2456
2515
|
):
|
|
2457
|
-
if datasource.namespace == DEFAULT_NAMESPACE:
|
|
2458
|
-
self.datasources[datasource.name] = datasource
|
|
2459
|
-
return datasource
|
|
2460
|
-
if not datasource.namespace:
|
|
2516
|
+
if not datasource.namespace or datasource.namespace == DEFAULT_NAMESPACE:
|
|
2461
2517
|
self.datasources[datasource.name] = datasource
|
|
2518
|
+
self.gen_materialized_concepts()
|
|
2462
2519
|
return datasource
|
|
2463
2520
|
self.datasources[datasource.namespace + "." + datasource.identifier] = (
|
|
2464
2521
|
datasource
|
|
2465
2522
|
)
|
|
2523
|
+
self.gen_materialized_concepts()
|
|
2466
2524
|
return datasource
|
|
2467
2525
|
|
|
2468
2526
|
|
|
@@ -2495,7 +2553,7 @@ class LazyEnvironment(Environment):
|
|
|
2495
2553
|
return super().__getattribute__(name)
|
|
2496
2554
|
|
|
2497
2555
|
|
|
2498
|
-
class Comparison(Namespaced, SelectGrain, BaseModel):
|
|
2556
|
+
class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
2499
2557
|
left: Union[
|
|
2500
2558
|
int,
|
|
2501
2559
|
str,
|
|
@@ -2547,7 +2605,7 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
|
|
|
2547
2605
|
return f"{str(self.left)} {self.operator.value} {str(self.right)}"
|
|
2548
2606
|
|
|
2549
2607
|
def with_namespace(self, namespace: str):
|
|
2550
|
-
return
|
|
2608
|
+
return self.__class__(
|
|
2551
2609
|
left=(
|
|
2552
2610
|
self.left.with_namespace(namespace)
|
|
2553
2611
|
if isinstance(self.left, Namespaced)
|
|
@@ -2562,7 +2620,7 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
|
|
|
2562
2620
|
)
|
|
2563
2621
|
|
|
2564
2622
|
def with_select_grain(self, grain: Grain):
|
|
2565
|
-
return
|
|
2623
|
+
return self.__class__(
|
|
2566
2624
|
left=(
|
|
2567
2625
|
self.left.with_select_grain(grain)
|
|
2568
2626
|
if isinstance(self.left, SelectGrain)
|
|
@@ -2581,7 +2639,9 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
|
|
|
2581
2639
|
output: List[Concept] = []
|
|
2582
2640
|
if isinstance(self.left, (Concept,)):
|
|
2583
2641
|
output += [self.left]
|
|
2584
|
-
if isinstance(
|
|
2642
|
+
if isinstance(
|
|
2643
|
+
self.left, (Comparison, SubselectComparison, Conditional, Parenthetical)
|
|
2644
|
+
):
|
|
2585
2645
|
output += self.left.input
|
|
2586
2646
|
if isinstance(self.left, FilterItem):
|
|
2587
2647
|
output += self.left.concept_arguments
|
|
@@ -2590,7 +2650,9 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
|
|
|
2590
2650
|
|
|
2591
2651
|
if isinstance(self.right, (Concept,)):
|
|
2592
2652
|
output += [self.right]
|
|
2593
|
-
if isinstance(
|
|
2653
|
+
if isinstance(
|
|
2654
|
+
self.right, (Comparison, SubselectComparison, Conditional, Parenthetical)
|
|
2655
|
+
):
|
|
2594
2656
|
output += self.right.input
|
|
2595
2657
|
if isinstance(self.right, FilterItem):
|
|
2596
2658
|
output += self.right.concept_arguments
|
|
@@ -2607,8 +2669,31 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
|
|
|
2607
2669
|
return output
|
|
2608
2670
|
|
|
2609
2671
|
|
|
2672
|
+
class SubselectComparison(Comparison):
|
|
2673
|
+
|
|
2674
|
+
@property
|
|
2675
|
+
def row_arguments(self) -> List[Concept]:
|
|
2676
|
+
return get_concept_arguments(self.left)
|
|
2677
|
+
|
|
2678
|
+
@property
|
|
2679
|
+
def existence_arguments(self) -> List[Concept]:
|
|
2680
|
+
return get_concept_arguments(self.right)
|
|
2681
|
+
|
|
2682
|
+
def with_select_grain(self, grain: Grain):
|
|
2683
|
+
# there's no need to pass the select grain through to a subselect comparison
|
|
2684
|
+
return self.__class__(
|
|
2685
|
+
left=(
|
|
2686
|
+
self.left.with_select_grain(grain)
|
|
2687
|
+
if isinstance(self.left, SelectGrain)
|
|
2688
|
+
else self.left
|
|
2689
|
+
),
|
|
2690
|
+
right=self.right,
|
|
2691
|
+
operator=self.operator,
|
|
2692
|
+
)
|
|
2693
|
+
|
|
2694
|
+
|
|
2610
2695
|
class CaseWhen(Namespaced, SelectGrain, BaseModel):
|
|
2611
|
-
comparison: Conditional | Comparison
|
|
2696
|
+
comparison: Conditional | SubselectComparison | Comparison
|
|
2612
2697
|
expr: "Expr"
|
|
2613
2698
|
|
|
2614
2699
|
@property
|
|
@@ -2675,7 +2760,7 @@ class CaseElse(Namespaced, SelectGrain, BaseModel):
|
|
|
2675
2760
|
)
|
|
2676
2761
|
|
|
2677
2762
|
|
|
2678
|
-
class Conditional(Namespaced, SelectGrain, BaseModel):
|
|
2763
|
+
class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
2679
2764
|
left: Union[
|
|
2680
2765
|
int,
|
|
2681
2766
|
str,
|
|
@@ -2770,6 +2855,32 @@ class Conditional(Namespaced, SelectGrain, BaseModel):
|
|
|
2770
2855
|
output += get_concept_arguments(self.right)
|
|
2771
2856
|
return output
|
|
2772
2857
|
|
|
2858
|
+
@property
|
|
2859
|
+
def row_arguments(self) -> List[Concept]:
|
|
2860
|
+
output = []
|
|
2861
|
+
if isinstance(self.left, ConceptArgs):
|
|
2862
|
+
output += self.left.row_arguments
|
|
2863
|
+
else:
|
|
2864
|
+
output += get_concept_arguments(self.left)
|
|
2865
|
+
if isinstance(self.right, ConceptArgs):
|
|
2866
|
+
output += self.right.row_arguments
|
|
2867
|
+
else:
|
|
2868
|
+
output += get_concept_arguments(self.right)
|
|
2869
|
+
return output
|
|
2870
|
+
|
|
2871
|
+
@property
|
|
2872
|
+
def existence_arguments(self) -> List[Concept]:
|
|
2873
|
+
output = []
|
|
2874
|
+
if isinstance(self.left, ConceptArgs):
|
|
2875
|
+
output += self.left.existence_arguments
|
|
2876
|
+
else:
|
|
2877
|
+
output += get_concept_arguments(self.left)
|
|
2878
|
+
if isinstance(self.right, ConceptArgs):
|
|
2879
|
+
output += self.right.existence_arguments
|
|
2880
|
+
else:
|
|
2881
|
+
output += get_concept_arguments(self.right)
|
|
2882
|
+
return output
|
|
2883
|
+
|
|
2773
2884
|
|
|
2774
2885
|
class AggregateWrapper(Namespaced, SelectGrain, BaseModel):
|
|
2775
2886
|
function: Function
|
|
@@ -2813,8 +2924,8 @@ class AggregateWrapper(Namespaced, SelectGrain, BaseModel):
|
|
|
2813
2924
|
return AggregateWrapper(function=self.function.with_select_grain(grain), by=by)
|
|
2814
2925
|
|
|
2815
2926
|
|
|
2816
|
-
class WhereClause(Namespaced, SelectGrain, BaseModel):
|
|
2817
|
-
conditional: Union[Comparison, Conditional, "Parenthetical"]
|
|
2927
|
+
class WhereClause(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
2928
|
+
conditional: Union[SubselectComparison, Comparison, Conditional, "Parenthetical"]
|
|
2818
2929
|
|
|
2819
2930
|
@property
|
|
2820
2931
|
def input(self) -> List[Concept]:
|
|
@@ -2824,6 +2935,14 @@ class WhereClause(Namespaced, SelectGrain, BaseModel):
|
|
|
2824
2935
|
def concept_arguments(self) -> List[Concept]:
|
|
2825
2936
|
return self.conditional.concept_arguments
|
|
2826
2937
|
|
|
2938
|
+
@property
|
|
2939
|
+
def row_arguments(self) -> List[Concept]:
|
|
2940
|
+
return self.conditional.row_arguments
|
|
2941
|
+
|
|
2942
|
+
@property
|
|
2943
|
+
def existence_arguments(self) -> List[Concept]:
|
|
2944
|
+
return self.conditional.existence_arguments
|
|
2945
|
+
|
|
2827
2946
|
def with_namespace(self, namespace: str) -> WhereClause:
|
|
2828
2947
|
return WhereClause(conditional=self.conditional.with_namespace(namespace))
|
|
2829
2948
|
|
|
@@ -3011,7 +3130,7 @@ class RowsetItem(Namespaced, BaseModel):
|
|
|
3011
3130
|
return [self.content]
|
|
3012
3131
|
|
|
3013
3132
|
|
|
3014
|
-
class Parenthetical(Namespaced, SelectGrain, BaseModel):
|
|
3133
|
+
class Parenthetical(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
3015
3134
|
content: "Expr"
|
|
3016
3135
|
|
|
3017
3136
|
def __str__(self):
|
|
@@ -3055,6 +3174,18 @@ class Parenthetical(Namespaced, SelectGrain, BaseModel):
|
|
|
3055
3174
|
base.append(x)
|
|
3056
3175
|
return base
|
|
3057
3176
|
|
|
3177
|
+
@property
|
|
3178
|
+
def row_arguments(self) -> List[Concept]:
|
|
3179
|
+
if isinstance(self.content, ConceptArgs):
|
|
3180
|
+
return self.content.row_arguments
|
|
3181
|
+
return self.concept_arguments
|
|
3182
|
+
|
|
3183
|
+
@property
|
|
3184
|
+
def existence_arguments(self) -> List[Concept]:
|
|
3185
|
+
if isinstance(self.content, ConceptArgs):
|
|
3186
|
+
return self.content.existence_arguments
|
|
3187
|
+
return self.concept_arguments
|
|
3188
|
+
|
|
3058
3189
|
@property
|
|
3059
3190
|
def input(self):
|
|
3060
3191
|
base = []
|
{pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/concept_strategies_v3.py
RENAMED
|
@@ -23,7 +23,6 @@ from trilogy.core.processing.node_generators import (
|
|
|
23
23
|
gen_window_node,
|
|
24
24
|
gen_group_node,
|
|
25
25
|
gen_basic_node,
|
|
26
|
-
gen_select_node,
|
|
27
26
|
gen_unnest_node,
|
|
28
27
|
gen_merge_node,
|
|
29
28
|
gen_group_to_node,
|
|
@@ -208,7 +207,8 @@ def generate_node(
|
|
|
208
207
|
history: History | None = None,
|
|
209
208
|
) -> StrategyNode | None:
|
|
210
209
|
# first check in case there is a materialized_concept
|
|
211
|
-
|
|
210
|
+
history = history or History()
|
|
211
|
+
candidate = history.gen_select_node(
|
|
212
212
|
concept,
|
|
213
213
|
local_optional,
|
|
214
214
|
environment,
|
|
@@ -218,6 +218,7 @@ def generate_node(
|
|
|
218
218
|
accept_partial=accept_partial,
|
|
219
219
|
accept_partial_optional=False,
|
|
220
220
|
)
|
|
221
|
+
|
|
221
222
|
if candidate:
|
|
222
223
|
return candidate
|
|
223
224
|
|
|
@@ -316,11 +317,12 @@ def generate_node(
|
|
|
316
317
|
return gen_basic_node(
|
|
317
318
|
concept, local_optional, environment, g, depth + 1, source_concepts, history
|
|
318
319
|
)
|
|
320
|
+
|
|
319
321
|
elif concept.derivation == PurposeLineage.ROOT:
|
|
320
322
|
logger.info(
|
|
321
323
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} for {concept.address}, generating select node with optional {[x.address for x in local_optional]}"
|
|
322
324
|
)
|
|
323
|
-
return gen_select_node(
|
|
325
|
+
return history.gen_select_node(
|
|
324
326
|
concept,
|
|
325
327
|
local_optional,
|
|
326
328
|
environment,
|
|
@@ -328,6 +330,7 @@ def generate_node(
|
|
|
328
330
|
depth + 1,
|
|
329
331
|
fail_if_not_found=False,
|
|
330
332
|
accept_partial=accept_partial,
|
|
333
|
+
accept_partial_optional=True,
|
|
331
334
|
)
|
|
332
335
|
else:
|
|
333
336
|
raise ValueError(f"Unknown derivation {concept.derivation}")
|
{pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/common.py
RENAMED
|
@@ -45,21 +45,33 @@ def resolve_function_parent_concepts(concept: Concept) -> List[Concept]:
|
|
|
45
45
|
return unique(concept.lineage.concept_arguments, "address")
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def resolve_filter_parent_concepts(
|
|
48
|
+
def resolve_filter_parent_concepts(
|
|
49
|
+
concept: Concept,
|
|
50
|
+
) -> Tuple[Concept, List[Concept], List[Concept]]:
|
|
49
51
|
if not isinstance(concept.lineage, FilterItem):
|
|
50
|
-
raise ValueError
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Concept {concept} lineage is not filter item, is {type(concept.lineage)}"
|
|
54
|
+
)
|
|
51
55
|
direct_parent = concept.lineage.content
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
base_existence = []
|
|
57
|
+
base_rows = [direct_parent]
|
|
58
|
+
base_rows += concept.lineage.where.row_arguments
|
|
59
|
+
base_existence += concept.lineage.where.existence_arguments
|
|
54
60
|
if direct_parent.grain:
|
|
55
|
-
|
|
61
|
+
base_rows += direct_parent.grain.components_copy
|
|
56
62
|
if (
|
|
57
63
|
isinstance(direct_parent, Concept)
|
|
58
64
|
and direct_parent.purpose == Purpose.PROPERTY
|
|
59
65
|
and direct_parent.keys
|
|
60
66
|
):
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
base_rows += direct_parent.keys
|
|
68
|
+
if concept.lineage.where.existence_arguments:
|
|
69
|
+
return (
|
|
70
|
+
concept.lineage.content,
|
|
71
|
+
unique(base_rows, "address"),
|
|
72
|
+
unique(base_existence, "address"),
|
|
73
|
+
)
|
|
74
|
+
return concept.lineage.content, unique(base_rows, "address"), []
|
|
63
75
|
|
|
64
76
|
|
|
65
77
|
def gen_property_enrichment_node(
|
{pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
@@ -11,7 +11,7 @@ from trilogy.core.processing.node_generators.common import (
|
|
|
11
11
|
resolve_filter_parent_concepts,
|
|
12
12
|
)
|
|
13
13
|
from trilogy.constants import logger
|
|
14
|
-
from trilogy.core.processing.utility import padding
|
|
14
|
+
from trilogy.core.processing.utility import padding, unique
|
|
15
15
|
from trilogy.core.processing.node_generators.common import concept_to_relevant_joins
|
|
16
16
|
|
|
17
17
|
LOGGER_PREFIX = "[GEN_FILTER_NODE]"
|
|
@@ -26,35 +26,64 @@ def gen_filter_node(
|
|
|
26
26
|
source_concepts,
|
|
27
27
|
history: History | None = None,
|
|
28
28
|
) -> MergeNode | FilterNode | None:
|
|
29
|
-
immediate_parent,
|
|
29
|
+
immediate_parent, parent_row_concepts, parent_existence_concepts = (
|
|
30
|
+
resolve_filter_parent_concepts(concept)
|
|
31
|
+
)
|
|
30
32
|
|
|
31
|
-
logger.info(
|
|
33
|
+
logger.info(
|
|
34
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching filter node row parents {[x.address for x in parent_row_concepts]}"
|
|
35
|
+
)
|
|
36
|
+
core_parents = []
|
|
32
37
|
parent = source_concepts(
|
|
33
|
-
mandatory_list=
|
|
38
|
+
mandatory_list=parent_row_concepts,
|
|
34
39
|
environment=environment,
|
|
35
40
|
g=g,
|
|
36
41
|
depth=depth + 1,
|
|
37
42
|
history=history,
|
|
38
43
|
)
|
|
44
|
+
|
|
39
45
|
if not parent:
|
|
40
46
|
return None
|
|
47
|
+
core_parents.append(parent)
|
|
48
|
+
if parent_existence_concepts:
|
|
49
|
+
logger.info(
|
|
50
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching filter node existence parents {[x.address for x in parent_existence_concepts]}"
|
|
51
|
+
)
|
|
52
|
+
parent_existence = source_concepts(
|
|
53
|
+
mandatory_list=parent_existence_concepts,
|
|
54
|
+
environment=environment,
|
|
55
|
+
g=g,
|
|
56
|
+
depth=depth + 1,
|
|
57
|
+
history=history,
|
|
58
|
+
)
|
|
59
|
+
if not parent_existence:
|
|
60
|
+
return None
|
|
61
|
+
core_parents.append(parent_existence)
|
|
62
|
+
|
|
41
63
|
filter_node = FilterNode(
|
|
42
|
-
input_concepts=
|
|
43
|
-
|
|
64
|
+
input_concepts=unique(
|
|
65
|
+
[immediate_parent] + parent_row_concepts + parent_existence_concepts,
|
|
66
|
+
"address",
|
|
67
|
+
),
|
|
68
|
+
output_concepts=[concept, immediate_parent] + parent_row_concepts,
|
|
44
69
|
environment=environment,
|
|
45
70
|
g=g,
|
|
46
|
-
parents=
|
|
71
|
+
parents=core_parents,
|
|
47
72
|
)
|
|
48
|
-
if not local_optional
|
|
73
|
+
if not local_optional or all(
|
|
74
|
+
[x.address in [y.address for y in parent_row_concepts] for x in local_optional]
|
|
75
|
+
):
|
|
49
76
|
return filter_node
|
|
50
77
|
enrich_node = source_concepts( # this fetches the parent + join keys
|
|
51
78
|
# to then connect to the rest of the query
|
|
52
|
-
mandatory_list=[immediate_parent] +
|
|
79
|
+
mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
|
|
53
80
|
environment=environment,
|
|
54
81
|
g=g,
|
|
55
82
|
depth=depth + 1,
|
|
56
83
|
history=history,
|
|
57
84
|
)
|
|
85
|
+
if not enrich_node:
|
|
86
|
+
return filter_node
|
|
58
87
|
x = MergeNode(
|
|
59
88
|
input_concepts=[concept, immediate_parent] + local_optional,
|
|
60
89
|
output_concepts=[
|
|
@@ -73,7 +102,7 @@ def gen_filter_node(
|
|
|
73
102
|
left_node=enrich_node,
|
|
74
103
|
right_node=filter_node,
|
|
75
104
|
concepts=concept_to_relevant_joins(
|
|
76
|
-
[immediate_parent] +
|
|
105
|
+
[immediate_parent] + parent_row_concepts
|
|
77
106
|
),
|
|
78
107
|
join_type=JoinType.LEFT_OUTER,
|
|
79
108
|
filter_to_mutual=False,
|
{pytrilogy-0.0.1.103 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/merge_node.py
RENAMED
|
@@ -87,8 +87,18 @@ def gen_merge_node(
|
|
|
87
87
|
) -> Optional[MergeNode]:
|
|
88
88
|
join_candidates: List[PathInfo] = []
|
|
89
89
|
# anchor on datasources
|
|
90
|
+
final_all_concepts = []
|
|
91
|
+
# implicit_upstream = {}
|
|
92
|
+
for x in all_concepts:
|
|
93
|
+
# if x.derivation in (PurposeLineage.AGGREGATE, PurposeLineage.BASIC):
|
|
94
|
+
# final_all_concepts +=resolve_function_parent_concepts(x)
|
|
95
|
+
# elif x.derivation == PurposeLineage.FILTER:
|
|
96
|
+
# final_all_concepts +=resolve_filter_parent_concepts(x)
|
|
97
|
+
# else:
|
|
98
|
+
# final_all_concepts.append(x)
|
|
99
|
+
final_all_concepts.append(x)
|
|
90
100
|
for datasource in environment.datasources.values():
|
|
91
|
-
path = identify_ds_join_paths(
|
|
101
|
+
path = identify_ds_join_paths(final_all_concepts, g, datasource, accept_partial)
|
|
92
102
|
if path and path.reduced_concepts:
|
|
93
103
|
join_candidates.append(path)
|
|
94
104
|
join_candidates.sort(key=lambda x: sum([len(v) for v in x.paths.values()]))
|