pytrilogy 0.0.2.27__tar.gz → 0.0.2.29__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.27/pytrilogy.egg-info → pytrilogy-0.0.2.29}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/SOURCES.txt +1 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_environment.py +1 -1
- pytrilogy-0.0.2.29/tests/test_executor.py +8 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_parsing.py +31 -3
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_undefined_concept.py +1 -1
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/models.py +38 -11
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/optimization.py +1 -15
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/concept_strategies_v3.py +2 -3
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/select_merge_node.py +16 -2
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/base_node.py +0 -3
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/select_node_v2.py +1 -4
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/utility.py +41 -6
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/base.py +13 -9
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/executor.py +17 -1
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/README.md +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/pyproject.toml +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/setup.cfg +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/setup.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_models.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_select.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_show.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parsing/common.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parsing/parse_engine.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/parsing/trilogy.lark +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/utility.py +0 -0
|
@@ -7,6 +7,7 @@ from trilogy.core.models import (
|
|
|
7
7
|
Environment,
|
|
8
8
|
Comparison,
|
|
9
9
|
TupleWrapper,
|
|
10
|
+
Datasource,
|
|
10
11
|
)
|
|
11
12
|
from trilogy.core.functions import argument_to_purpose, function_args_to_output_purpose
|
|
12
13
|
from trilogy.parsing.parse_engine import (
|
|
@@ -178,7 +179,7 @@ select
|
|
|
178
179
|
)
|
|
179
180
|
|
|
180
181
|
for name in ["name_alphabetical", "name_alphabetical_2"]:
|
|
181
|
-
assert name in env.concepts
|
|
182
|
+
assert f"local.{name}" in env.concepts
|
|
182
183
|
assert env.concepts[name].purpose == Purpose.PROPERTY
|
|
183
184
|
assert env.concepts[name].keys == (env.concepts["id"],)
|
|
184
185
|
|
|
@@ -197,7 +198,6 @@ select
|
|
|
197
198
|
)
|
|
198
199
|
|
|
199
200
|
for name in ["join_id"]:
|
|
200
|
-
assert name in env.concepts
|
|
201
201
|
assert env.concepts[name].purpose == Purpose.PROPERTY
|
|
202
202
|
assert env.concepts[name].keys == (
|
|
203
203
|
env.concepts["id"],
|
|
@@ -225,7 +225,6 @@ select
|
|
|
225
225
|
)
|
|
226
226
|
# assert output_purpose == Purpose.METRIC
|
|
227
227
|
for name in ["test_name_count"]:
|
|
228
|
-
assert name in env.concepts
|
|
229
228
|
assert env.concepts[name].purpose == Purpose.METRIC
|
|
230
229
|
|
|
231
230
|
|
|
@@ -513,6 +512,35 @@ address `abc:def`
|
|
|
513
512
|
assert ds.non_partial_for.conditional.right == 10
|
|
514
513
|
|
|
515
514
|
|
|
515
|
+
def test_datasource_from_persist():
|
|
516
|
+
|
|
517
|
+
text = """
|
|
518
|
+
key x int;
|
|
519
|
+
key y int;
|
|
520
|
+
|
|
521
|
+
datasource test (
|
|
522
|
+
x:x,
|
|
523
|
+
y:y)
|
|
524
|
+
grain(x)
|
|
525
|
+
address `abc:def`
|
|
526
|
+
;
|
|
527
|
+
|
|
528
|
+
persist alias into tbl_alias from
|
|
529
|
+
select
|
|
530
|
+
x,
|
|
531
|
+
y
|
|
532
|
+
where y>10;
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
"""
|
|
537
|
+
env, parsed = parse_text(text)
|
|
538
|
+
|
|
539
|
+
ds: Datasource = parsed[-1].datasource
|
|
540
|
+
assert ds.non_partial_for.conditional.right == 10
|
|
541
|
+
assert not ds.where
|
|
542
|
+
|
|
543
|
+
|
|
516
544
|
def test_filter_concise():
|
|
517
545
|
|
|
518
546
|
text = """
|
|
@@ -32,6 +32,6 @@ def test_undefined_concept_dict():
|
|
|
32
32
|
try:
|
|
33
33
|
env["orid"]
|
|
34
34
|
except UndefinedConceptException as e:
|
|
35
|
-
assert e.suggestions == ["order_id"]
|
|
35
|
+
assert e.suggestions == ["local.order_id"]
|
|
36
36
|
assert "suggestions" in e.message.lower()
|
|
37
37
|
assert "order_id" in e.message.lower()
|
|
@@ -606,6 +606,8 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
606
606
|
return self.grain.components_copy if self.grain else []
|
|
607
607
|
|
|
608
608
|
def with_namespace(self, namespace: str) -> "Concept":
|
|
609
|
+
if namespace == self.namespace:
|
|
610
|
+
return self
|
|
609
611
|
return self.__class__(
|
|
610
612
|
name=self.name,
|
|
611
613
|
datatype=self.datatype,
|
|
@@ -1758,7 +1760,6 @@ class SelectStatement(HasUUID, Mergeable, Namespaced, SelectTypeMixin, BaseModel
|
|
|
1758
1760
|
grain=grain or self.grain,
|
|
1759
1761
|
columns=columns,
|
|
1760
1762
|
namespace=namespace,
|
|
1761
|
-
where=WhereClause(conditional=condition) if condition else None,
|
|
1762
1763
|
non_partial_for=WhereClause(conditional=condition) if condition else None,
|
|
1763
1764
|
)
|
|
1764
1765
|
for column in columns:
|
|
@@ -3253,7 +3254,6 @@ class EnvironmentConceptDict(dict):
|
|
|
3253
3254
|
)
|
|
3254
3255
|
self.undefined[key] = undefined
|
|
3255
3256
|
return undefined
|
|
3256
|
-
|
|
3257
3257
|
matches = self._find_similar_concepts(key)
|
|
3258
3258
|
message = f"Undefined concept: {key}."
|
|
3259
3259
|
if matches:
|
|
@@ -3263,8 +3263,15 @@ class EnvironmentConceptDict(dict):
|
|
|
3263
3263
|
raise UndefinedConceptException(f"line: {line_no}: " + message, matches)
|
|
3264
3264
|
raise UndefinedConceptException(message, matches)
|
|
3265
3265
|
|
|
3266
|
-
def _find_similar_concepts(self, concept_name):
|
|
3267
|
-
|
|
3266
|
+
def _find_similar_concepts(self, concept_name: str):
|
|
3267
|
+
def strip_local(input: str):
|
|
3268
|
+
if input.startswith(f"{DEFAULT_NAMESPACE}."):
|
|
3269
|
+
return input[len(DEFAULT_NAMESPACE) + 1 :]
|
|
3270
|
+
return input
|
|
3271
|
+
|
|
3272
|
+
matches = difflib.get_close_matches(
|
|
3273
|
+
strip_local(concept_name), [strip_local(x) for x in self.keys()]
|
|
3274
|
+
)
|
|
3268
3275
|
return matches
|
|
3269
3276
|
|
|
3270
3277
|
def items(self) -> ItemsView[str, Concept]: # type: ignore
|
|
@@ -3325,7 +3332,6 @@ class Environment(BaseModel):
|
|
|
3325
3332
|
|
|
3326
3333
|
materialized_concepts: List[Concept] = Field(default_factory=list)
|
|
3327
3334
|
alias_origin_lookup: Dict[str, Concept] = Field(default_factory=dict)
|
|
3328
|
-
canonical_map: Dict[str, str] = Field(default_factory=dict)
|
|
3329
3335
|
_parse_count: int = 0
|
|
3330
3336
|
|
|
3331
3337
|
@classmethod
|
|
@@ -3444,14 +3450,38 @@ class Environment(BaseModel):
|
|
|
3444
3450
|
exists = True
|
|
3445
3451
|
imp_stm = ImportStatement(alias=alias, path=Path(source.working_path))
|
|
3446
3452
|
|
|
3453
|
+
same_namespace = alias == self.namespace
|
|
3454
|
+
|
|
3447
3455
|
if not exists:
|
|
3448
3456
|
self.imports[alias].append(imp_stm)
|
|
3449
3457
|
|
|
3450
|
-
for
|
|
3451
|
-
|
|
3458
|
+
for k, concept in source.concepts.items():
|
|
3459
|
+
if same_namespace:
|
|
3460
|
+
new = self.add_concept(concept, _ignore_cache=True)
|
|
3461
|
+
else:
|
|
3462
|
+
new = self.add_concept(
|
|
3463
|
+
concept.with_namespace(alias), _ignore_cache=True
|
|
3464
|
+
)
|
|
3465
|
+
|
|
3466
|
+
k = address_with_namespace(k, alias)
|
|
3467
|
+
# set this explicitly, to handle aliasing
|
|
3468
|
+
self.concepts[k] = new
|
|
3452
3469
|
|
|
3453
3470
|
for _, datasource in source.datasources.items():
|
|
3454
|
-
|
|
3471
|
+
if same_namespace:
|
|
3472
|
+
self.add_datasource(datasource, _ignore_cache=True)
|
|
3473
|
+
else:
|
|
3474
|
+
self.add_datasource(
|
|
3475
|
+
datasource.with_namespace(alias), _ignore_cache=True
|
|
3476
|
+
)
|
|
3477
|
+
for key, val in source.alias_origin_lookup.items():
|
|
3478
|
+
if same_namespace:
|
|
3479
|
+
self.alias_origin_lookup[key] = val
|
|
3480
|
+
else:
|
|
3481
|
+
self.alias_origin_lookup[address_with_namespace(key, alias)] = (
|
|
3482
|
+
val.with_namespace(alias)
|
|
3483
|
+
)
|
|
3484
|
+
|
|
3455
3485
|
self.gen_concept_list_caches()
|
|
3456
3486
|
return self
|
|
3457
3487
|
|
|
@@ -3542,8 +3572,6 @@ class Environment(BaseModel):
|
|
|
3542
3572
|
existing = self.validate_concept(concept, meta=meta)
|
|
3543
3573
|
if existing:
|
|
3544
3574
|
concept = existing
|
|
3545
|
-
if concept.namespace == DEFAULT_NAMESPACE:
|
|
3546
|
-
self.concepts[concept.name] = concept
|
|
3547
3575
|
self.concepts[concept.address] = concept
|
|
3548
3576
|
from trilogy.core.environment_helpers import generate_related_concepts
|
|
3549
3577
|
|
|
@@ -3631,7 +3659,6 @@ class Environment(BaseModel):
|
|
|
3631
3659
|
v.pseudonyms.add(source.address)
|
|
3632
3660
|
if v.address == source.address:
|
|
3633
3661
|
replacements[k] = target
|
|
3634
|
-
self.canonical_map[k] = target.address
|
|
3635
3662
|
v.pseudonyms.add(target.address)
|
|
3636
3663
|
# we need to update keys and grains of all concepts
|
|
3637
3664
|
else:
|
|
@@ -13,7 +13,7 @@ from trilogy.core.optimizations import (
|
|
|
13
13
|
PredicatePushdownRemove,
|
|
14
14
|
InlineDatasource,
|
|
15
15
|
)
|
|
16
|
-
|
|
16
|
+
from trilogy.core.processing.utility import sort_select_output
|
|
17
17
|
|
|
18
18
|
MAX_OPTIMIZATION_LOOPS = 100
|
|
19
19
|
|
|
@@ -154,20 +154,6 @@ def is_direct_return_eligible(cte: CTE) -> CTE | None:
|
|
|
154
154
|
return direct_parent
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
def sort_select_output(cte: CTE, query: SelectStatement | MultiSelectStatement):
|
|
158
|
-
hidden_addresses = [c.address for c in query.hidden_components]
|
|
159
|
-
output_addresses = [
|
|
160
|
-
c.address for c in query.output_components if c.address not in hidden_addresses
|
|
161
|
-
]
|
|
162
|
-
|
|
163
|
-
mapping = {x.address: x for x in cte.output_columns}
|
|
164
|
-
|
|
165
|
-
new_output = []
|
|
166
|
-
for x in output_addresses:
|
|
167
|
-
new_output.append(mapping[x])
|
|
168
|
-
cte.output_columns = new_output
|
|
169
|
-
|
|
170
|
-
|
|
171
157
|
def optimize_ctes(
|
|
172
158
|
input: list[CTE], root_cte: CTE, select: SelectStatement | MultiSelectStatement
|
|
173
159
|
) -> list[CTE]:
|
|
@@ -838,9 +838,8 @@ def _search_concepts(
|
|
|
838
838
|
f" {accept_partial} (complete: {complete}), have {found} from {[n for n in stack]} (missing {missing} partial {partial} virtual {virtual}), attempted {attempted}, mandatory w/ filter {mandatory_completion}"
|
|
839
839
|
)
|
|
840
840
|
if complete == ValidationResult.INCOMPLETE_CONDITION:
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
)
|
|
841
|
+
cond_dict = {str(node): node.preexisting_conditions for node in stack}
|
|
842
|
+
raise SyntaxError(f"Have {cond_dict} and need {str(conditions)}")
|
|
844
843
|
# early exit if we have a complete stack with one node
|
|
845
844
|
# we can only early exit if we have a complete stack
|
|
846
845
|
# and we are not looking for more non-partial sources
|
|
@@ -233,7 +233,6 @@ def create_select_node(
|
|
|
233
233
|
]
|
|
234
234
|
nullable_lcl = LooseConceptList(concepts=nullable_concepts)
|
|
235
235
|
partial_is_full = conditions and (conditions == datasource.non_partial_for)
|
|
236
|
-
|
|
237
236
|
bcandidate: StrategyNode = SelectNode(
|
|
238
237
|
input_concepts=[c.concept for c in datasource.columns],
|
|
239
238
|
output_concepts=all_concepts,
|
|
@@ -249,7 +248,9 @@ def create_select_node(
|
|
|
249
248
|
datasource=datasource,
|
|
250
249
|
grain=Grain(components=all_concepts),
|
|
251
250
|
conditions=datasource.where.conditional if datasource.where else None,
|
|
252
|
-
|
|
251
|
+
preexisting_conditions=(
|
|
252
|
+
conditions.conditional if partial_is_full and conditions else None
|
|
253
|
+
),
|
|
253
254
|
)
|
|
254
255
|
|
|
255
256
|
# we need to nest the group node one further
|
|
@@ -263,6 +264,9 @@ def create_select_node(
|
|
|
263
264
|
depth=depth,
|
|
264
265
|
partial_concepts=bcandidate.partial_concepts,
|
|
265
266
|
nullable_concepts=bcandidate.nullable_concepts,
|
|
267
|
+
preexisting_conditions=(
|
|
268
|
+
conditions.conditional if partial_is_full and conditions else None
|
|
269
|
+
),
|
|
266
270
|
)
|
|
267
271
|
else:
|
|
268
272
|
candidate = bcandidate
|
|
@@ -340,6 +344,15 @@ def gen_select_merge_node(
|
|
|
340
344
|
|
|
341
345
|
if len(parents) == 1:
|
|
342
346
|
return parents[0]
|
|
347
|
+
preexisting_conditions = None
|
|
348
|
+
if conditions and all(
|
|
349
|
+
[
|
|
350
|
+
x.preexisting_conditions
|
|
351
|
+
and x.preexisting_conditions == conditions.conditional
|
|
352
|
+
for x in parents
|
|
353
|
+
]
|
|
354
|
+
):
|
|
355
|
+
preexisting_conditions = conditions.conditional
|
|
343
356
|
return MergeNode(
|
|
344
357
|
output_concepts=all_concepts,
|
|
345
358
|
input_concepts=non_constant,
|
|
@@ -347,4 +360,5 @@ def gen_select_merge_node(
|
|
|
347
360
|
g=g,
|
|
348
361
|
depth=depth,
|
|
349
362
|
parents=parents,
|
|
363
|
+
preexisting_conditions=preexisting_conditions,
|
|
350
364
|
)
|
|
@@ -165,7 +165,6 @@ class StrategyNode:
|
|
|
165
165
|
hidden_concepts: List[Concept] | None = None,
|
|
166
166
|
existence_concepts: List[Concept] | None = None,
|
|
167
167
|
virtual_output_concepts: List[Concept] | None = None,
|
|
168
|
-
render_condition: bool = True,
|
|
169
168
|
):
|
|
170
169
|
self.input_concepts: List[Concept] = (
|
|
171
170
|
unique(input_concepts, "address") if input_concepts else []
|
|
@@ -209,7 +208,6 @@ class StrategyNode:
|
|
|
209
208
|
)
|
|
210
209
|
self.validate_parents()
|
|
211
210
|
self.log = True
|
|
212
|
-
self.render_condition = render_condition
|
|
213
211
|
|
|
214
212
|
def add_parents(self, parents: list["StrategyNode"]):
|
|
215
213
|
self.parents += parents
|
|
@@ -382,7 +380,6 @@ class StrategyNode:
|
|
|
382
380
|
hidden_concepts=list(self.hidden_concepts),
|
|
383
381
|
existence_concepts=list(self.existence_concepts),
|
|
384
382
|
virtual_output_concepts=list(self.virtual_output_concepts),
|
|
385
|
-
render_condition=self.render_condition,
|
|
386
383
|
)
|
|
387
384
|
|
|
388
385
|
|
|
@@ -49,7 +49,6 @@ class SelectNode(StrategyNode):
|
|
|
49
49
|
conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
50
50
|
preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
51
51
|
hidden_concepts: List[Concept] | None = None,
|
|
52
|
-
render_condition: bool = True,
|
|
53
52
|
):
|
|
54
53
|
super().__init__(
|
|
55
54
|
input_concepts=input_concepts,
|
|
@@ -66,7 +65,6 @@ class SelectNode(StrategyNode):
|
|
|
66
65
|
conditions=conditions,
|
|
67
66
|
preexisting_conditions=preexisting_conditions,
|
|
68
67
|
hidden_concepts=hidden_concepts,
|
|
69
|
-
render_condition=render_condition,
|
|
70
68
|
)
|
|
71
69
|
self.accept_partial = accept_partial
|
|
72
70
|
self.datasource = datasource
|
|
@@ -123,7 +121,7 @@ class SelectNode(StrategyNode):
|
|
|
123
121
|
nullable_concepts=[c.concept for c in datasource.columns if c.is_nullable],
|
|
124
122
|
source_type=SourceType.DIRECT_SELECT,
|
|
125
123
|
# we can skip rendering conditions
|
|
126
|
-
condition=self.conditions
|
|
124
|
+
condition=self.conditions,
|
|
127
125
|
# select nodes should never group
|
|
128
126
|
force_group=self.force_group,
|
|
129
127
|
hidden_concepts=self.hidden_concepts,
|
|
@@ -208,7 +206,6 @@ class SelectNode(StrategyNode):
|
|
|
208
206
|
conditions=self.conditions,
|
|
209
207
|
preexisting_conditions=self.preexisting_conditions,
|
|
210
208
|
hidden_concepts=self.hidden_concepts,
|
|
211
|
-
render_condition=self.render_condition,
|
|
212
209
|
)
|
|
213
210
|
|
|
214
211
|
|
|
@@ -29,6 +29,10 @@ from trilogy.core.models import (
|
|
|
29
29
|
NumericType,
|
|
30
30
|
ListType,
|
|
31
31
|
TupleWrapper,
|
|
32
|
+
CTE,
|
|
33
|
+
MultiSelectStatement,
|
|
34
|
+
SelectStatement,
|
|
35
|
+
ProcessedQuery,
|
|
32
36
|
)
|
|
33
37
|
|
|
34
38
|
from trilogy.core.enums import Purpose, Granularity, BooleanOperator
|
|
@@ -66,9 +70,6 @@ def resolve_join_order_v2(
|
|
|
66
70
|
) -> list[JoinOrderOutput]:
|
|
67
71
|
datasources = [x for x in g.nodes if x.startswith("ds~")]
|
|
68
72
|
concepts = [x for x in g.nodes if x.startswith("c~")]
|
|
69
|
-
# from trilogy.hooks.graph_hook import GraphHook
|
|
70
|
-
|
|
71
|
-
# GraphHook().query_graph_built(g)
|
|
72
73
|
|
|
73
74
|
output: list[JoinOrderOutput] = []
|
|
74
75
|
pivot_map = {
|
|
@@ -78,7 +79,7 @@ def resolve_join_order_v2(
|
|
|
78
79
|
pivots = list(
|
|
79
80
|
sorted(
|
|
80
81
|
[x for x in pivot_map if len(pivot_map[x]) > 1],
|
|
81
|
-
key=lambda x: len(pivot_map[x]),
|
|
82
|
+
key=lambda x: (len(pivot_map[x]), len(x), x),
|
|
82
83
|
)
|
|
83
84
|
)
|
|
84
85
|
solo = [x for x in pivot_map if len(pivot_map[x]) == 1]
|
|
@@ -95,7 +96,7 @@ def resolve_join_order_v2(
|
|
|
95
96
|
root = pivots.pop()
|
|
96
97
|
|
|
97
98
|
# sort so less partials is last and eligible lefts are
|
|
98
|
-
def score_key(x: str) -> int:
|
|
99
|
+
def score_key(x: str) -> tuple[int, int, str]:
|
|
99
100
|
base = 1
|
|
100
101
|
# if it's left, higher weight
|
|
101
102
|
if x in eligible_left:
|
|
@@ -103,7 +104,7 @@ def resolve_join_order_v2(
|
|
|
103
104
|
# if it has the concept as a partial, lower weight
|
|
104
105
|
if root in partials.get(x, []):
|
|
105
106
|
base -= 1
|
|
106
|
-
return base
|
|
107
|
+
return (base, len(x), x)
|
|
107
108
|
|
|
108
109
|
# get remainig un-joined datasets
|
|
109
110
|
to_join = sorted(
|
|
@@ -531,3 +532,37 @@ def find_nullable_concepts(
|
|
|
531
532
|
if set(v).issubset(all_ds):
|
|
532
533
|
final_nullable.add(k)
|
|
533
534
|
return list(sorted(final_nullable))
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def sort_select_output_processed(cte: CTE, query: ProcessedQuery) -> CTE:
|
|
538
|
+
hidden_addresses = [c.address for c in query.hidden_columns]
|
|
539
|
+
output_addresses = [
|
|
540
|
+
c.address for c in query.output_columns if c.address not in hidden_addresses
|
|
541
|
+
]
|
|
542
|
+
|
|
543
|
+
mapping = {x.address: x for x in cte.output_columns}
|
|
544
|
+
|
|
545
|
+
new_output = []
|
|
546
|
+
for x in output_addresses:
|
|
547
|
+
new_output.append(mapping[x])
|
|
548
|
+
cte.output_columns = new_output
|
|
549
|
+
return cte
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def sort_select_output(
|
|
553
|
+
cte: CTE, query: SelectStatement | MultiSelectStatement | ProcessedQuery
|
|
554
|
+
) -> CTE:
|
|
555
|
+
if isinstance(query, ProcessedQuery):
|
|
556
|
+
return sort_select_output_processed(cte, query)
|
|
557
|
+
hidden_addresses = [c.address for c in query.hidden_components]
|
|
558
|
+
output_addresses = [
|
|
559
|
+
c.address for c in query.output_components if c.address not in hidden_addresses
|
|
560
|
+
]
|
|
561
|
+
|
|
562
|
+
mapping = {x.address: x for x in cte.output_columns}
|
|
563
|
+
|
|
564
|
+
new_output = []
|
|
565
|
+
for x in output_addresses:
|
|
566
|
+
new_output.append(mapping[x])
|
|
567
|
+
cte.output_columns = new_output
|
|
568
|
+
return cte
|
|
@@ -2,7 +2,11 @@ from typing import List, Union, Optional, Dict, Any, Sequence, Callable
|
|
|
2
2
|
|
|
3
3
|
from jinja2 import Template
|
|
4
4
|
|
|
5
|
-
from trilogy.core.processing.utility import
|
|
5
|
+
from trilogy.core.processing.utility import (
|
|
6
|
+
is_scalar_condition,
|
|
7
|
+
decompose_condition,
|
|
8
|
+
sort_select_output,
|
|
9
|
+
)
|
|
6
10
|
from trilogy.constants import CONFIG, logger, MagicConstants
|
|
7
11
|
from trilogy.core.internal import DEFAULT_CONCEPTS
|
|
8
12
|
from trilogy.core.enums import (
|
|
@@ -537,7 +541,7 @@ class BaseDialect:
|
|
|
537
541
|
else:
|
|
538
542
|
raise ValueError(f"Unable to render type {type(e)} {e}")
|
|
539
543
|
|
|
540
|
-
def render_cte(self, cte: CTE):
|
|
544
|
+
def render_cte(self, cte: CTE, auto_sort: bool = True):
|
|
541
545
|
if self.UNNEST_MODE in (
|
|
542
546
|
UnnestMode.CROSS_APPLY,
|
|
543
547
|
UnnestMode.CROSS_JOIN,
|
|
@@ -561,6 +565,8 @@ class BaseDialect:
|
|
|
561
565
|
for c in cte.output_columns
|
|
562
566
|
if c.address not in [y.address for y in cte.hidden_concepts]
|
|
563
567
|
]
|
|
568
|
+
if auto_sort:
|
|
569
|
+
select_columns = sorted(select_columns, key=lambda x: x)
|
|
564
570
|
source: str | None = cte.base_name
|
|
565
571
|
if not cte.render_from_clause:
|
|
566
572
|
if len(cte.joins) > 0:
|
|
@@ -657,8 +663,11 @@ class BaseDialect:
|
|
|
657
663
|
def generate_ctes(
|
|
658
664
|
self,
|
|
659
665
|
query: ProcessedQuery,
|
|
660
|
-
):
|
|
661
|
-
return [self.render_cte(cte) for cte in query.ctes]
|
|
666
|
+
) -> List[CompiledCTE]:
|
|
667
|
+
return [self.render_cte(cte) for cte in query.ctes[:-1]] + [
|
|
668
|
+
# last CTE needs to respect the user output order
|
|
669
|
+
self.render_cte(sort_select_output(query.ctes[-1], query), auto_sort=False)
|
|
670
|
+
]
|
|
662
671
|
|
|
663
672
|
def generate_queries(
|
|
664
673
|
self,
|
|
@@ -790,11 +799,6 @@ class BaseDialect:
|
|
|
790
799
|
|
|
791
800
|
compiled_ctes = self.generate_ctes(query)
|
|
792
801
|
|
|
793
|
-
# restort selections by the order they were written in
|
|
794
|
-
sorted_select: List[str] = []
|
|
795
|
-
for output_c in output_addresses:
|
|
796
|
-
sorted_select.append(select_columns[output_c])
|
|
797
|
-
|
|
798
802
|
final = self.SQL_TEMPLATE.render(
|
|
799
803
|
output=(
|
|
800
804
|
query.output_to if isinstance(query, ProcessedQueryPersist) else None
|
|
@@ -276,6 +276,20 @@ class Executor(object):
|
|
|
276
276
|
output.append(compiled_sql)
|
|
277
277
|
return output
|
|
278
278
|
|
|
279
|
+
def parse_file(self, file: str | Path, persist: bool = False) -> Generator[
|
|
280
|
+
ProcessedQuery
|
|
281
|
+
| ProcessedQueryPersist
|
|
282
|
+
| ProcessedShowStatement
|
|
283
|
+
| ProcessedRawSQLStatement
|
|
284
|
+
| ProcessedCopyStatement,
|
|
285
|
+
None,
|
|
286
|
+
None,
|
|
287
|
+
]:
|
|
288
|
+
file = Path(file)
|
|
289
|
+
with open(file, "r") as f:
|
|
290
|
+
command = f.read()
|
|
291
|
+
return self.parse_text_generator(command, persist=persist)
|
|
292
|
+
|
|
279
293
|
def parse_text(
|
|
280
294
|
self, command: str, persist: bool = False
|
|
281
295
|
) -> List[
|
|
@@ -319,9 +333,11 @@ class Executor(object):
|
|
|
319
333
|
x = self.generator.generate_queries(
|
|
320
334
|
self.environment, [t], hooks=self.hooks
|
|
321
335
|
)[0]
|
|
336
|
+
|
|
337
|
+
yield x
|
|
338
|
+
|
|
322
339
|
if persist and isinstance(x, ProcessedQueryPersist):
|
|
323
340
|
self.environment.add_datasource(x.datasource)
|
|
324
|
-
yield x
|
|
325
341
|
|
|
326
342
|
def execute_raw_sql(
|
|
327
343
|
self, command: str, variables: dict | None = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/__init__.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/basic_node.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/group_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/group_to_node.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/node_merge_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/rowset_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/select_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/unnest_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.27 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/window_node.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|