pytrilogy 0.0.2.22__tar.gz → 0.0.2.23__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.22/pytrilogy.egg-info → pytrilogy-0.0.2.23}/PKG-INFO +1 -1
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23/pytrilogy.egg-info}/PKG-INFO +1 -1
- pytrilogy-0.0.2.23/tests/test_imports.py +42 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_models.py +9 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_parsing.py +31 -13
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_show.py +0 -1
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_statements.py +21 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/enums.py +10 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/functions.py +2 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/models.py +63 -2
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/utility.py +2 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/query_processor.py +20 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/base.py +16 -2
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/executor.py +41 -36
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parsing/parse_engine.py +42 -11
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parsing/render.py +23 -3
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parsing/trilogy.lark +11 -3
- pytrilogy-0.0.2.22/tests/test_imports.py +0 -23
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/README.md +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/pyproject.toml +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/setup.cfg +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/setup.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_select.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/concept_strategies_v3.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parsing/common.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/utility.py +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from trilogy import Environment
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_multi_environment():
|
|
6
|
+
basic = Environment()
|
|
7
|
+
|
|
8
|
+
basic.parse(
|
|
9
|
+
"""
|
|
10
|
+
const pi <- 3.14;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
""",
|
|
14
|
+
namespace="math",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
basic.parse(
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
select math.pi;
|
|
21
|
+
"""
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
assert basic.concepts["math.pi"].name == "pi"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_test_alias_free_import():
|
|
28
|
+
basic = Environment(working_path=Path(__file__).parent)
|
|
29
|
+
|
|
30
|
+
basic.parse(
|
|
31
|
+
"""
|
|
32
|
+
import test_env;
|
|
33
|
+
|
|
34
|
+
key id2 int;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
""",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
assert basic.concepts["id"].name == "id"
|
|
41
|
+
assert basic.concepts["id2"].name == "id2"
|
|
42
|
+
assert basic.concepts["id"].namespace == basic.concepts["id2"].namespace
|
|
@@ -15,8 +15,11 @@ from trilogy.core.models import (
|
|
|
15
15
|
Concept,
|
|
16
16
|
AggregateWrapper,
|
|
17
17
|
RowsetItem,
|
|
18
|
+
TupleWrapper,
|
|
19
|
+
DataType,
|
|
18
20
|
)
|
|
19
21
|
from trilogy import parse
|
|
22
|
+
from copy import deepcopy
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
def test_cte_merge(test_environment, test_environment_graph):
|
|
@@ -312,3 +315,9 @@ select avg_greater_ten;
|
|
|
312
315
|
lineage = env.concepts["avg_greater_ten"].lineage
|
|
313
316
|
assert isinstance(lineage, AggregateWrapper)
|
|
314
317
|
assert isinstance(lineage.function.concept_arguments[0].lineage, RowsetItem)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_tuple_clone():
|
|
321
|
+
x = TupleWrapper([1, 2, 3], type=DataType.INTEGER)
|
|
322
|
+
y = deepcopy(x)
|
|
323
|
+
assert y == x
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from trilogy.core.enums import Purpose, ComparisonOperator
|
|
2
2
|
from trilogy.core.models import (
|
|
3
3
|
DataType,
|
|
4
|
-
Parenthetical,
|
|
5
4
|
ProcessedQuery,
|
|
6
5
|
ShowStatement,
|
|
7
6
|
SelectStatement,
|
|
8
7
|
Environment,
|
|
9
8
|
Comparison,
|
|
9
|
+
TupleWrapper,
|
|
10
10
|
)
|
|
11
11
|
from trilogy.core.functions import argument_to_purpose, function_args_to_output_purpose
|
|
12
12
|
from trilogy.parsing.parse_engine import (
|
|
@@ -27,11 +27,11 @@ def test_in():
|
|
|
27
27
|
right = query.where_clause.conditional.right
|
|
28
28
|
assert isinstance(
|
|
29
29
|
right,
|
|
30
|
-
|
|
30
|
+
TupleWrapper,
|
|
31
31
|
), type(right)
|
|
32
|
-
assert right
|
|
32
|
+
assert right[0] == 1
|
|
33
33
|
rendered = BaseDialect().render_expr(right)
|
|
34
|
-
assert rendered.strip() == "(
|
|
34
|
+
assert rendered.strip() == "(1,2,3)".strip()
|
|
35
35
|
|
|
36
36
|
_, parsed = parse_text(
|
|
37
37
|
"const order_id <- 3; SELECT order_id WHERE order_id IN (1,);"
|
|
@@ -40,11 +40,11 @@ def test_in():
|
|
|
40
40
|
right = query.where_clause.conditional.right
|
|
41
41
|
assert isinstance(
|
|
42
42
|
right,
|
|
43
|
-
|
|
43
|
+
TupleWrapper,
|
|
44
44
|
), type(right)
|
|
45
|
-
assert right
|
|
45
|
+
assert right[0] == 1
|
|
46
46
|
rendered = BaseDialect().render_expr(right)
|
|
47
|
-
assert rendered.strip() == "(
|
|
47
|
+
assert rendered.strip() == "(1)".strip()
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def test_not_in():
|
|
@@ -53,13 +53,10 @@ def test_not_in():
|
|
|
53
53
|
)
|
|
54
54
|
query: ProcessedQuery = parsed[-1]
|
|
55
55
|
right = query.where_clause.conditional.right
|
|
56
|
-
assert isinstance(
|
|
57
|
-
|
|
58
|
-
Parenthetical,
|
|
59
|
-
), type(right)
|
|
60
|
-
assert right.content[0] == 1
|
|
56
|
+
assert isinstance(right, TupleWrapper), type(right)
|
|
57
|
+
assert right[0] == 1
|
|
61
58
|
rendered = BaseDialect().render_expr(right)
|
|
62
|
-
assert rendered.strip() == "(
|
|
59
|
+
assert rendered.strip() == "(1,2,3)".strip()
|
|
63
60
|
|
|
64
61
|
|
|
65
62
|
def test_is_not_null():
|
|
@@ -516,3 +513,24 @@ select filtered_test;
|
|
|
516
513
|
results = Dialects.DUCK_DB.default_executor().generate_sql(text)[0]
|
|
517
514
|
|
|
518
515
|
assert "filtered_test" in results, results
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def test_unnest_parsing():
|
|
519
|
+
x = """
|
|
520
|
+
key scalar int;
|
|
521
|
+
property scalar.int_array list<int>;
|
|
522
|
+
|
|
523
|
+
key split <- unnest(int_array);
|
|
524
|
+
|
|
525
|
+
datasource avalues (
|
|
526
|
+
int_array: int_array,
|
|
527
|
+
scalar: scalar
|
|
528
|
+
)
|
|
529
|
+
grain (scalar)
|
|
530
|
+
query '''(
|
|
531
|
+
select [1,2,3,4] as int_array, 2 as scalar
|
|
532
|
+
)''';
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
env, parsed = parse_text(x)
|
|
536
|
+
assert env.concepts["split"].datatype == DataType.INTEGER
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
from trilogy.parser import parse
|
|
2
|
+
from trilogy import Dialects
|
|
3
|
+
from trilogy.core.models import ProcessedCopyStatement
|
|
4
|
+
from pathlib import Path
|
|
2
5
|
|
|
3
6
|
# from trilogy.compiler import compile
|
|
4
7
|
|
|
@@ -43,6 +46,24 @@ datasource posts (
|
|
|
43
46
|
parse(text)
|
|
44
47
|
|
|
45
48
|
|
|
49
|
+
def test_io_statement():
|
|
50
|
+
target = Path(__file__).parent / "test_io_statement.csv"
|
|
51
|
+
if target.exists():
|
|
52
|
+
target.unlink()
|
|
53
|
+
text = f"""const array <- [1,2,3,4];
|
|
54
|
+
|
|
55
|
+
auto x <- unnest(array);
|
|
56
|
+
|
|
57
|
+
copy into csv '{target}' from select x -> test;
|
|
58
|
+
"""
|
|
59
|
+
exec = Dialects.DUCK_DB.default_executor()
|
|
60
|
+
results = exec.parse_text(text)
|
|
61
|
+
assert isinstance(results[-1], ProcessedCopyStatement)
|
|
62
|
+
for z in results:
|
|
63
|
+
exec.execute_query(z)
|
|
64
|
+
assert target.exists(), "csv file was not created"
|
|
65
|
+
|
|
66
|
+
|
|
46
67
|
def test_datasource_where():
|
|
47
68
|
text = """key user_id int metadata(description="the description");
|
|
48
69
|
property user_id.display_name string metadata(description="The display name ");
|
|
@@ -292,3 +292,13 @@ class SelectFiltering(Enum):
|
|
|
292
292
|
NONE = "none"
|
|
293
293
|
EXPLICIT = "explicit" # the filtering contains only selected values
|
|
294
294
|
IMPLICIT = "implicit" # the filtering contains unselected values
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class IOType(Enum):
|
|
298
|
+
CSV = "csv"
|
|
299
|
+
|
|
300
|
+
@classmethod
|
|
301
|
+
def _missing_(cls, value):
|
|
302
|
+
if isinstance(value, str) and value.lower() != value:
|
|
303
|
+
return IOType(value.lower())
|
|
304
|
+
return super()._missing_(value)
|
|
@@ -104,6 +104,8 @@ def Unnest(args: list[Concept]) -> Function:
|
|
|
104
104
|
output = arg_to_datatype(args[0])
|
|
105
105
|
if isinstance(output, (ListType)):
|
|
106
106
|
output = output.value_data_type
|
|
107
|
+
else:
|
|
108
|
+
output = DataType.STRING
|
|
107
109
|
return Function(
|
|
108
110
|
operator=FunctionType.UNNEST,
|
|
109
111
|
arguments=args,
|
|
@@ -65,6 +65,7 @@ from trilogy.core.enums import (
|
|
|
65
65
|
ShowCategory,
|
|
66
66
|
Granularity,
|
|
67
67
|
SelectFiltering,
|
|
68
|
+
IOType,
|
|
68
69
|
)
|
|
69
70
|
from trilogy.core.exceptions import UndefinedConceptException, InvalidSyntaxException
|
|
70
71
|
from trilogy.utility import unique
|
|
@@ -81,6 +82,9 @@ LT = TypeVar("LT")
|
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
def is_compatible_datatype(left, right):
|
|
85
|
+
# for unknown types, we can't make any assumptions
|
|
86
|
+
if right == DataType.UNKNOWN or left == DataType.UNKNOWN:
|
|
87
|
+
return True
|
|
84
88
|
if left == right:
|
|
85
89
|
return True
|
|
86
90
|
if {left, right} == {DataType.NUMERIC, DataType.FLOAT}:
|
|
@@ -1580,6 +1584,13 @@ class RawSQLStatement(BaseModel):
|
|
|
1580
1584
|
meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
|
|
1581
1585
|
|
|
1582
1586
|
|
|
1587
|
+
class CopyStatement(BaseModel):
|
|
1588
|
+
target: str
|
|
1589
|
+
target_type: IOType
|
|
1590
|
+
meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
|
|
1591
|
+
select: SelectStatement
|
|
1592
|
+
|
|
1593
|
+
|
|
1583
1594
|
class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
|
|
1584
1595
|
selection: List[SelectItem]
|
|
1585
1596
|
order_by: Optional[OrderBy] = None
|
|
@@ -3599,6 +3610,7 @@ class Comparison(
|
|
|
3599
3610
|
MagicConstants,
|
|
3600
3611
|
WindowItem,
|
|
3601
3612
|
AggregateWrapper,
|
|
3613
|
+
TupleWrapper,
|
|
3602
3614
|
]
|
|
3603
3615
|
operator: ComparisonOperator
|
|
3604
3616
|
|
|
@@ -4258,13 +4270,23 @@ class ProcessedQuery(BaseModel):
|
|
|
4258
4270
|
order_by: Optional[OrderBy] = None
|
|
4259
4271
|
|
|
4260
4272
|
|
|
4261
|
-
class
|
|
4273
|
+
class PersistQueryMixin(BaseModel):
|
|
4262
4274
|
output_to: MaterializedDataset
|
|
4263
4275
|
datasource: Datasource
|
|
4264
4276
|
# base:Dataset
|
|
4265
4277
|
|
|
4266
4278
|
|
|
4267
|
-
class ProcessedQueryPersist(ProcessedQuery,
|
|
4279
|
+
class ProcessedQueryPersist(ProcessedQuery, PersistQueryMixin):
|
|
4280
|
+
pass
|
|
4281
|
+
|
|
4282
|
+
|
|
4283
|
+
class CopyQueryMixin(BaseModel):
|
|
4284
|
+
target: str
|
|
4285
|
+
target_type: IOType
|
|
4286
|
+
# base:Dataset
|
|
4287
|
+
|
|
4288
|
+
|
|
4289
|
+
class ProcessedCopyStatement(ProcessedQuery, CopyQueryMixin):
|
|
4268
4290
|
pass
|
|
4269
4291
|
|
|
4270
4292
|
|
|
@@ -4523,6 +4545,37 @@ class Parenthetical(
|
|
|
4523
4545
|
return base
|
|
4524
4546
|
|
|
4525
4547
|
|
|
4548
|
+
class TupleWrapper(Generic[VT], tuple):
|
|
4549
|
+
"""Used to distinguish parsed tuple objects from other tuples"""
|
|
4550
|
+
|
|
4551
|
+
def __init__(self, val, type: DataType, **kwargs):
|
|
4552
|
+
super().__init__()
|
|
4553
|
+
self.type = type
|
|
4554
|
+
self.val = val
|
|
4555
|
+
|
|
4556
|
+
def __getnewargs__(self):
|
|
4557
|
+
return (self.val, self.type)
|
|
4558
|
+
|
|
4559
|
+
def __new__(cls, val, type: DataType, **kwargs):
|
|
4560
|
+
return super().__new__(cls, tuple(val))
|
|
4561
|
+
# self.type = type
|
|
4562
|
+
|
|
4563
|
+
@classmethod
|
|
4564
|
+
def __get_pydantic_core_schema__(
|
|
4565
|
+
cls, source_type: Any, handler: Callable[[Any], core_schema.CoreSchema]
|
|
4566
|
+
) -> core_schema.CoreSchema:
|
|
4567
|
+
args = get_args(source_type)
|
|
4568
|
+
if args:
|
|
4569
|
+
schema = handler(Tuple[args]) # type: ignore
|
|
4570
|
+
else:
|
|
4571
|
+
schema = handler(Tuple)
|
|
4572
|
+
return core_schema.no_info_after_validator_function(cls.validate, schema)
|
|
4573
|
+
|
|
4574
|
+
@classmethod
|
|
4575
|
+
def validate(cls, v):
|
|
4576
|
+
return cls(v, type=arg_to_datatype(v[0]))
|
|
4577
|
+
|
|
4578
|
+
|
|
4526
4579
|
class PersistStatement(BaseModel):
|
|
4527
4580
|
datasource: Datasource
|
|
4528
4581
|
select: SelectStatement
|
|
@@ -4589,6 +4642,12 @@ def list_to_wrapper(args):
|
|
|
4589
4642
|
return ListWrapper(args, type=types[0])
|
|
4590
4643
|
|
|
4591
4644
|
|
|
4645
|
+
def tuple_to_wrapper(args):
|
|
4646
|
+
types = [arg_to_datatype(arg) for arg in args]
|
|
4647
|
+
assert len(set(types)) == 1
|
|
4648
|
+
return TupleWrapper(args, type=types[0])
|
|
4649
|
+
|
|
4650
|
+
|
|
4592
4651
|
def dict_to_map_wrapper(arg):
|
|
4593
4652
|
key_types = [arg_to_datatype(arg) for arg in arg.keys()]
|
|
4594
4653
|
|
|
@@ -4644,6 +4703,8 @@ def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType | Numeric
|
|
|
4644
4703
|
return arg.function.output_datatype
|
|
4645
4704
|
elif isinstance(arg, Parenthetical):
|
|
4646
4705
|
return arg_to_datatype(arg.content)
|
|
4706
|
+
elif isinstance(arg, TupleWrapper):
|
|
4707
|
+
return ListType(type=arg.type)
|
|
4647
4708
|
elif isinstance(arg, WindowItem):
|
|
4648
4709
|
if arg.type in (WindowType.RANK, WindowType.ROW_NUMBER):
|
|
4649
4710
|
return DataType.INTEGER
|
|
@@ -28,6 +28,7 @@ from trilogy.core.models import (
|
|
|
28
28
|
DatePart,
|
|
29
29
|
NumericType,
|
|
30
30
|
ListType,
|
|
31
|
+
TupleWrapper,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
from trilogy.core.enums import Purpose, Granularity, BooleanOperator, Modifier
|
|
@@ -422,6 +423,7 @@ def is_scalar_condition(
|
|
|
422
423
|
| NumericType
|
|
423
424
|
| DatePart
|
|
424
425
|
| ListWrapper[Any]
|
|
426
|
+
| TupleWrapper[Any]
|
|
425
427
|
),
|
|
426
428
|
materialized: set[str] | None = None,
|
|
427
429
|
) -> bool:
|
|
@@ -26,6 +26,8 @@ from trilogy.core.models import (
|
|
|
26
26
|
BaseJoin,
|
|
27
27
|
InstantiatedUnnestJoin,
|
|
28
28
|
Conditional,
|
|
29
|
+
ProcessedCopyStatement,
|
|
30
|
+
CopyStatement,
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
from trilogy.utility import unique
|
|
@@ -418,6 +420,24 @@ def process_persist(
|
|
|
418
420
|
)
|
|
419
421
|
|
|
420
422
|
|
|
423
|
+
def process_copy(
|
|
424
|
+
environment: Environment,
|
|
425
|
+
statement: CopyStatement,
|
|
426
|
+
hooks: List[BaseHook] | None = None,
|
|
427
|
+
) -> ProcessedCopyStatement:
|
|
428
|
+
select = process_query(
|
|
429
|
+
environment=environment, statement=statement.select, hooks=hooks
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# build our object to return
|
|
433
|
+
arg_dict = {k: v for k, v in select.__dict__.items()}
|
|
434
|
+
return ProcessedCopyStatement(
|
|
435
|
+
**arg_dict,
|
|
436
|
+
target=statement.target,
|
|
437
|
+
target_type=statement.target_type,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
421
441
|
def process_query(
|
|
422
442
|
environment: Environment,
|
|
423
443
|
statement: SelectStatement | MultiSelectStatement,
|
|
@@ -35,6 +35,7 @@ from trilogy.core.models import (
|
|
|
35
35
|
Environment,
|
|
36
36
|
RawColumnExpr,
|
|
37
37
|
ListWrapper,
|
|
38
|
+
TupleWrapper,
|
|
38
39
|
MapWrapper,
|
|
39
40
|
ShowStatement,
|
|
40
41
|
RowsetItem,
|
|
@@ -49,8 +50,10 @@ from trilogy.core.models import (
|
|
|
49
50
|
StructType,
|
|
50
51
|
MergeStatementV2,
|
|
51
52
|
Datasource,
|
|
53
|
+
CopyStatement,
|
|
54
|
+
ProcessedCopyStatement,
|
|
52
55
|
)
|
|
53
|
-
from trilogy.core.query_processor import process_query, process_persist
|
|
56
|
+
from trilogy.core.query_processor import process_query, process_persist, process_copy
|
|
54
57
|
from trilogy.dialect.common import render_join, render_unnest
|
|
55
58
|
from trilogy.hooks.base_hook import BaseHook
|
|
56
59
|
from trilogy.core.enums import UnnestMode
|
|
@@ -391,6 +394,7 @@ class BaseDialect:
|
|
|
391
394
|
StructType,
|
|
392
395
|
ListType,
|
|
393
396
|
ListWrapper[Any],
|
|
397
|
+
TupleWrapper[Any],
|
|
394
398
|
DatePart,
|
|
395
399
|
CaseWhen,
|
|
396
400
|
CaseElse,
|
|
@@ -430,7 +434,7 @@ class BaseDialect:
|
|
|
430
434
|
f"Missing source CTE for {e.right.address}"
|
|
431
435
|
)
|
|
432
436
|
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} (select {target}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} from {target} where {target}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} is not null)"
|
|
433
|
-
elif isinstance(e.right, (ListWrapper, Parenthetical, list)):
|
|
437
|
+
elif isinstance(e.right, (ListWrapper, TupleWrapper, Parenthetical, list)):
|
|
434
438
|
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}"
|
|
435
439
|
|
|
436
440
|
elif isinstance(
|
|
@@ -511,6 +515,8 @@ class BaseDialect:
|
|
|
511
515
|
return str(e)
|
|
512
516
|
elif isinstance(e, ListWrapper):
|
|
513
517
|
return f"[{','.join([self.render_expr(x, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid) for x in e])}]"
|
|
518
|
+
elif isinstance(e, TupleWrapper):
|
|
519
|
+
return f"({','.join([self.render_expr(x, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid) for x in e])})"
|
|
514
520
|
elif isinstance(e, MapWrapper):
|
|
515
521
|
return f"MAP {{{','.join([f'{self.render_expr(k, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}:{self.render_expr(v, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}' for k, v in e.items()])}}}"
|
|
516
522
|
elif isinstance(e, list):
|
|
@@ -662,6 +668,7 @@ class BaseDialect:
|
|
|
662
668
|
| ImportStatement
|
|
663
669
|
| RawSQLStatement
|
|
664
670
|
| MergeStatementV2
|
|
671
|
+
| CopyStatement
|
|
665
672
|
],
|
|
666
673
|
hooks: Optional[List[BaseHook]] = None,
|
|
667
674
|
) -> List[
|
|
@@ -675,6 +682,7 @@ class BaseDialect:
|
|
|
675
682
|
| ProcessedQueryPersist
|
|
676
683
|
| ProcessedShowStatement
|
|
677
684
|
| ProcessedRawSQLStatement
|
|
685
|
+
| ProcessedCopyStatement
|
|
678
686
|
] = []
|
|
679
687
|
for statement in statements:
|
|
680
688
|
if isinstance(statement, PersistStatement):
|
|
@@ -683,6 +691,12 @@ class BaseDialect:
|
|
|
683
691
|
hook.process_persist_info(statement)
|
|
684
692
|
persist = process_persist(environment, statement, hooks=hooks)
|
|
685
693
|
output.append(persist)
|
|
694
|
+
elif isinstance(statement, CopyStatement):
|
|
695
|
+
if hooks:
|
|
696
|
+
for hook in hooks:
|
|
697
|
+
hook.process_select_info(statement.select)
|
|
698
|
+
copy = process_copy(environment, statement, hooks=hooks)
|
|
699
|
+
output.append(copy)
|
|
686
700
|
elif isinstance(statement, SelectStatement):
|
|
687
701
|
if hooks:
|
|
688
702
|
for hook in hooks:
|
|
@@ -10,6 +10,7 @@ from trilogy.core.models import (
|
|
|
10
10
|
ProcessedShowStatement,
|
|
11
11
|
ProcessedQueryPersist,
|
|
12
12
|
ProcessedRawSQLStatement,
|
|
13
|
+
ProcessedCopyStatement,
|
|
13
14
|
RawSQLStatement,
|
|
14
15
|
MultiSelectStatement,
|
|
15
16
|
SelectStatement,
|
|
@@ -18,9 +19,11 @@ from trilogy.core.models import (
|
|
|
18
19
|
Concept,
|
|
19
20
|
ConceptDeclarationStatement,
|
|
20
21
|
Datasource,
|
|
22
|
+
CopyStatement,
|
|
21
23
|
)
|
|
22
24
|
from trilogy.dialect.base import BaseDialect
|
|
23
25
|
from trilogy.dialect.enums import Dialects
|
|
26
|
+
from trilogy.core.enums import IOType
|
|
24
27
|
from trilogy.parser import parse_text
|
|
25
28
|
from trilogy.hooks.base_hook import BaseHook
|
|
26
29
|
from pathlib import Path
|
|
@@ -94,7 +97,15 @@ class Executor(object):
|
|
|
94
97
|
self.connection = self.engine.connect()
|
|
95
98
|
|
|
96
99
|
def execute_statement(self, statement) -> Optional[CursorResult]:
|
|
97
|
-
if not isinstance(
|
|
100
|
+
if not isinstance(
|
|
101
|
+
statement,
|
|
102
|
+
(
|
|
103
|
+
ProcessedQuery,
|
|
104
|
+
ProcessedShowStatement,
|
|
105
|
+
ProcessedQueryPersist,
|
|
106
|
+
ProcessedCopyStatement,
|
|
107
|
+
),
|
|
108
|
+
):
|
|
98
109
|
return None
|
|
99
110
|
return self.execute_query(statement)
|
|
100
111
|
|
|
@@ -183,12 +194,33 @@ class Executor(object):
|
|
|
183
194
|
|
|
184
195
|
@execute_query.register
|
|
185
196
|
def _(self, query: ProcessedQueryPersist) -> CursorResult:
|
|
197
|
+
|
|
186
198
|
sql = self.generator.compile_statement(query)
|
|
187
|
-
|
|
199
|
+
|
|
188
200
|
output = self.connection.execute(text(sql))
|
|
189
201
|
self.environment.add_datasource(query.datasource)
|
|
190
202
|
return output
|
|
191
203
|
|
|
204
|
+
@execute_query.register
|
|
205
|
+
def _(self, query: ProcessedCopyStatement) -> CursorResult:
|
|
206
|
+
sql = self.generator.compile_statement(query)
|
|
207
|
+
output: CursorResult = self.connection.execute(text(sql))
|
|
208
|
+
if query.target_type == IOType.CSV:
|
|
209
|
+
import csv
|
|
210
|
+
|
|
211
|
+
with open(query.target, "w", newline="", encoding="utf-8") as f:
|
|
212
|
+
outcsv = csv.writer(f)
|
|
213
|
+
outcsv.writerow(output.keys())
|
|
214
|
+
outcsv.writerows(output)
|
|
215
|
+
else:
|
|
216
|
+
raise NotImplementedError(f"Unsupported IOType {query.target_type}")
|
|
217
|
+
# now return the query we ran through IO
|
|
218
|
+
# TODO: instead return how many rows were written?
|
|
219
|
+
return generate_result_set(
|
|
220
|
+
query.output_columns,
|
|
221
|
+
[self.generator.compile_statement(query)],
|
|
222
|
+
)
|
|
223
|
+
|
|
192
224
|
@singledispatchmethod
|
|
193
225
|
def generate_sql(self, command) -> list[str]:
|
|
194
226
|
raise NotImplementedError(
|
|
@@ -251,39 +283,17 @@ class Executor(object):
|
|
|
251
283
|
| ProcessedQueryPersist
|
|
252
284
|
| ProcessedShowStatement
|
|
253
285
|
| ProcessedRawSQLStatement
|
|
286
|
+
| ProcessedCopyStatement
|
|
254
287
|
]:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
generatable = [
|
|
258
|
-
x
|
|
259
|
-
for x in parsed
|
|
260
|
-
if isinstance(
|
|
261
|
-
x,
|
|
262
|
-
(
|
|
263
|
-
SelectStatement,
|
|
264
|
-
PersistStatement,
|
|
265
|
-
MultiSelectStatement,
|
|
266
|
-
ShowStatement,
|
|
267
|
-
RawSQLStatement,
|
|
268
|
-
),
|
|
269
|
-
)
|
|
270
|
-
]
|
|
271
|
-
sql = []
|
|
272
|
-
while generatable:
|
|
273
|
-
t = generatable.pop(0)
|
|
274
|
-
x = self.generator.generate_queries(
|
|
275
|
-
self.environment, [t], hooks=self.hooks
|
|
276
|
-
)[0]
|
|
277
|
-
if persist and isinstance(x, ProcessedQueryPersist):
|
|
278
|
-
self.environment.add_datasource(x.datasource)
|
|
279
|
-
sql.append(x)
|
|
280
|
-
return sql
|
|
288
|
+
|
|
289
|
+
return list(self.parse_text_generator(command, persist=persist))
|
|
281
290
|
|
|
282
291
|
def parse_text_generator(self, command: str, persist: bool = False) -> Generator[
|
|
283
292
|
ProcessedQuery
|
|
284
293
|
| ProcessedQueryPersist
|
|
285
294
|
| ProcessedShowStatement
|
|
286
|
-
| ProcessedRawSQLStatement
|
|
295
|
+
| ProcessedRawSQLStatement
|
|
296
|
+
| ProcessedCopyStatement,
|
|
287
297
|
None,
|
|
288
298
|
None,
|
|
289
299
|
]:
|
|
@@ -300,6 +310,7 @@ class Executor(object):
|
|
|
300
310
|
MultiSelectStatement,
|
|
301
311
|
ShowStatement,
|
|
302
312
|
RawSQLStatement,
|
|
313
|
+
CopyStatement,
|
|
303
314
|
),
|
|
304
315
|
)
|
|
305
316
|
]
|
|
@@ -340,13 +351,7 @@ class Executor(object):
|
|
|
340
351
|
)
|
|
341
352
|
)
|
|
342
353
|
continue
|
|
343
|
-
|
|
344
|
-
logger.debug(compiled_sql)
|
|
345
|
-
|
|
346
|
-
output.append(self.connection.execute(text(compiled_sql)))
|
|
347
|
-
# generalize post-run success hooks
|
|
348
|
-
if isinstance(statement, ProcessedQueryPersist):
|
|
349
|
-
self.environment.add_datasource(statement.datasource)
|
|
354
|
+
output.append(self.execute_query(statement))
|
|
350
355
|
return output
|
|
351
356
|
|
|
352
357
|
def execute_file(self, file: str | Path) -> List[CursorResult]:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from os.path import dirname, join
|
|
2
2
|
from typing import List, Optional, Tuple, Union
|
|
3
3
|
from re import IGNORECASE
|
|
4
|
-
from lark import Lark, Transformer, v_args
|
|
4
|
+
from lark import Lark, Transformer, v_args, Tree
|
|
5
5
|
from lark.exceptions import (
|
|
6
6
|
UnexpectedCharacters,
|
|
7
7
|
UnexpectedEOF,
|
|
@@ -31,6 +31,7 @@ from trilogy.core.enums import (
|
|
|
31
31
|
DatePart,
|
|
32
32
|
ShowCategory,
|
|
33
33
|
FunctionClass,
|
|
34
|
+
IOType,
|
|
34
35
|
)
|
|
35
36
|
from trilogy.core.exceptions import InvalidSyntaxException, UndefinedConceptException
|
|
36
37
|
from trilogy.core.functions import (
|
|
@@ -84,6 +85,7 @@ from trilogy.core.models import (
|
|
|
84
85
|
PersistStatement,
|
|
85
86
|
Query,
|
|
86
87
|
RawSQLStatement,
|
|
88
|
+
CopyStatement,
|
|
87
89
|
SelectStatement,
|
|
88
90
|
SelectItem,
|
|
89
91
|
WhereClause,
|
|
@@ -105,9 +107,11 @@ from trilogy.core.models import (
|
|
|
105
107
|
ConceptDerivation,
|
|
106
108
|
RowsetDerivationStatement,
|
|
107
109
|
list_to_wrapper,
|
|
110
|
+
tuple_to_wrapper,
|
|
108
111
|
dict_to_map_wrapper,
|
|
109
112
|
NumericType,
|
|
110
113
|
HavingClause,
|
|
114
|
+
TupleWrapper,
|
|
111
115
|
)
|
|
112
116
|
from trilogy.parsing.exceptions import ParseError
|
|
113
117
|
from trilogy.parsing.common import (
|
|
@@ -748,13 +752,29 @@ class ParseToObjects(Transformer):
|
|
|
748
752
|
def rawsql_statement(self, meta: Meta, args) -> RawSQLStatement:
|
|
749
753
|
return RawSQLStatement(meta=Metadata(line_number=meta.line), text=args[0])
|
|
750
754
|
|
|
755
|
+
def COPY_TYPE(self, args) -> IOType:
|
|
756
|
+
return IOType(args.value)
|
|
757
|
+
|
|
758
|
+
@v_args(meta=True)
|
|
759
|
+
def copy_statement(self, meta: Meta, args) -> CopyStatement:
|
|
760
|
+
|
|
761
|
+
return CopyStatement(
|
|
762
|
+
target=args[1],
|
|
763
|
+
target_type=args[0],
|
|
764
|
+
meta=Metadata(line_number=meta.line),
|
|
765
|
+
select=args[-1],
|
|
766
|
+
)
|
|
767
|
+
|
|
751
768
|
def resolve_import_address(self, address) -> str:
|
|
752
769
|
with open(address, "r", encoding="utf-8") as f:
|
|
753
770
|
text = f.read()
|
|
754
771
|
return text
|
|
755
772
|
|
|
756
773
|
def import_statement(self, args: list[str]) -> ImportStatement:
|
|
757
|
-
|
|
774
|
+
if len(args) == 2:
|
|
775
|
+
alias = args[-1]
|
|
776
|
+
else:
|
|
777
|
+
alias = self.environment.namespace
|
|
758
778
|
path = args[0].split(".")
|
|
759
779
|
|
|
760
780
|
target = join(self.environment.working_path, *path) + ".preql"
|
|
@@ -1064,6 +1084,9 @@ class ParseToObjects(Transformer):
|
|
|
1064
1084
|
def array_lit(self, args):
|
|
1065
1085
|
return list_to_wrapper(args)
|
|
1066
1086
|
|
|
1087
|
+
def tuple_lit(self, args):
|
|
1088
|
+
return tuple_to_wrapper(args)
|
|
1089
|
+
|
|
1067
1090
|
def struct_lit(self, args):
|
|
1068
1091
|
|
|
1069
1092
|
zipped = dict(zip(args[::2], args[1::2]))
|
|
@@ -1124,12 +1147,18 @@ class ParseToObjects(Transformer):
|
|
|
1124
1147
|
|
|
1125
1148
|
while isinstance(right, Parenthetical) and isinstance(
|
|
1126
1149
|
right.content,
|
|
1127
|
-
(
|
|
1150
|
+
(
|
|
1151
|
+
Concept,
|
|
1152
|
+
Function,
|
|
1153
|
+
FilterItem,
|
|
1154
|
+
WindowItem,
|
|
1155
|
+
AggregateWrapper,
|
|
1156
|
+
ListWrapper,
|
|
1157
|
+
TupleWrapper,
|
|
1158
|
+
),
|
|
1128
1159
|
):
|
|
1129
1160
|
right = right.content
|
|
1130
|
-
if isinstance(
|
|
1131
|
-
right, (Function, FilterItem, WindowItem, AggregateWrapper, ListWrapper)
|
|
1132
|
-
):
|
|
1161
|
+
if isinstance(right, (Function, FilterItem, WindowItem, AggregateWrapper)):
|
|
1133
1162
|
right = arbitrary_to_concept(
|
|
1134
1163
|
right,
|
|
1135
1164
|
namespace=self.environment.namespace,
|
|
@@ -1142,7 +1171,7 @@ class ParseToObjects(Transformer):
|
|
|
1142
1171
|
)
|
|
1143
1172
|
|
|
1144
1173
|
def expr_tuple(self, args):
|
|
1145
|
-
return
|
|
1174
|
+
return TupleWrapper(content=tuple(args))
|
|
1146
1175
|
|
|
1147
1176
|
def parenthetical(self, args):
|
|
1148
1177
|
return Parenthetical(content=args[0])
|
|
@@ -1840,10 +1869,12 @@ def unpack_visit_error(e: VisitError):
|
|
|
1840
1869
|
unpack_visit_error(e.orig_exc)
|
|
1841
1870
|
elif isinstance(e.orig_exc, (UndefinedConceptException, ImportError)):
|
|
1842
1871
|
raise e.orig_exc
|
|
1843
|
-
elif isinstance(e.orig_exc, SyntaxError):
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1872
|
+
elif isinstance(e.orig_exc, (SyntaxError, TypeError)):
|
|
1873
|
+
if isinstance(e.obj, Tree):
|
|
1874
|
+
raise InvalidSyntaxException(
|
|
1875
|
+
str(e.orig_exc) + " in " + str(e.rule) + f" Line: {e.obj.meta.line}"
|
|
1876
|
+
)
|
|
1877
|
+
raise InvalidSyntaxException(str(e.orig_exc))
|
|
1847
1878
|
raise e
|
|
1848
1879
|
|
|
1849
1880
|
|
|
@@ -32,6 +32,8 @@ from trilogy.core.models import (
|
|
|
32
32
|
AggregateWrapper,
|
|
33
33
|
PersistStatement,
|
|
34
34
|
ListWrapper,
|
|
35
|
+
ListType,
|
|
36
|
+
TupleWrapper,
|
|
35
37
|
RowsetDerivationStatement,
|
|
36
38
|
MultiSelectStatement,
|
|
37
39
|
OrderBy,
|
|
@@ -40,6 +42,7 @@ from trilogy.core.models import (
|
|
|
40
42
|
RawSQLStatement,
|
|
41
43
|
NumericType,
|
|
42
44
|
MergeStatementV2,
|
|
45
|
+
CopyStatement,
|
|
43
46
|
)
|
|
44
47
|
from trilogy.core.enums import Modifier
|
|
45
48
|
|
|
@@ -180,6 +183,10 @@ class Renderer:
|
|
|
180
183
|
def _(self, arg: ListWrapper):
|
|
181
184
|
return "[" + ", ".join([self.to_string(x) for x in arg]) + "]"
|
|
182
185
|
|
|
186
|
+
@to_string.register
|
|
187
|
+
def _(self, arg: TupleWrapper):
|
|
188
|
+
return "(" + ", ".join([self.to_string(x) for x in arg]) + ")"
|
|
189
|
+
|
|
183
190
|
@to_string.register
|
|
184
191
|
def _(self, arg: DatePart):
|
|
185
192
|
return arg.value
|
|
@@ -211,21 +218,30 @@ class Renderer:
|
|
|
211
218
|
base_description = concept.metadata.description
|
|
212
219
|
else:
|
|
213
220
|
base_description = None
|
|
214
|
-
if concept.namespace:
|
|
221
|
+
if concept.namespace and concept.namespace != DEFAULT_NAMESPACE:
|
|
215
222
|
namespace = f"{concept.namespace}."
|
|
216
223
|
else:
|
|
217
224
|
namespace = ""
|
|
218
225
|
if not concept.lineage:
|
|
219
226
|
if concept.purpose == Purpose.PROPERTY and concept.keys:
|
|
220
|
-
|
|
227
|
+
keys = ",".join([self.to_string(key) for key in concept.keys])
|
|
228
|
+
output = f"{concept.purpose.value} <{keys}>.{namespace}{concept.name} {self.to_string(concept.datatype)};"
|
|
221
229
|
else:
|
|
222
|
-
output = f"{concept.purpose.value} {namespace}{concept.name} {concept.datatype
|
|
230
|
+
output = f"{concept.purpose.value} {namespace}{concept.name} {self.to_string(concept.datatype)};"
|
|
223
231
|
else:
|
|
224
232
|
output = f"{concept.purpose.value} {namespace}{concept.name} <- {self.to_string(concept.lineage)};"
|
|
225
233
|
if base_description:
|
|
226
234
|
output += f" # {base_description}"
|
|
227
235
|
return output
|
|
228
236
|
|
|
237
|
+
@to_string.register
|
|
238
|
+
def _(self, arg: ListType):
|
|
239
|
+
return f"list<{self.to_string(arg.value_data_type)}>"
|
|
240
|
+
|
|
241
|
+
@to_string.register
|
|
242
|
+
def _(self, arg: DataType):
|
|
243
|
+
return arg.value
|
|
244
|
+
|
|
229
245
|
@to_string.register
|
|
230
246
|
def _(self, arg: ConceptDerivation):
|
|
231
247
|
# this is identical rendering;
|
|
@@ -271,6 +287,10 @@ class Renderer:
|
|
|
271
287
|
base += "\n;"
|
|
272
288
|
return base
|
|
273
289
|
|
|
290
|
+
@to_string.register
|
|
291
|
+
def _(self, arg: CopyStatement):
|
|
292
|
+
return f"COPY INTO {arg.target_type.value.upper()} '{arg.target}' FROM {self.to_string(arg.select)}"
|
|
293
|
+
|
|
274
294
|
@to_string.register
|
|
275
295
|
def _(self, arg: AlignClause):
|
|
276
296
|
return "\nALIGN\n\t" + ",\n\t".join([self.to_string(c) for c in arg.items])
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
| persist_statement
|
|
9
9
|
| rowset_derivation_statement
|
|
10
10
|
| import_statement
|
|
11
|
-
|
|
11
|
+
| copy_statement
|
|
12
12
|
| merge_statement_v2
|
|
13
13
|
| rawsql_statement
|
|
14
14
|
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
column_list : (IDENTIFIER "," )* IDENTIFIER ","?
|
|
59
59
|
|
|
60
|
-
import_statement: "import"
|
|
60
|
+
import_statement: "import" IDENTIFIER ("." IDENTIFIER)* ("as" IDENTIFIER)?
|
|
61
61
|
|
|
62
62
|
// persist_statement
|
|
63
63
|
persist_statement: "persist"i IDENTIFIER "into"i IDENTIFIER "from"i select_statement grain_clause?
|
|
@@ -78,6 +78,12 @@
|
|
|
78
78
|
// raw sql statement
|
|
79
79
|
rawsql_statement: "raw_sql"i "(" MULTILINE_STRING ")"
|
|
80
80
|
|
|
81
|
+
// copy statement
|
|
82
|
+
|
|
83
|
+
COPY_TYPE: "csv"i
|
|
84
|
+
|
|
85
|
+
copy_statement: "copy"i "into"i COPY_TYPE _string_lit "from"i select_statement
|
|
86
|
+
|
|
81
87
|
// FUNCTION blocks
|
|
82
88
|
function: raw_function
|
|
83
89
|
function_binding_item: IDENTIFIER ":" data_type
|
|
@@ -303,6 +309,8 @@
|
|
|
303
309
|
|
|
304
310
|
array_lit: "[" (literal ",")* literal ","? "]"()
|
|
305
311
|
|
|
312
|
+
tuple_lit: "(" (literal ",")* literal ","? ")"
|
|
313
|
+
|
|
306
314
|
map_lit: "{" (literal ":" literal ",")* literal ":" literal ","? "}"
|
|
307
315
|
|
|
308
316
|
_STRUCT.1: "struct("i
|
|
@@ -312,7 +320,7 @@
|
|
|
312
320
|
|
|
313
321
|
!null_lit.1: "null"i
|
|
314
322
|
|
|
315
|
-
literal: null_lit | _string_lit | int_lit | float_lit | bool_lit | array_lit | map_lit | struct_lit
|
|
323
|
+
literal: null_lit | _string_lit | int_lit | float_lit | bool_lit | array_lit | map_lit | struct_lit | tuple_lit
|
|
316
324
|
|
|
317
325
|
MODIFIER: "Optional"i | "Partial"i | "Nullable"i
|
|
318
326
|
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from trilogy import Environment
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def test_multi_environment():
|
|
5
|
-
basic = Environment()
|
|
6
|
-
|
|
7
|
-
basic.parse(
|
|
8
|
-
"""
|
|
9
|
-
const pi <- 3.14;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
""",
|
|
13
|
-
namespace="math",
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
basic.parse(
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
select math.pi;
|
|
20
|
-
"""
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
assert basic.concepts["math.pi"].name == "pi"
|
|
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.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/__init__.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/basic_node.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/group_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/group_to_node.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/node_merge_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/rowset_node.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/select_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/trilogy/core/processing/node_generators/unnest_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.2.22 → pytrilogy-0.0.2.23}/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
|