pytrilogy 0.0.2.36__tar.gz → 0.0.2.38__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.36/pytrilogy.egg-info → pytrilogy-0.0.2.38}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_select.py +1 -1
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/constants.py +8 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/models.py +23 -20
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/base.py +8 -1
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/executor.py +77 -15
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parsing/parse_engine.py +46 -13
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parsing/render.py +3 -1
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parsing/trilogy.lark +4 -4
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/README.md +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/pyproject.toml +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/setup.cfg +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/setup.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_models.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_show.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/concept_strategies_v3.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parsing/common.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/utility.py +0 -0
|
@@ -39,6 +39,13 @@ class Comments:
|
|
|
39
39
|
partial: bool = True
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
@dataclass
|
|
43
|
+
class Rendering:
|
|
44
|
+
"""Control how the SQL is rendered"""
|
|
45
|
+
|
|
46
|
+
parameters: bool = True
|
|
47
|
+
|
|
48
|
+
|
|
42
49
|
# TODO: support loading from environments
|
|
43
50
|
@dataclass
|
|
44
51
|
class Config:
|
|
@@ -48,6 +55,7 @@ class Config:
|
|
|
48
55
|
validate_missing: bool = True
|
|
49
56
|
comments: Comments = field(default_factory=Comments)
|
|
50
57
|
optimizations: Optimizations = field(default_factory=Optimizations)
|
|
58
|
+
rendering: Rendering = field(default_factory=Rendering)
|
|
51
59
|
|
|
52
60
|
@property
|
|
53
61
|
def show_comments(self) -> bool:
|
|
@@ -2085,14 +2085,16 @@ class DatasourceMetadata(BaseModel):
|
|
|
2085
2085
|
|
|
2086
2086
|
|
|
2087
2087
|
class MergeStatementV2(HasUUID, Namespaced, BaseModel):
|
|
2088
|
-
|
|
2089
|
-
|
|
2088
|
+
sources: list[Concept]
|
|
2089
|
+
targets: dict[str, Concept]
|
|
2090
|
+
source_wildcard: str | None = None
|
|
2091
|
+
target_wildcard: str | None = None
|
|
2090
2092
|
modifiers: List[Modifier] = Field(default_factory=list)
|
|
2091
2093
|
|
|
2092
2094
|
def with_namespace(self, namespace: str) -> "MergeStatementV2":
|
|
2093
2095
|
new = MergeStatementV2(
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
+
sources=[x.with_namespace(namespace) for x in self.sources],
|
|
2097
|
+
targets={k: v.with_namespace(namespace) for k, v in self.targets.items()},
|
|
2096
2098
|
modifiers=self.modifiers,
|
|
2097
2099
|
)
|
|
2098
2100
|
return new
|
|
@@ -3381,6 +3383,21 @@ class Environment(BaseModel):
|
|
|
3381
3383
|
materialized_concepts: List[Concept] = Field(default_factory=list)
|
|
3382
3384
|
alias_origin_lookup: Dict[str, Concept] = Field(default_factory=dict)
|
|
3383
3385
|
|
|
3386
|
+
def __init__(self, **data):
|
|
3387
|
+
super().__init__(**data)
|
|
3388
|
+
self.concepts["_env_working_path"] = Concept(
|
|
3389
|
+
name="_env_working_path",
|
|
3390
|
+
namespace=self.namespace,
|
|
3391
|
+
lineage=Function(
|
|
3392
|
+
operator=FunctionType.CONSTANT,
|
|
3393
|
+
arguments=[str(self.working_path)],
|
|
3394
|
+
output_datatype=DataType.STRING,
|
|
3395
|
+
output_purpose=Purpose.CONSTANT,
|
|
3396
|
+
),
|
|
3397
|
+
datatype=DataType.STRING,
|
|
3398
|
+
purpose=Purpose.CONSTANT,
|
|
3399
|
+
)
|
|
3400
|
+
|
|
3384
3401
|
@classmethod
|
|
3385
3402
|
def from_file(cls, path: str | Path) -> "Environment":
|
|
3386
3403
|
with open(path, "r") as f:
|
|
@@ -3554,16 +3571,7 @@ class Environment(BaseModel):
|
|
|
3554
3571
|
target = target.with_suffix(".preql")
|
|
3555
3572
|
else:
|
|
3556
3573
|
target = path
|
|
3557
|
-
if
|
|
3558
|
-
imports = self.imports[alias]
|
|
3559
|
-
for x in imports:
|
|
3560
|
-
if x.path == target:
|
|
3561
|
-
return imports
|
|
3562
|
-
if env:
|
|
3563
|
-
self.imports[alias].append(
|
|
3564
|
-
ImportStatement(alias=alias, path=target, environment=env)
|
|
3565
|
-
)
|
|
3566
|
-
else:
|
|
3574
|
+
if not env:
|
|
3567
3575
|
parse_address = gen_cache_lookup(str(target), alias, str(self.working_path))
|
|
3568
3576
|
try:
|
|
3569
3577
|
with open(target, "r", encoding="utf-8") as f:
|
|
@@ -3587,13 +3595,8 @@ class Environment(BaseModel):
|
|
|
3587
3595
|
f"Unable to import file {target.parent}, parsing error: {e}"
|
|
3588
3596
|
)
|
|
3589
3597
|
env = nparser.environment
|
|
3590
|
-
for _, concept in env.concepts.items():
|
|
3591
|
-
self.add_concept(concept.with_namespace(alias))
|
|
3592
|
-
|
|
3593
|
-
for _, datasource in env.datasources.items():
|
|
3594
|
-
self.add_datasource(datasource.with_namespace(alias))
|
|
3595
3598
|
imps = ImportStatement(alias=alias, path=target, environment=env)
|
|
3596
|
-
self.
|
|
3599
|
+
self.add_import(alias, source=env, imp_stm=imps)
|
|
3597
3600
|
return imps
|
|
3598
3601
|
|
|
3599
3602
|
def parse(
|
|
@@ -337,6 +337,13 @@ class BaseDialect:
|
|
|
337
337
|
" target grain"
|
|
338
338
|
)
|
|
339
339
|
rval = f"{self.FUNCTION_GRAIN_MATCH_MAP[c.lineage.function.operator](args)}"
|
|
340
|
+
elif (
|
|
341
|
+
isinstance(c.lineage, Function)
|
|
342
|
+
and c.lineage.operator == FunctionType.CONSTANT
|
|
343
|
+
and CONFIG.rendering.parameters is True
|
|
344
|
+
and c.datatype.data_type != DataType.MAP
|
|
345
|
+
):
|
|
346
|
+
rval = f":{c.safe_address}"
|
|
340
347
|
else:
|
|
341
348
|
args = [
|
|
342
349
|
self.render_expr(
|
|
@@ -541,7 +548,7 @@ class BaseDialect:
|
|
|
541
548
|
else:
|
|
542
549
|
raise ValueError(f"Unable to render type {type(e)} {e}")
|
|
543
550
|
|
|
544
|
-
def render_cte(self, cte: CTE, auto_sort: bool = True):
|
|
551
|
+
def render_cte(self, cte: CTE, auto_sort: bool = True) -> CompiledCTE:
|
|
545
552
|
if self.UNNEST_MODE in (
|
|
546
553
|
UnnestMode.CROSS_APPLY,
|
|
547
554
|
UnnestMode.CROSS_JOIN,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Optional, Any, Generator
|
|
1
|
+
from typing import List, Optional, Any, Generator, Protocol
|
|
2
2
|
from functools import singledispatchmethod
|
|
3
3
|
from sqlalchemy import text
|
|
4
4
|
from sqlalchemy.engine import Engine, CursorResult
|
|
@@ -22,16 +22,29 @@ from trilogy.core.models import (
|
|
|
22
22
|
CopyStatement,
|
|
23
23
|
ImportStatement,
|
|
24
24
|
MergeStatementV2,
|
|
25
|
+
Function,
|
|
26
|
+
FunctionType,
|
|
27
|
+
MapWrapper,
|
|
28
|
+
ListWrapper,
|
|
25
29
|
)
|
|
26
30
|
from trilogy.dialect.base import BaseDialect
|
|
27
31
|
from trilogy.dialect.enums import Dialects
|
|
28
|
-
from trilogy.core.enums import IOType
|
|
32
|
+
from trilogy.core.enums import IOType, Granularity
|
|
29
33
|
from trilogy.parser import parse_text
|
|
30
34
|
from trilogy.hooks.base_hook import BaseHook
|
|
31
35
|
from pathlib import Path
|
|
32
36
|
from dataclasses import dataclass
|
|
33
37
|
|
|
34
38
|
|
|
39
|
+
class ResultProtocol(Protocol):
|
|
40
|
+
values: List[Any]
|
|
41
|
+
columns: List[str]
|
|
42
|
+
|
|
43
|
+
def fetchall(self) -> List[Any]: ...
|
|
44
|
+
|
|
45
|
+
def keys(self) -> List[str]: ...
|
|
46
|
+
|
|
47
|
+
|
|
35
48
|
@dataclass
|
|
36
49
|
class MockResult:
|
|
37
50
|
values: list[Any]
|
|
@@ -185,7 +198,6 @@ class Executor(object):
|
|
|
185
198
|
|
|
186
199
|
@execute_query.register
|
|
187
200
|
def _(self, query: ImportStatement) -> CursorResult:
|
|
188
|
-
self.environment.add_file_import(query.path, query.alias)
|
|
189
201
|
return MockResult(
|
|
190
202
|
[
|
|
191
203
|
{
|
|
@@ -198,15 +210,16 @@ class Executor(object):
|
|
|
198
210
|
|
|
199
211
|
@execute_query.register
|
|
200
212
|
def _(self, query: MergeStatementV2) -> CursorResult:
|
|
213
|
+
for concept in query.sources:
|
|
214
|
+
self.environment.merge_concept(
|
|
215
|
+
concept, query.targets[concept.address], modifiers=query.modifiers
|
|
216
|
+
)
|
|
201
217
|
|
|
202
|
-
self.environment.merge_concept(
|
|
203
|
-
query.source, query.target, modifiers=query.modifiers
|
|
204
|
-
)
|
|
205
218
|
return MockResult(
|
|
206
219
|
[
|
|
207
220
|
{
|
|
208
|
-
"
|
|
209
|
-
"
|
|
221
|
+
"sources": ",".join([x.address for x in query.sources]),
|
|
222
|
+
"targets": ",".join([x.address for _, x in query.targets.items()]),
|
|
210
223
|
}
|
|
211
224
|
],
|
|
212
225
|
["source", "target"],
|
|
@@ -219,8 +232,7 @@ class Executor(object):
|
|
|
219
232
|
@execute_query.register
|
|
220
233
|
def _(self, query: ProcessedQuery) -> CursorResult:
|
|
221
234
|
sql = self.generator.compile_statement(query)
|
|
222
|
-
|
|
223
|
-
output = self.connection.execute(text(sql))
|
|
235
|
+
output = self.execute_raw_sql(sql)
|
|
224
236
|
return output
|
|
225
237
|
|
|
226
238
|
@execute_query.register
|
|
@@ -228,14 +240,14 @@ class Executor(object):
|
|
|
228
240
|
|
|
229
241
|
sql = self.generator.compile_statement(query)
|
|
230
242
|
|
|
231
|
-
output = self.
|
|
243
|
+
output = self.execute_raw_sql(sql)
|
|
232
244
|
self.environment.add_datasource(query.datasource)
|
|
233
245
|
return output
|
|
234
246
|
|
|
235
247
|
@execute_query.register
|
|
236
248
|
def _(self, query: ProcessedCopyStatement) -> CursorResult:
|
|
237
249
|
sql = self.generator.compile_statement(query)
|
|
238
|
-
output: CursorResult = self.
|
|
250
|
+
output: CursorResult = self.execute_raw_sql(sql)
|
|
239
251
|
if query.target_type == IOType.CSV:
|
|
240
252
|
import csv
|
|
241
253
|
|
|
@@ -244,7 +256,7 @@ class Executor(object):
|
|
|
244
256
|
outcsv.writerow(output.keys())
|
|
245
257
|
outcsv.writerows(output)
|
|
246
258
|
else:
|
|
247
|
-
raise NotImplementedError(f"Unsupported
|
|
259
|
+
raise NotImplementedError(f"Unsupported IO Type {query.target_type}")
|
|
248
260
|
# now return the query we ran through IO
|
|
249
261
|
# TODO: instead return how many rows were written?
|
|
250
262
|
return generate_result_set(
|
|
@@ -307,7 +319,20 @@ class Executor(object):
|
|
|
307
319
|
output.append(compiled_sql)
|
|
308
320
|
return output
|
|
309
321
|
|
|
310
|
-
def parse_file(
|
|
322
|
+
def parse_file(
|
|
323
|
+
self, file: str | Path, persist: bool = False
|
|
324
|
+
) -> list[
|
|
325
|
+
ProcessedQuery
|
|
326
|
+
| ProcessedQueryPersist
|
|
327
|
+
| ProcessedShowStatement
|
|
328
|
+
| ProcessedRawSQLStatement
|
|
329
|
+
| ProcessedCopyStatement,
|
|
330
|
+
]:
|
|
331
|
+
return list(self.parse_file_generator(file, persist=persist))
|
|
332
|
+
|
|
333
|
+
def parse_file_generator(
|
|
334
|
+
self, file: str | Path, persist: bool = False
|
|
335
|
+
) -> Generator[
|
|
311
336
|
ProcessedQuery
|
|
312
337
|
| ProcessedQueryPersist
|
|
313
338
|
| ProcessedShowStatement
|
|
@@ -370,13 +395,50 @@ class Executor(object):
|
|
|
370
395
|
if persist and isinstance(x, ProcessedQueryPersist):
|
|
371
396
|
self.environment.add_datasource(x.datasource)
|
|
372
397
|
|
|
398
|
+
def _hydrate_param(self, param: str) -> Any:
|
|
399
|
+
matched = [
|
|
400
|
+
v
|
|
401
|
+
for v in self.environment.concepts.values()
|
|
402
|
+
if v.safe_address == param or v.address == param
|
|
403
|
+
]
|
|
404
|
+
if not matched:
|
|
405
|
+
raise SyntaxError(f"No concept found for parameter {param}")
|
|
406
|
+
|
|
407
|
+
concept: Concept = matched.pop()
|
|
408
|
+
if not concept.granularity == Granularity.SINGLE_ROW:
|
|
409
|
+
raise SyntaxError(f"Cannot bind non-singleton concept {concept.address}")
|
|
410
|
+
if (
|
|
411
|
+
isinstance(concept.lineage, Function)
|
|
412
|
+
and concept.lineage.operator == FunctionType.CONSTANT
|
|
413
|
+
):
|
|
414
|
+
rval = concept.lineage.arguments[0]
|
|
415
|
+
if isinstance(rval, ListWrapper):
|
|
416
|
+
return [x for x in rval]
|
|
417
|
+
if isinstance(rval, MapWrapper):
|
|
418
|
+
return {k: v for k, v in rval.items()}
|
|
419
|
+
return rval
|
|
420
|
+
else:
|
|
421
|
+
results = self.execute_query(f"select {concept.name} limit 1;").fetchone()
|
|
422
|
+
if not results:
|
|
423
|
+
return None
|
|
424
|
+
return results[0]
|
|
425
|
+
|
|
373
426
|
def execute_raw_sql(
|
|
374
427
|
self, command: str, variables: dict | None = None
|
|
375
428
|
) -> CursorResult:
|
|
376
429
|
"""Run a command against the raw underlying
|
|
377
430
|
execution engine"""
|
|
431
|
+
final_params = None
|
|
432
|
+
q = text(command)
|
|
378
433
|
if variables:
|
|
379
|
-
|
|
434
|
+
final_params = variables
|
|
435
|
+
else:
|
|
436
|
+
params = q.compile().params
|
|
437
|
+
if params:
|
|
438
|
+
final_params = {x: self._hydrate_param(x) for x in params}
|
|
439
|
+
|
|
440
|
+
if final_params:
|
|
441
|
+
return self.connection.execute(text(command), final_params)
|
|
380
442
|
return self.connection.execute(
|
|
381
443
|
text(command),
|
|
382
444
|
)
|
|
@@ -302,6 +302,9 @@ class ParseToObjects(Transformer):
|
|
|
302
302
|
def IDENTIFIER(self, args) -> str:
|
|
303
303
|
return args.value
|
|
304
304
|
|
|
305
|
+
def WILDCARD_IDENTIFIER(self, args) -> str:
|
|
306
|
+
return args.value
|
|
307
|
+
|
|
305
308
|
def QUOTED_IDENTIFIER(self, args) -> str:
|
|
306
309
|
return args.value[1:-1]
|
|
307
310
|
|
|
@@ -786,25 +789,54 @@ class ParseToObjects(Transformer):
|
|
|
786
789
|
return [x for x in args]
|
|
787
790
|
|
|
788
791
|
@v_args(meta=True)
|
|
789
|
-
def
|
|
792
|
+
def merge_statement(self, meta: Meta, args) -> MergeStatementV2:
|
|
790
793
|
modifiers = []
|
|
791
|
-
|
|
794
|
+
cargs: list[str] = []
|
|
795
|
+
source_wildcard = None
|
|
796
|
+
target_wildcard = None
|
|
792
797
|
for arg in args:
|
|
793
798
|
if isinstance(arg, Modifier):
|
|
794
799
|
modifiers.append(arg)
|
|
795
800
|
else:
|
|
796
|
-
|
|
801
|
+
cargs.append(arg)
|
|
802
|
+
source, target = cargs
|
|
803
|
+
if source.endswith(".*"):
|
|
804
|
+
if not target.endswith(".*"):
|
|
805
|
+
raise ValueError("Invalid merge, source is wildcard, target is not")
|
|
806
|
+
source_wildcard = source[:-2]
|
|
807
|
+
target_wildcard = target[:-2]
|
|
808
|
+
sources = [
|
|
809
|
+
v
|
|
810
|
+
for k, v in self.environment.concepts.items()
|
|
811
|
+
if v.namespace == source_wildcard
|
|
812
|
+
]
|
|
813
|
+
targets = {}
|
|
814
|
+
for x in sources:
|
|
815
|
+
target = target_wildcard + "." + x.name
|
|
816
|
+
if target in self.environment.concepts:
|
|
817
|
+
targets[x.address] = self.environment.concepts[target]
|
|
818
|
+
sources = [x for x in sources if x.address in targets]
|
|
819
|
+
else:
|
|
820
|
+
sources = [self.environment.concepts[source]]
|
|
821
|
+
targets = {sources[0].address: self.environment.concepts[target]}
|
|
797
822
|
new = MergeStatementV2(
|
|
798
|
-
|
|
823
|
+
sources=sources,
|
|
824
|
+
targets=targets,
|
|
825
|
+
modifiers=modifiers,
|
|
826
|
+
source_wildcard=source_wildcard,
|
|
827
|
+
target_wildcard=target_wildcard,
|
|
799
828
|
)
|
|
800
|
-
|
|
801
|
-
|
|
829
|
+
for source_c in new.sources:
|
|
830
|
+
self.environment.merge_concept(
|
|
831
|
+
source_c, targets[source_c.address], modifiers
|
|
832
|
+
)
|
|
802
833
|
|
|
803
834
|
return new
|
|
804
835
|
|
|
805
836
|
@v_args(meta=True)
|
|
806
837
|
def rawsql_statement(self, meta: Meta, args) -> RawSQLStatement:
|
|
807
|
-
|
|
838
|
+
statement = RawSQLStatement(meta=Metadata(line_number=meta.line), text=args[0])
|
|
839
|
+
return statement
|
|
808
840
|
|
|
809
841
|
def COPY_TYPE(self, args) -> IOType:
|
|
810
842
|
return IOType(args.value)
|
|
@@ -1744,7 +1776,7 @@ class ParseToObjects(Transformer):
|
|
|
1744
1776
|
arguments=args,
|
|
1745
1777
|
output_datatype=output_datatype,
|
|
1746
1778
|
output_purpose=function_args_to_output_purpose(args),
|
|
1747
|
-
|
|
1779
|
+
valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
1748
1780
|
arg_count=2,
|
|
1749
1781
|
)
|
|
1750
1782
|
|
|
@@ -1757,7 +1789,7 @@ class ParseToObjects(Transformer):
|
|
|
1757
1789
|
arguments=args,
|
|
1758
1790
|
output_datatype=output_datatype,
|
|
1759
1791
|
output_purpose=function_args_to_output_purpose(args),
|
|
1760
|
-
|
|
1792
|
+
valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
1761
1793
|
arg_count=2,
|
|
1762
1794
|
)
|
|
1763
1795
|
|
|
@@ -1770,20 +1802,21 @@ class ParseToObjects(Transformer):
|
|
|
1770
1802
|
arguments=args,
|
|
1771
1803
|
output_datatype=output_datatype,
|
|
1772
1804
|
output_purpose=function_args_to_output_purpose(args),
|
|
1773
|
-
|
|
1805
|
+
valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
1774
1806
|
arg_count=2,
|
|
1775
1807
|
)
|
|
1776
1808
|
|
|
1777
1809
|
@v_args(meta=True)
|
|
1778
1810
|
def fdiv(self, meta: Meta, args):
|
|
1779
1811
|
args = process_function_args(args, meta=meta, environment=self.environment)
|
|
1780
|
-
|
|
1812
|
+
# 2024-11-18 - this is a bit of a hack, but division always returns a float
|
|
1813
|
+
# output_datatype = merge_datatypes([arg_to_datatype(x) for x in args])
|
|
1781
1814
|
return Function(
|
|
1782
1815
|
operator=FunctionType.DIVIDE,
|
|
1783
1816
|
arguments=args,
|
|
1784
|
-
output_datatype=
|
|
1817
|
+
output_datatype=DataType.FLOAT, # division always returns a float
|
|
1785
1818
|
output_purpose=function_args_to_output_purpose(args),
|
|
1786
|
-
|
|
1819
|
+
valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
1787
1820
|
arg_count=2,
|
|
1788
1821
|
)
|
|
1789
1822
|
|
|
@@ -432,7 +432,9 @@ class Renderer:
|
|
|
432
432
|
|
|
433
433
|
@to_string.register
|
|
434
434
|
def _(self, arg: MergeStatementV2):
|
|
435
|
-
|
|
435
|
+
if len(arg.sources) == 1:
|
|
436
|
+
return f"MERGE {self.to_string(arg.sources[0])} into {''.join([self.to_string(modifier) for modifier in arg.modifiers])}{self.to_string(arg.targets[arg.sources[0].address])};"
|
|
437
|
+
return f"MERGE {arg.source_wildcard}.* into {''.join([self.to_string(modifier) for modifier in arg.modifiers])}{arg.target_wildcard}.*;"
|
|
436
438
|
|
|
437
439
|
@to_string.register
|
|
438
440
|
def _(self, arg: Modifier):
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| rowset_derivation_statement
|
|
10
10
|
| import_statement
|
|
11
11
|
| copy_statement
|
|
12
|
-
|
|
|
12
|
+
| merge_statement
|
|
13
13
|
| rawsql_statement
|
|
14
14
|
|
|
15
15
|
_TERMINATOR: ";"i /\s*/
|
|
@@ -74,8 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
align_clause: align_item ("AND"i align_item)* "AND"i?
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
merge_statement_v2: "merge"i IDENTIFIER "into"i SHORTHAND_MODIFIER? IDENTIFIER
|
|
77
|
+
merge_statement: "merge"i WILDCARD_IDENTIFIER "into"i SHORTHAND_MODIFIER? WILDCARD_IDENTIFIER
|
|
79
78
|
|
|
80
79
|
// raw sql statement
|
|
81
80
|
rawsql_statement: "raw_sql"i "(" MULTILINE_STRING ")"
|
|
@@ -292,6 +291,7 @@
|
|
|
292
291
|
// base language constructs
|
|
293
292
|
concept_lit: IDENTIFIER
|
|
294
293
|
IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.]*/
|
|
294
|
+
WILDCARD_IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.\*]*/
|
|
295
295
|
QUOTED_IDENTIFIER: /`[a-zA-Z\_][a-zA-Z0-9\_\.\-\*\:\s]*`/
|
|
296
296
|
QUOTED_ADDRESS: /`[a-zA-Z\_][a-zA-Z0-9\_\.\-\*\:]*`/
|
|
297
297
|
ADDRESS: IDENTIFIER
|
|
@@ -302,7 +302,7 @@
|
|
|
302
302
|
SINGLE_STRING_CHARS: /(?:(?!\${)([^'\\]|\\.))+/+ // any character except '
|
|
303
303
|
_single_quote: "'" ( SINGLE_STRING_CHARS )* "'"
|
|
304
304
|
_double_quote: "\"" ( DOUBLE_STRING_CHARS )* "\""
|
|
305
|
-
string_lit: _single_quote | _double_quote
|
|
305
|
+
string_lit: _single_quote | _double_quote | MULTILINE_STRING
|
|
306
306
|
|
|
307
307
|
MINUS: "-"
|
|
308
308
|
|
|
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
|
|
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.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/__init__.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/basic_node.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/group_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/group_to_node.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/node_merge_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/rowset_node.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/select_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/trilogy/core/processing/node_generators/unnest_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.36 → pytrilogy-0.0.2.38}/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
|