pytrilogy 0.0.3.16__tar.gz → 0.0.3.18__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.3.16/pytrilogy.egg-info → pytrilogy-0.0.3.18}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_show.py +3 -3
- pytrilogy-0.0.3.18/tests/test_typing.py +145 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/authoring/__init__.py +3 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/enums.py +1 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/ergonomics.py +0 -1
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/functions.py +33 -14
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/author.py +34 -19
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/core.py +23 -3
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/environment.py +1 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/author.py +5 -2
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/duckdb.py +2 -1
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/common.py +4 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/parse_engine.py +80 -5
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/trilogy.lark +5 -3
- pytrilogy-0.0.3.16/tests/test_typing.py +0 -42
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/README.md +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/setup.cfg +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/setup.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_user_functions.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/build.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/execute.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/concept_strategies_v3.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/base.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/utility.py +0 -0
|
@@ -52,6 +52,6 @@ def test_show_bigquery():
|
|
|
52
52
|
.execute_query(select)
|
|
53
53
|
.fetchall()
|
|
54
54
|
)
|
|
55
|
-
assert
|
|
56
|
-
0
|
|
57
|
-
]["__preql_internal_query_text"]
|
|
55
|
+
assert (
|
|
56
|
+
"FULL JOIN cheerful on 1=1" in query[0]["__preql_internal_query_text"]
|
|
57
|
+
), query[0]["__preql_internal_query_text"]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
from trilogy import Dialects
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_typing():
|
|
7
|
+
env = Dialects.DUCK_DB.default_executor()
|
|
8
|
+
env.environment.parse(
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
type email string;
|
|
12
|
+
|
|
13
|
+
key customer_id int;
|
|
14
|
+
property customer_id.email string::email;
|
|
15
|
+
|
|
16
|
+
def is_valid_email(email) -> contains(email, '@');
|
|
17
|
+
|
|
18
|
+
datasource customers (
|
|
19
|
+
id:customer_id,
|
|
20
|
+
email: email
|
|
21
|
+
)
|
|
22
|
+
grain (customer_id)
|
|
23
|
+
query '''
|
|
24
|
+
select 1 as id, 'bright@gmail.com' as email
|
|
25
|
+
union all
|
|
26
|
+
select 2 as id, 'funky@hotmail.com' as email
|
|
27
|
+
''';
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
results = env.execute_query(
|
|
34
|
+
"""SELECT
|
|
35
|
+
customer_id,
|
|
36
|
+
@is_valid_email(email)->valid;"""
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
for row in results.fetchall():
|
|
40
|
+
assert row.valid is True
|
|
41
|
+
|
|
42
|
+
assert "email" in env.environment.data_types
|
|
43
|
+
|
|
44
|
+
assert env.environment.concepts["email"].datatype.traits == ["email"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_typing_aggregate():
|
|
48
|
+
env = Dialects.DUCK_DB.default_executor()
|
|
49
|
+
env.environment.parse(
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
type money float;
|
|
53
|
+
|
|
54
|
+
key revenue float::money;
|
|
55
|
+
key revenue_two float::money;
|
|
56
|
+
key multiplier float;
|
|
57
|
+
|
|
58
|
+
datasource orders (
|
|
59
|
+
revenue:revenue,
|
|
60
|
+
revenue_two:revenue_two,
|
|
61
|
+
multiplier:multiplier
|
|
62
|
+
)
|
|
63
|
+
grain (revenue)
|
|
64
|
+
query '''
|
|
65
|
+
select 5.0 as revenue, 3.3 as revenue_two, 2.0 as multiplier
|
|
66
|
+
union all
|
|
67
|
+
select 10.0 as revenue, 13.1 as revenue_two, 3.0 as multiplier
|
|
68
|
+
''';
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
results = env.execute_query(
|
|
75
|
+
"""SELECT
|
|
76
|
+
sum(revenue)->direct_total,
|
|
77
|
+
sum(revenue*multiplier)->total,
|
|
78
|
+
direct_total-total -> diff;"""
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
for row in results.fetchall():
|
|
82
|
+
assert row.total == Decimal("40.00")
|
|
83
|
+
|
|
84
|
+
assert "money" in env.environment.data_types
|
|
85
|
+
assert env.environment.concepts["direct_total"].datatype.traits == ["money"]
|
|
86
|
+
assert env.environment.concepts["total"].datatype.traits == ["money"]
|
|
87
|
+
assert env.environment.concepts["diff"].datatype.traits == [
|
|
88
|
+
"money"
|
|
89
|
+
], env.environment.concepts["diff"].datatype
|
|
90
|
+
|
|
91
|
+
results = env.execute_query(
|
|
92
|
+
"""SELECT
|
|
93
|
+
revenue+revenue_two->add_total,
|
|
94
|
+
revenue-revenue_two->sub_total
|
|
95
|
+
;"""
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
assert "money" in env.environment.data_types
|
|
99
|
+
assert env.environment.concepts["add_total"].datatype.traits == ["money"]
|
|
100
|
+
assert env.environment.concepts["sub_total"].datatype.traits == ["money"]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_custom_function_typing():
|
|
104
|
+
env = Dialects.DUCK_DB.default_executor()
|
|
105
|
+
env.environment.parse(
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
type money float;
|
|
109
|
+
|
|
110
|
+
key revenue float::money;
|
|
111
|
+
|
|
112
|
+
def revenue_times_2(revenue) -> revenue*2;
|
|
113
|
+
|
|
114
|
+
def revenue_times_multiplier(revenue, multiplier) -> revenue*multiplier;
|
|
115
|
+
|
|
116
|
+
datasource orders (
|
|
117
|
+
revenue:revenue,
|
|
118
|
+
|
|
119
|
+
)
|
|
120
|
+
grain (revenue)
|
|
121
|
+
query '''
|
|
122
|
+
select 5.0 as revenue, 3.3 as revenue_two, 2.0 as multiplier
|
|
123
|
+
union all
|
|
124
|
+
select 10.0 as revenue, 13.1 as revenue_two, 3.0 as multiplier
|
|
125
|
+
''';
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
_ = env.execute_query(
|
|
132
|
+
"""
|
|
133
|
+
with scaled as
|
|
134
|
+
SELECT
|
|
135
|
+
@revenue_times_2(revenue)->revenue
|
|
136
|
+
;
|
|
137
|
+
|
|
138
|
+
SELECT
|
|
139
|
+
sum(
|
|
140
|
+
round( @revenue_times_multiplier(lag 1 @revenue_times_2(scaled.revenue), 2.0), 2)
|
|
141
|
+
)->total;
|
|
142
|
+
"""
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
assert env.environment.concepts["total"].datatype.traits == ["money"]
|
|
@@ -40,6 +40,7 @@ from trilogy.core.models.core import (
|
|
|
40
40
|
MapType,
|
|
41
41
|
StructType,
|
|
42
42
|
)
|
|
43
|
+
from trilogy.core.models.datasource import Datasource, DatasourceMetadata
|
|
43
44
|
from trilogy.core.models.environment import Environment
|
|
44
45
|
from trilogy.core.statements.author import (
|
|
45
46
|
ConceptDeclarationStatement,
|
|
@@ -100,4 +101,6 @@ __all__ = [
|
|
|
100
101
|
"MultiSelectStatement",
|
|
101
102
|
"PersistStatement",
|
|
102
103
|
"RawSQLStatement",
|
|
104
|
+
"Datasource",
|
|
105
|
+
"DatasourceMetadata",
|
|
103
106
|
]
|
|
@@ -29,6 +29,7 @@ from trilogy.core.models.core import (
|
|
|
29
29
|
MapType,
|
|
30
30
|
NumericType,
|
|
31
31
|
StructType,
|
|
32
|
+
TraitDataType,
|
|
32
33
|
arg_to_datatype,
|
|
33
34
|
merge_datatypes,
|
|
34
35
|
)
|
|
@@ -94,6 +95,10 @@ def get_cast_output_type(
|
|
|
94
95
|
return args[1]
|
|
95
96
|
|
|
96
97
|
|
|
98
|
+
def get_output_type_at_index(args, index: int):
|
|
99
|
+
return arg_to_datatype(args[index])
|
|
100
|
+
|
|
101
|
+
|
|
97
102
|
def validate_case_output(
|
|
98
103
|
args: list[Any],
|
|
99
104
|
) -> DataType:
|
|
@@ -140,6 +145,12 @@ def get_date_trunc_output(
|
|
|
140
145
|
|
|
141
146
|
|
|
142
147
|
FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
148
|
+
FunctionType.ALIAS: FunctionConfig(
|
|
149
|
+
arg_count=1,
|
|
150
|
+
),
|
|
151
|
+
FunctionType.PARENTHETICAL: FunctionConfig(
|
|
152
|
+
arg_count=1,
|
|
153
|
+
),
|
|
143
154
|
FunctionType.UNNEST: FunctionConfig(
|
|
144
155
|
valid_inputs={
|
|
145
156
|
DataType.ARRAY,
|
|
@@ -173,7 +184,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
173
184
|
DataType.BOOL,
|
|
174
185
|
},
|
|
175
186
|
output_purpose=Purpose.METRIC,
|
|
176
|
-
output_type=DataType.INTEGER,
|
|
177
187
|
arg_count=1,
|
|
178
188
|
),
|
|
179
189
|
FunctionType.MIN: FunctionConfig(
|
|
@@ -186,7 +196,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
186
196
|
DataType.TIMESTAMP,
|
|
187
197
|
},
|
|
188
198
|
output_purpose=Purpose.METRIC,
|
|
189
|
-
output_type=DataType.INTEGER,
|
|
190
199
|
arg_count=1,
|
|
191
200
|
),
|
|
192
201
|
FunctionType.SPLIT: FunctionConfig(
|
|
@@ -237,7 +246,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
237
246
|
FunctionType.ABS: FunctionConfig(
|
|
238
247
|
valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
239
248
|
output_purpose=Purpose.PROPERTY,
|
|
240
|
-
output_type=DataType.INTEGER,
|
|
241
249
|
arg_count=1,
|
|
242
250
|
),
|
|
243
251
|
FunctionType.COALESCE: FunctionConfig(
|
|
@@ -370,6 +378,21 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
370
378
|
output_type=DataType.DATE,
|
|
371
379
|
arg_count=3,
|
|
372
380
|
),
|
|
381
|
+
FunctionType.DATE_SUB: FunctionConfig(
|
|
382
|
+
valid_inputs=[
|
|
383
|
+
{
|
|
384
|
+
DataType.DATE,
|
|
385
|
+
DataType.TIMESTAMP,
|
|
386
|
+
DataType.DATETIME,
|
|
387
|
+
DataType.STRING,
|
|
388
|
+
},
|
|
389
|
+
{DataType.DATE_PART},
|
|
390
|
+
{DataType.INTEGER},
|
|
391
|
+
],
|
|
392
|
+
output_purpose=Purpose.PROPERTY,
|
|
393
|
+
output_type=DataType.DATE,
|
|
394
|
+
arg_count=3,
|
|
395
|
+
),
|
|
373
396
|
FunctionType.DATE_DIFF: FunctionConfig(
|
|
374
397
|
valid_inputs=[
|
|
375
398
|
{
|
|
@@ -519,7 +542,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
519
542
|
DataType.NUMERIC,
|
|
520
543
|
},
|
|
521
544
|
output_purpose=Purpose.PROPERTY,
|
|
522
|
-
output_type=DataType.INTEGER,
|
|
523
545
|
arg_count=InfiniteFunctionArgs,
|
|
524
546
|
),
|
|
525
547
|
FunctionType.SUBTRACT: FunctionConfig(
|
|
@@ -530,7 +552,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
530
552
|
DataType.NUMERIC,
|
|
531
553
|
},
|
|
532
554
|
output_purpose=Purpose.PROPERTY,
|
|
533
|
-
output_type=DataType.INTEGER,
|
|
534
555
|
arg_count=InfiniteFunctionArgs,
|
|
535
556
|
),
|
|
536
557
|
FunctionType.MULTIPLY: FunctionConfig(
|
|
@@ -541,7 +562,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
541
562
|
DataType.NUMERIC,
|
|
542
563
|
},
|
|
543
564
|
output_purpose=Purpose.PROPERTY,
|
|
544
|
-
output_type=DataType.INTEGER,
|
|
545
565
|
arg_count=InfiniteFunctionArgs,
|
|
546
566
|
),
|
|
547
567
|
FunctionType.DIVIDE: FunctionConfig(
|
|
@@ -552,7 +572,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
552
572
|
DataType.NUMERIC,
|
|
553
573
|
},
|
|
554
574
|
output_purpose=Purpose.PROPERTY,
|
|
555
|
-
output_type=DataType.INTEGER,
|
|
556
575
|
arg_count=InfiniteFunctionArgs,
|
|
557
576
|
),
|
|
558
577
|
FunctionType.MOD: FunctionConfig(
|
|
@@ -570,7 +589,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
570
589
|
{DataType.INTEGER},
|
|
571
590
|
],
|
|
572
591
|
output_purpose=Purpose.PROPERTY,
|
|
573
|
-
|
|
592
|
+
output_type_function=lambda args: get_output_type_at_index(args, 0),
|
|
574
593
|
arg_count=2,
|
|
575
594
|
),
|
|
576
595
|
FunctionType.CUSTOM: FunctionConfig(
|
|
@@ -621,13 +640,11 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
621
640
|
FunctionType.SUM: FunctionConfig(
|
|
622
641
|
valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
623
642
|
output_purpose=Purpose.METRIC,
|
|
624
|
-
output_type=DataType.INTEGER,
|
|
625
643
|
arg_count=1,
|
|
626
644
|
),
|
|
627
645
|
FunctionType.AVG: FunctionConfig(
|
|
628
646
|
valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
629
647
|
output_purpose=Purpose.METRIC,
|
|
630
|
-
output_type=DataType.INTEGER,
|
|
631
648
|
arg_count=1,
|
|
632
649
|
),
|
|
633
650
|
FunctionType.UNIX_TO_TIMESTAMP: FunctionConfig(
|
|
@@ -640,8 +657,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
640
657
|
|
|
641
658
|
EXCLUDED_FUNCTIONS = {
|
|
642
659
|
FunctionType.CUSTOM,
|
|
643
|
-
FunctionType.ALIAS,
|
|
644
|
-
FunctionType.PARENTHETICAL,
|
|
645
660
|
# Temporary
|
|
646
661
|
FunctionType.DATE_LITERAL,
|
|
647
662
|
FunctionType.DATETIME_LITERAL,
|
|
@@ -650,7 +665,9 @@ EXCLUDED_FUNCTIONS = {
|
|
|
650
665
|
|
|
651
666
|
for k in FunctionType.__members__.values():
|
|
652
667
|
if k not in FUNCTION_REGISTRY and k not in EXCLUDED_FUNCTIONS:
|
|
653
|
-
raise InvalidSyntaxException(
|
|
668
|
+
raise InvalidSyntaxException(
|
|
669
|
+
f"Function enum value {k} not in creation registry"
|
|
670
|
+
)
|
|
654
671
|
|
|
655
672
|
|
|
656
673
|
class FunctionFactory:
|
|
@@ -686,8 +703,10 @@ class FunctionFactory:
|
|
|
686
703
|
full_args = []
|
|
687
704
|
final_output_type: CONCRETE_TYPES
|
|
688
705
|
if config.output_type_function:
|
|
706
|
+
|
|
689
707
|
final_output_type = config.output_type_function(full_args)
|
|
690
708
|
elif not base_output_type:
|
|
709
|
+
|
|
691
710
|
final_output_type = merge_datatypes([arg_to_datatype(x) for x in full_args])
|
|
692
711
|
elif base_output_type:
|
|
693
712
|
final_output_type = base_output_type
|
|
@@ -715,7 +734,7 @@ def create_function_derived_concept(
|
|
|
715
734
|
operator: FunctionType,
|
|
716
735
|
arguments: list[Concept],
|
|
717
736
|
output_type: Optional[
|
|
718
|
-
DataType | ListType | StructType | MapType | NumericType
|
|
737
|
+
DataType | ListType | StructType | MapType | NumericType | TraitDataType
|
|
719
738
|
] = None,
|
|
720
739
|
output_purpose: Optional[Purpose] = None,
|
|
721
740
|
) -> Concept:
|
|
@@ -793,6 +793,10 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
793
793
|
base = f"{self.address}@{self.grain}"
|
|
794
794
|
return base
|
|
795
795
|
|
|
796
|
+
@property
|
|
797
|
+
def is_internal(self) -> bool:
|
|
798
|
+
return self.namespace.startswith("_") or self.name.startswith("_")
|
|
799
|
+
|
|
796
800
|
@property
|
|
797
801
|
def reference(self) -> ConceptRef:
|
|
798
802
|
return ConceptRef.model_construct(
|
|
@@ -1370,7 +1374,7 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1370
1374
|
|
|
1371
1375
|
|
|
1372
1376
|
def get_basic_type(
|
|
1373
|
-
type: DataType | ListType | StructType | MapType | NumericType,
|
|
1377
|
+
type: DataType | ListType | StructType | MapType | NumericType | TraitDataType,
|
|
1374
1378
|
) -> DataType:
|
|
1375
1379
|
if isinstance(type, ListType):
|
|
1376
1380
|
return DataType.LIST
|
|
@@ -1380,6 +1384,8 @@ def get_basic_type(
|
|
|
1380
1384
|
return DataType.MAP
|
|
1381
1385
|
if isinstance(type, NumericType):
|
|
1382
1386
|
return DataType.NUMERIC
|
|
1387
|
+
if isinstance(type, TraitDataType):
|
|
1388
|
+
return type.type
|
|
1383
1389
|
return type
|
|
1384
1390
|
|
|
1385
1391
|
|
|
@@ -1531,7 +1537,9 @@ def get_concept_arguments(expr) -> List["ConceptRef"]:
|
|
|
1531
1537
|
class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
1532
1538
|
operator: FunctionType
|
|
1533
1539
|
arg_count: int = Field(default=1)
|
|
1534
|
-
output_datatype:
|
|
1540
|
+
output_datatype: (
|
|
1541
|
+
DataType | ListType | StructType | MapType | NumericType | TraitDataType
|
|
1542
|
+
)
|
|
1535
1543
|
output_purpose: Purpose
|
|
1536
1544
|
valid_inputs: Optional[
|
|
1537
1545
|
Union[
|
|
@@ -1629,23 +1637,30 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1629
1637
|
return v
|
|
1630
1638
|
|
|
1631
1639
|
def with_reference_replacement(self, source: str, target: Expr):
|
|
1640
|
+
from trilogy.core.functions import arg_to_datatype, merge_datatypes
|
|
1641
|
+
|
|
1642
|
+
nargs = [
|
|
1643
|
+
(
|
|
1644
|
+
c.with_reference_replacement(
|
|
1645
|
+
source,
|
|
1646
|
+
target,
|
|
1647
|
+
)
|
|
1648
|
+
if isinstance(
|
|
1649
|
+
c,
|
|
1650
|
+
Mergeable,
|
|
1651
|
+
)
|
|
1652
|
+
else c
|
|
1653
|
+
)
|
|
1654
|
+
for c in self.arguments
|
|
1655
|
+
]
|
|
1656
|
+
if self.output_datatype == DataType.UNKNOWN:
|
|
1657
|
+
new_output = merge_datatypes([arg_to_datatype(x) for x in nargs])
|
|
1658
|
+
else:
|
|
1659
|
+
new_output = self.output_datatype
|
|
1632
1660
|
return Function.model_construct(
|
|
1633
1661
|
operator=self.operator,
|
|
1634
|
-
arguments=
|
|
1635
|
-
|
|
1636
|
-
c.with_reference_replacement(
|
|
1637
|
-
source,
|
|
1638
|
-
target,
|
|
1639
|
-
)
|
|
1640
|
-
if isinstance(
|
|
1641
|
-
c,
|
|
1642
|
-
Mergeable,
|
|
1643
|
-
)
|
|
1644
|
-
else c
|
|
1645
|
-
)
|
|
1646
|
-
for c in self.arguments
|
|
1647
|
-
],
|
|
1648
|
-
output_datatype=self.output_datatype,
|
|
1662
|
+
arguments=nargs,
|
|
1663
|
+
output_datatype=new_output,
|
|
1649
1664
|
output_purpose=self.output_purpose,
|
|
1650
1665
|
valid_inputs=self.valid_inputs,
|
|
1651
1666
|
arg_count=self.arg_count,
|
|
@@ -1777,7 +1792,7 @@ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel)
|
|
|
1777
1792
|
)
|
|
1778
1793
|
|
|
1779
1794
|
|
|
1780
|
-
class FilterItem(Namespaced, ConceptArgs, BaseModel):
|
|
1795
|
+
class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
|
|
1781
1796
|
content: ConceptRef
|
|
1782
1797
|
where: "WhereClause"
|
|
1783
1798
|
|
|
@@ -1837,7 +1852,7 @@ class RowsetLineage(Namespaced, Mergeable, BaseModel):
|
|
|
1837
1852
|
)
|
|
1838
1853
|
|
|
1839
1854
|
|
|
1840
|
-
class RowsetItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
1855
|
+
class RowsetItem(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel):
|
|
1841
1856
|
content: ConceptRef
|
|
1842
1857
|
rowset: RowsetLineage
|
|
1843
1858
|
|
|
@@ -51,7 +51,13 @@ class Addressable(ABC):
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
TYPEDEF_TYPES = Union[
|
|
54
|
-
"DataType",
|
|
54
|
+
"DataType",
|
|
55
|
+
"MapType",
|
|
56
|
+
"ListType",
|
|
57
|
+
"NumericType",
|
|
58
|
+
"StructType",
|
|
59
|
+
"DataTyped",
|
|
60
|
+
"TraitDataType",
|
|
55
61
|
]
|
|
56
62
|
|
|
57
63
|
CONCRETE_TYPES = Union[
|
|
@@ -60,6 +66,7 @@ CONCRETE_TYPES = Union[
|
|
|
60
66
|
"ListType",
|
|
61
67
|
"NumericType",
|
|
62
68
|
"StructType",
|
|
69
|
+
"TraitDataType",
|
|
63
70
|
]
|
|
64
71
|
|
|
65
72
|
KT = TypeVar("KT")
|
|
@@ -101,6 +108,16 @@ class TraitDataType(BaseModel):
|
|
|
101
108
|
type: DataType
|
|
102
109
|
traits: list[str]
|
|
103
110
|
|
|
111
|
+
def __hash__(self):
|
|
112
|
+
return hash(self.type)
|
|
113
|
+
|
|
114
|
+
def __eq__(self, other):
|
|
115
|
+
if isinstance(other, DataType):
|
|
116
|
+
return self.type == other
|
|
117
|
+
elif isinstance(other, TraitDataType):
|
|
118
|
+
return self.type == other.type and self.traits == other.traits
|
|
119
|
+
return False
|
|
120
|
+
|
|
104
121
|
@property
|
|
105
122
|
def data_type(self):
|
|
106
123
|
return self.type
|
|
@@ -312,8 +329,10 @@ def dict_to_map_wrapper(arg):
|
|
|
312
329
|
|
|
313
330
|
|
|
314
331
|
def merge_datatypes(
|
|
315
|
-
inputs: list[
|
|
316
|
-
|
|
332
|
+
inputs: list[
|
|
333
|
+
DataType | ListType | StructType | MapType | NumericType | TraitDataType
|
|
334
|
+
],
|
|
335
|
+
) -> DataType | ListType | StructType | MapType | NumericType | TraitDataType:
|
|
317
336
|
"""This is a temporary hack for doing between
|
|
318
337
|
allowable datatype transformation matrix"""
|
|
319
338
|
if len(inputs) == 1:
|
|
@@ -348,6 +367,7 @@ def is_compatible_datatype(left, right):
|
|
|
348
367
|
|
|
349
368
|
|
|
350
369
|
def arg_to_datatype(arg) -> CONCRETE_TYPES:
|
|
370
|
+
|
|
351
371
|
if isinstance(arg, MagicConstants):
|
|
352
372
|
if arg == MagicConstants.NULL:
|
|
353
373
|
return DataType.NULL
|
|
@@ -134,7 +134,6 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
134
134
|
output.grain = output.calculate_grain(environment)
|
|
135
135
|
|
|
136
136
|
for x in selection:
|
|
137
|
-
|
|
138
137
|
if x.is_undefined and environment.concepts.fail_on_missing:
|
|
139
138
|
environment.concepts.raise_undefined(
|
|
140
139
|
x.concept.address, meta.line_number if meta else None
|
|
@@ -158,7 +157,6 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
158
157
|
output.local_concepts[x.content.address] = environment.concepts[
|
|
159
158
|
x.content.address
|
|
160
159
|
] # .set_select_grain(output.grain, environment)
|
|
161
|
-
|
|
162
160
|
output.validate_syntax(environment)
|
|
163
161
|
return output
|
|
164
162
|
|
|
@@ -384,7 +382,12 @@ class MergeStatementV2(HasUUID, BaseModel):
|
|
|
384
382
|
|
|
385
383
|
|
|
386
384
|
class ImportStatement(HasUUID, BaseModel):
|
|
385
|
+
# import abc.def as bar
|
|
386
|
+
# the bit after 'as', eg bar
|
|
387
387
|
alias: str
|
|
388
|
+
# the bit after import, abc.def
|
|
389
|
+
input_path: str
|
|
390
|
+
# what it actually resolves to, typically a filepath
|
|
388
391
|
path: Path
|
|
389
392
|
|
|
390
393
|
|
|
@@ -8,6 +8,7 @@ from trilogy.dialect.base import BaseDialect
|
|
|
8
8
|
|
|
9
9
|
WINDOW_FUNCTION_MAP: Mapping[WindowType, Callable[[Any, Any, Any], str]] = {}
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
FUNCTION_MAP = {
|
|
12
13
|
FunctionType.COUNT: lambda args: f"count({args[0]})",
|
|
13
14
|
FunctionType.SUM: lambda args: f"sum({args[0]})",
|
|
@@ -27,7 +28,7 @@ FUNCTION_MAP = {
|
|
|
27
28
|
# datetime is aliased
|
|
28
29
|
FunctionType.CURRENT_DATETIME: lambda x: "cast(get_current_timestamp() as datetime)",
|
|
29
30
|
FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc('{x[1]}', {x[0]})",
|
|
30
|
-
FunctionType.DATE_ADD: lambda x: f"date_add({x[0]},
|
|
31
|
+
FunctionType.DATE_ADD: lambda x: f"date_add({x[0]}, {x[2]} * INTERVAL 1 {x[1]})",
|
|
31
32
|
FunctionType.DATE_PART: lambda x: f"date_part('{x[1]}', {x[0]})",
|
|
32
33
|
FunctionType.DATE_DIFF: lambda x: f"date_diff('{x[2]}', {x[0]}, {x[1]})",
|
|
33
34
|
FunctionType.CONCAT: lambda x: f"({' || '.join(x)})",
|
|
@@ -100,6 +100,8 @@ def process_function_arg(
|
|
|
100
100
|
concept.metadata.line_number = meta.line
|
|
101
101
|
environment.add_concept(concept, meta=meta)
|
|
102
102
|
return concept
|
|
103
|
+
elif isinstance(arg, ConceptRef):
|
|
104
|
+
return environment.concepts[arg.address]
|
|
103
105
|
return arg
|
|
104
106
|
|
|
105
107
|
|
|
@@ -192,6 +194,8 @@ def concept_is_relevant(
|
|
|
192
194
|
) -> bool:
|
|
193
195
|
if isinstance(concept, UndefinedConcept):
|
|
194
196
|
|
|
197
|
+
return False
|
|
198
|
+
if concept.datatype == DataType.UNKNOWN:
|
|
195
199
|
return False
|
|
196
200
|
if isinstance(concept, ConceptRef):
|
|
197
201
|
if environment:
|