pytrilogy 0.0.1.111__tar.gz → 0.0.1.113__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.1.111/pytrilogy.egg-info → pytrilogy-0.0.1.113}/PKG-INFO +1 -1
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_declarations.py +3 -1
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_parsing.py +48 -2
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_select.py +1 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_where_clause.py +1 -1
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/enums.py +7 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/models.py +44 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/optimization.py +11 -12
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/base.py +37 -17
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/parsing/common.py +1 -1
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/parsing/parse_engine.py +72 -309
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/LICENSE.md +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/README.md +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/pyproject.toml +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/setup.cfg +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/setup.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_models.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/concept_strategies_v3.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/concept_merge_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/utility.py +0 -0
|
@@ -4,7 +4,7 @@ from trilogy.parser import parse
|
|
|
4
4
|
def test_declarations():
|
|
5
5
|
declarations = """key namespace.user_id int metadata(description="the description");
|
|
6
6
|
metric namespace.count <- count(namespace.user_id);
|
|
7
|
-
metric namespace.distinct_count <- count_distinct(namespace.user_id); #
|
|
7
|
+
metric namespace.distinct_count <- count_distinct(namespace.user_id); #the distinct count of user ids
|
|
8
8
|
"""
|
|
9
9
|
env, _ = parse(declarations)
|
|
10
10
|
|
|
@@ -14,3 +14,5 @@ def test_declarations():
|
|
|
14
14
|
env.concepts["namespace.distinct_count"].metadata.description
|
|
15
15
|
== "the distinct count of user ids"
|
|
16
16
|
)
|
|
17
|
+
|
|
18
|
+
assert env.concepts["namespace.user_id"].metadata.description == "the description"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from trilogy.core.enums import Purpose
|
|
1
|
+
from trilogy.core.enums import Purpose, ComparisonOperator
|
|
2
2
|
from trilogy.core.models import (
|
|
3
3
|
DataType,
|
|
4
4
|
Parenthetical,
|
|
@@ -6,6 +6,7 @@ from trilogy.core.models import (
|
|
|
6
6
|
ShowStatement,
|
|
7
7
|
SelectStatement,
|
|
8
8
|
Environment,
|
|
9
|
+
Comparison,
|
|
9
10
|
)
|
|
10
11
|
from trilogy.core.functions import argument_to_purpose, function_args_to_output_purpose
|
|
11
12
|
from trilogy.parsing.parse_engine import (
|
|
@@ -210,10 +211,55 @@ rowset test<- select
|
|
|
210
211
|
;
|
|
211
212
|
|
|
212
213
|
select
|
|
213
|
-
count(test.name) -> test_name_count;
|
|
214
|
+
count( test.name ) -> test_name_count;
|
|
214
215
|
"""
|
|
215
216
|
)
|
|
216
217
|
# assert output_purpose == Purpose.METRIC
|
|
217
218
|
for name in ["test_name_count"]:
|
|
218
219
|
assert name in env.concepts
|
|
219
220
|
assert env.concepts[name].purpose == Purpose.METRIC
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_between():
|
|
224
|
+
_, parsed = parse_text(
|
|
225
|
+
"const order_id <- 4; SELECT order_id WHERE order_id BETWEEN 3 and 5;"
|
|
226
|
+
)
|
|
227
|
+
query: ProcessedQuery = parsed[-1]
|
|
228
|
+
left = query.where_clause.conditional.left
|
|
229
|
+
assert isinstance(
|
|
230
|
+
left,
|
|
231
|
+
Comparison,
|
|
232
|
+
), type(left)
|
|
233
|
+
assert left.operator == ComparisonOperator.GTE
|
|
234
|
+
assert left.right == 3
|
|
235
|
+
|
|
236
|
+
right = query.where_clause.conditional.right
|
|
237
|
+
assert isinstance(
|
|
238
|
+
right,
|
|
239
|
+
Comparison,
|
|
240
|
+
), type(right)
|
|
241
|
+
assert right.operator == ComparisonOperator.LTE
|
|
242
|
+
assert right.right == 5
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_the_comments():
|
|
246
|
+
_, parsed = parse_text(
|
|
247
|
+
"""const
|
|
248
|
+
# comment here?
|
|
249
|
+
order_id <- 4; SELECT
|
|
250
|
+
# TOOD - add in more columns?
|
|
251
|
+
order_id # this is the order id
|
|
252
|
+
WHERE
|
|
253
|
+
# order_id should not be null
|
|
254
|
+
order_id
|
|
255
|
+
# in this comp
|
|
256
|
+
is not
|
|
257
|
+
null; # nulls are the worst
|
|
258
|
+
|
|
259
|
+
"""
|
|
260
|
+
)
|
|
261
|
+
query = parsed[-1]
|
|
262
|
+
right = query.where_clause.conditional.right
|
|
263
|
+
assert isinstance(right, MagicConstants), type(right)
|
|
264
|
+
rendered = BaseDialect().render_expr(right)
|
|
265
|
+
assert rendered == "null"
|
|
@@ -76,6 +76,12 @@ class JoinType(Enum):
|
|
|
76
76
|
class Ordering(Enum):
|
|
77
77
|
ASCENDING = "asc"
|
|
78
78
|
DESCENDING = "desc"
|
|
79
|
+
ASC_NULLS_AUTO = "asc nulls auto"
|
|
80
|
+
ASC_NULLS_FIRST = "asc nulls first"
|
|
81
|
+
ASC_NULLS_LAST = "asc nulls last"
|
|
82
|
+
DESC_NULLS_FIRST = "desc nulls first"
|
|
83
|
+
DESC_NULLS_LAST = "desc nulls last"
|
|
84
|
+
DESC_NULLS_AUTO = "desc nulls auto"
|
|
79
85
|
|
|
80
86
|
|
|
81
87
|
class WindowType(Enum):
|
|
@@ -220,6 +226,7 @@ class ComparisonOperator(Enum):
|
|
|
220
226
|
ILIKE = "ilike"
|
|
221
227
|
CONTAINS = "contains"
|
|
222
228
|
ELSE = "else"
|
|
229
|
+
BETWEEN = "between"
|
|
223
230
|
|
|
224
231
|
@classmethod
|
|
225
232
|
def _missing_(cls, value):
|
|
@@ -812,6 +812,7 @@ class Function(Namespaced, SelectGrain, BaseModel):
|
|
|
812
812
|
"Parenthetical",
|
|
813
813
|
CaseWhen,
|
|
814
814
|
"CaseElse",
|
|
815
|
+
list,
|
|
815
816
|
ListWrapper[int],
|
|
816
817
|
ListWrapper[str],
|
|
817
818
|
ListWrapper[float],
|
|
@@ -996,6 +997,7 @@ class WindowItem(Namespaced, SelectGrain, BaseModel):
|
|
|
996
997
|
content: Concept
|
|
997
998
|
order_by: List["OrderItem"]
|
|
998
999
|
over: List["Concept"] = Field(default_factory=list)
|
|
1000
|
+
index: Optional[int] = None
|
|
999
1001
|
|
|
1000
1002
|
def with_namespace(self, namespace: str) -> "WindowItem":
|
|
1001
1003
|
return WindowItem(
|
|
@@ -2041,6 +2043,14 @@ class CTE(BaseModel):
|
|
|
2041
2043
|
ds_being_inlined = qds_being_inlined.datasources[0]
|
|
2042
2044
|
if not isinstance(ds_being_inlined, Datasource):
|
|
2043
2045
|
return False
|
|
2046
|
+
if any(
|
|
2047
|
+
[
|
|
2048
|
+
x.identifier == ds_being_inlined.identifier
|
|
2049
|
+
for x in self.source.datasources
|
|
2050
|
+
]
|
|
2051
|
+
):
|
|
2052
|
+
return False
|
|
2053
|
+
|
|
2044
2054
|
self.source.datasources = [
|
|
2045
2055
|
ds_being_inlined,
|
|
2046
2056
|
*[
|
|
@@ -2727,6 +2737,14 @@ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
|
2727
2737
|
raise SyntaxError(
|
|
2728
2738
|
f"Cannot compare {self.left} and {self.right} of different types"
|
|
2729
2739
|
)
|
|
2740
|
+
if self.operator == ComparisonOperator.BETWEEN:
|
|
2741
|
+
if (
|
|
2742
|
+
not isinstance(self.right, ComparisonOperator)
|
|
2743
|
+
and self.right.operator == BooleanOperator.AND
|
|
2744
|
+
):
|
|
2745
|
+
raise SyntaxError(
|
|
2746
|
+
f"Between operator must have two operands with and, not {self.right}"
|
|
2747
|
+
)
|
|
2730
2748
|
|
|
2731
2749
|
def __add__(self, other):
|
|
2732
2750
|
if not isinstance(other, (Comparison, Conditional, Parenthetical)):
|
|
@@ -2802,6 +2820,29 @@ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
|
2802
2820
|
output += get_concept_arguments(self.right)
|
|
2803
2821
|
return output
|
|
2804
2822
|
|
|
2823
|
+
@property
|
|
2824
|
+
def row_arguments(self) -> List[Concept]:
|
|
2825
|
+
output = []
|
|
2826
|
+
if isinstance(self.left, ConceptArgs):
|
|
2827
|
+
output += self.left.row_arguments
|
|
2828
|
+
else:
|
|
2829
|
+
output += get_concept_arguments(self.left)
|
|
2830
|
+
if isinstance(self.right, ConceptArgs):
|
|
2831
|
+
output += self.right.row_arguments
|
|
2832
|
+
else:
|
|
2833
|
+
output += get_concept_arguments(self.right)
|
|
2834
|
+
return output
|
|
2835
|
+
|
|
2836
|
+
@property
|
|
2837
|
+
def existence_arguments(self) -> List[Tuple[Concept, ...]]:
|
|
2838
|
+
"""Return concepts directly referenced in where clause"""
|
|
2839
|
+
output: List[Tuple[Concept, ...]] = []
|
|
2840
|
+
if isinstance(self.left, ConceptArgs):
|
|
2841
|
+
output += self.left.existence_arguments
|
|
2842
|
+
if isinstance(self.right, ConceptArgs):
|
|
2843
|
+
output += self.right.existence_arguments
|
|
2844
|
+
return output
|
|
2845
|
+
|
|
2805
2846
|
|
|
2806
2847
|
class SubselectComparison(Comparison):
|
|
2807
2848
|
|
|
@@ -2901,6 +2942,7 @@ class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
|
2901
2942
|
float,
|
|
2902
2943
|
list,
|
|
2903
2944
|
bool,
|
|
2945
|
+
MagicConstants,
|
|
2904
2946
|
Concept,
|
|
2905
2947
|
Comparison,
|
|
2906
2948
|
"Conditional",
|
|
@@ -2914,11 +2956,13 @@ class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
|
2914
2956
|
float,
|
|
2915
2957
|
list,
|
|
2916
2958
|
bool,
|
|
2959
|
+
MagicConstants,
|
|
2917
2960
|
Concept,
|
|
2918
2961
|
Comparison,
|
|
2919
2962
|
"Conditional",
|
|
2920
2963
|
"Parenthetical",
|
|
2921
2964
|
Function,
|
|
2965
|
+
FilterItem,
|
|
2922
2966
|
]
|
|
2923
2967
|
operator: BooleanOperator
|
|
2924
2968
|
|
|
@@ -12,9 +12,6 @@ from trilogy.constants import logger, CONFIG
|
|
|
12
12
|
from abc import ABC
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
REGISTERED_RULES: list["OptimizationRule"] = []
|
|
16
|
-
|
|
17
|
-
|
|
18
15
|
class OptimizationRule(ABC):
|
|
19
16
|
|
|
20
17
|
def optimize(self, cte: CTE, inverse_map: dict[str, list[CTE]]) -> bool:
|
|
@@ -66,13 +63,15 @@ class InlineDatasource(OptimizationRule):
|
|
|
66
63
|
to_inline.append(parent_cte)
|
|
67
64
|
|
|
68
65
|
for replaceable in to_inline:
|
|
69
|
-
self.log(f"Inlining parent {replaceable.name}")
|
|
70
|
-
cte.inline_parent_datasource(replaceable, force_group=force_group)
|
|
71
66
|
|
|
67
|
+
result = cte.inline_parent_datasource(replaceable, force_group=force_group)
|
|
68
|
+
if result:
|
|
69
|
+
self.log(f"Inlined parent {replaceable.name}")
|
|
70
|
+
else:
|
|
71
|
+
self.log(f"Failed to inline {replaceable.name}")
|
|
72
72
|
return optimized
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
# This will be used in the future for more complex condition decomposition
|
|
76
75
|
def decompose_condition(conditional: Conditional):
|
|
77
76
|
chunks = []
|
|
78
77
|
if conditional.operator == BooleanOperator.AND:
|
|
@@ -154,12 +153,6 @@ class PredicatePushdown(OptimizationRule):
|
|
|
154
153
|
return optimized
|
|
155
154
|
|
|
156
155
|
|
|
157
|
-
if CONFIG.optimizations.datasource_inlining:
|
|
158
|
-
REGISTERED_RULES.append(InlineDatasource())
|
|
159
|
-
if CONFIG.optimizations.predicate_pushdown:
|
|
160
|
-
REGISTERED_RULES.append(PredicatePushdown())
|
|
161
|
-
|
|
162
|
-
|
|
163
156
|
def filter_irrelevant_ctes(
|
|
164
157
|
input: list[CTE],
|
|
165
158
|
root_cte: CTE,
|
|
@@ -233,6 +226,12 @@ def optimize_ctes(
|
|
|
233
226
|
input: list[CTE], root_cte: CTE, select: SelectStatement | MultiSelectStatement
|
|
234
227
|
):
|
|
235
228
|
complete = False
|
|
229
|
+
REGISTERED_RULES: list["OptimizationRule"] = []
|
|
230
|
+
|
|
231
|
+
if CONFIG.optimizations.datasource_inlining:
|
|
232
|
+
REGISTERED_RULES.append(InlineDatasource())
|
|
233
|
+
if CONFIG.optimizations.predicate_pushdown:
|
|
234
|
+
REGISTERED_RULES.append(PredicatePushdown())
|
|
236
235
|
|
|
237
236
|
while not complete:
|
|
238
237
|
actions_taken = False
|
|
@@ -10,6 +10,7 @@ from trilogy.core.enums import (
|
|
|
10
10
|
WindowType,
|
|
11
11
|
DatePart,
|
|
12
12
|
PurposeLineage,
|
|
13
|
+
ComparisonOperator,
|
|
13
14
|
)
|
|
14
15
|
from trilogy.core.models import (
|
|
15
16
|
ListType,
|
|
@@ -58,17 +59,23 @@ def INVALID_REFERENCE_STRING(x: Any, callsite: str = ""):
|
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
def window_factory(string: str, include_concept: bool = False) -> Callable:
|
|
61
|
-
def render_window(
|
|
62
|
+
def render_window(
|
|
63
|
+
concept: str, window: str, sort: str, offset: int | None = None
|
|
64
|
+
) -> str:
|
|
62
65
|
if not include_concept:
|
|
63
66
|
concept = ""
|
|
67
|
+
if offset:
|
|
68
|
+
base = f"{string}({concept}, {offset})"
|
|
69
|
+
else:
|
|
70
|
+
base = f"{string}({concept})"
|
|
64
71
|
if window and sort:
|
|
65
|
-
return f"{
|
|
72
|
+
return f"{base} over (partition by {window} order by {sort} )"
|
|
66
73
|
elif window:
|
|
67
|
-
return f"{
|
|
74
|
+
return f"{base} over (partition by {window})"
|
|
68
75
|
elif sort:
|
|
69
|
-
return f"{
|
|
76
|
+
return f"{base} over (order by {sort} )"
|
|
70
77
|
else:
|
|
71
|
-
return f"{
|
|
78
|
+
return f"{base} over ()"
|
|
72
79
|
|
|
73
80
|
return render_window
|
|
74
81
|
|
|
@@ -109,10 +116,10 @@ FUNCTION_MAP = {
|
|
|
109
116
|
FunctionType.INDEX_ACCESS: lambda x: f"{x[0]}[{x[1]}]",
|
|
110
117
|
FunctionType.UNNEST: lambda x: f"unnest({x[0]})",
|
|
111
118
|
# math
|
|
112
|
-
FunctionType.ADD: lambda x: f"
|
|
113
|
-
FunctionType.SUBTRACT: lambda x: f"
|
|
114
|
-
FunctionType.DIVIDE: lambda x: f"
|
|
115
|
-
FunctionType.MULTIPLY: lambda x: f"
|
|
119
|
+
FunctionType.ADD: lambda x: f"{x[0]} + {x[1]}",
|
|
120
|
+
FunctionType.SUBTRACT: lambda x: f"{x[0]} - {x[1]}",
|
|
121
|
+
FunctionType.DIVIDE: lambda x: f"{x[0]} / {x[1]}",
|
|
122
|
+
FunctionType.MULTIPLY: lambda x: f"{x[0]} * {x[1]}",
|
|
116
123
|
FunctionType.ROUND: lambda x: f"round({x[0]},{x[1]})",
|
|
117
124
|
FunctionType.MOD: lambda x: f"({x[0]} % {x[1]})",
|
|
118
125
|
# aggregate types
|
|
@@ -355,17 +362,30 @@ class BaseDialect:
|
|
|
355
362
|
lookup = lookup_cte.existence_source_map[e.right.address]
|
|
356
363
|
|
|
357
364
|
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} (select {lookup[0]}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} from {lookup[0]})"
|
|
358
|
-
elif isinstance(e.right, (ListWrapper, Parenthetical)):
|
|
365
|
+
elif isinstance(e.right, (ListWrapper, Parenthetical, list)):
|
|
359
366
|
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
360
|
-
|
|
367
|
+
|
|
368
|
+
elif isinstance(
|
|
369
|
+
e.right,
|
|
370
|
+
(
|
|
371
|
+
str,
|
|
372
|
+
int,
|
|
373
|
+
bool,
|
|
374
|
+
float,
|
|
375
|
+
),
|
|
376
|
+
):
|
|
361
377
|
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} ({self.render_expr(e.right, cte=cte, cte_map=cte_map)})"
|
|
362
378
|
else:
|
|
363
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value}
|
|
379
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
364
380
|
elif isinstance(e, Comparison):
|
|
381
|
+
if e.operator == ComparisonOperator.BETWEEN:
|
|
382
|
+
right_comp = e.right
|
|
383
|
+
assert isinstance(right_comp, Conditional)
|
|
384
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(right_comp.left, cte=cte, cte_map=cte_map) and self.render_expr(right_comp.right, cte=cte, cte_map=cte_map)}"
|
|
365
385
|
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
366
386
|
elif isinstance(e, Conditional):
|
|
367
387
|
# conditions need to be nested in parentheses
|
|
368
|
-
return f"
|
|
388
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
369
389
|
elif isinstance(e, WindowItem):
|
|
370
390
|
rendered_order_components = [
|
|
371
391
|
f"{self.render_expr(x.expr, cte, cte_map=cte_map)} {x.order.value}"
|
|
@@ -375,11 +395,11 @@ class BaseDialect:
|
|
|
375
395
|
self.render_expr(x, cte, cte_map=cte_map) for x in e.over
|
|
376
396
|
]
|
|
377
397
|
return f"{self.WINDOW_FUNCTION_MAP[e.type](concept = self.render_expr(e.content, cte=cte, cte_map=cte_map), window=','.join(rendered_over_components), sort=','.join(rendered_order_components))}" # noqa: E501
|
|
378
|
-
elif isinstance(e, FilterItem):
|
|
379
|
-
return f"CASE WHEN {self.render_expr(e.where.conditional, cte=cte, cte_map=cte_map)} THEN {self.render_expr(e.content, cte=cte, cte_map=cte_map)} ELSE 0 END"
|
|
380
398
|
elif isinstance(e, Parenthetical):
|
|
381
399
|
# conditions need to be nested in parentheses
|
|
382
|
-
|
|
400
|
+
if isinstance(e.content, list):
|
|
401
|
+
return f"( {','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e.content])} )"
|
|
402
|
+
return f"( {self.render_expr(e.content, cte=cte, cte_map=cte_map)} )"
|
|
383
403
|
elif isinstance(e, CaseWhen):
|
|
384
404
|
return f"WHEN {self.render_expr(e.comparison, cte=cte, cte_map=cte_map) } THEN {self.render_expr(e.expr, cte=cte, cte_map=cte_map) }"
|
|
385
405
|
elif isinstance(e, CaseElse):
|
|
@@ -412,7 +432,7 @@ class BaseDialect:
|
|
|
412
432
|
elif isinstance(e, ListWrapper):
|
|
413
433
|
return f"[{','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}]"
|
|
414
434
|
elif isinstance(e, list):
|
|
415
|
-
return f"{','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}"
|
|
435
|
+
return f"[{','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}]"
|
|
416
436
|
elif isinstance(e, DataType):
|
|
417
437
|
return str(e.value)
|
|
418
438
|
elif isinstance(e, DatePart):
|
|
@@ -41,7 +41,7 @@ def concept_list_to_keys(concepts: Tuple[Concept, ...]) -> Tuple[Concept, ...]:
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def constant_to_concept(
|
|
44
|
-
parent: ListWrapper | int | float | str,
|
|
44
|
+
parent: ListWrapper | list | int | float | str,
|
|
45
45
|
name: str,
|
|
46
46
|
namespace: str,
|
|
47
47
|
purpose: Purpose | None = None,
|
|
@@ -114,296 +114,17 @@ from trilogy.parsing.common import (
|
|
|
114
114
|
arbitrary_to_concept,
|
|
115
115
|
)
|
|
116
116
|
|
|
117
|
+
CONSTANT_TYPES = (int, float, str, bool, list, ListWrapper)
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
| select_statement
|
|
128
|
-
| persist_statement
|
|
129
|
-
| rowset_derivation_statement
|
|
130
|
-
| import_statement
|
|
131
|
-
| merge_statement
|
|
132
|
-
|
|
133
|
-
_TERMINATOR: ";"i /\s*/
|
|
134
|
-
|
|
135
|
-
comment: /#.*(\n|$)/ | /\/\/.*\n/
|
|
136
|
-
|
|
137
|
-
// property display_name string
|
|
138
|
-
concept_declaration: PURPOSE IDENTIFIER data_type concept_nullable_modifier? metadata?
|
|
139
|
-
//customer_id.property first_name STRING;
|
|
140
|
-
//<customer_id,country>.property local_alias STRING
|
|
141
|
-
concept_property_declaration: PROPERTY (prop_ident | IDENTIFIER) data_type concept_nullable_modifier? metadata?
|
|
142
|
-
//metric post_length <- len(post_text);
|
|
143
|
-
concept_derivation: (PURPOSE | AUTO | PROPERTY ) (prop_ident | IDENTIFIER) "<" "-" expr
|
|
144
|
-
|
|
145
|
-
rowset_derivation_statement: ("rowset"i IDENTIFIER "<" "-" (multi_select_statement | select_statement)) | ("with"i IDENTIFIER "as"i (multi_select_statement | select_statement))
|
|
146
|
-
|
|
147
|
-
constant_derivation: CONST IDENTIFIER "<" "-" literal
|
|
148
|
-
concept_nullable_modifier: "?"
|
|
149
|
-
concept: (concept_declaration | concept_derivation | concept_property_declaration | constant_derivation)
|
|
150
|
-
|
|
151
|
-
//concept property
|
|
152
|
-
prop_ident: "<" (IDENTIFIER ",")* IDENTIFIER ","? ">" "." IDENTIFIER
|
|
153
|
-
|
|
154
|
-
// datasource concepts
|
|
155
|
-
datasource: "datasource" IDENTIFIER "(" column_assignment_list ")" grain_clause? (address | query)
|
|
156
|
-
|
|
157
|
-
grain_clause: "grain" "(" column_list ")"
|
|
158
|
-
|
|
159
|
-
address: "address" ADDRESS
|
|
160
|
-
|
|
161
|
-
query: "query" MULTILINE_STRING
|
|
162
|
-
|
|
163
|
-
concept_assignment: IDENTIFIER | (MODIFIER "[" concept_assignment "]" ) | (SHORTHAND_MODIFIER concept_assignment )
|
|
164
|
-
|
|
165
|
-
column_assignment: ((IDENTIFIER | raw_column_assignment | _static_functions ) ":" concept_assignment)
|
|
166
|
-
|
|
167
|
-
raw_column_assignment: "raw" "(" MULTILINE_STRING ")"
|
|
168
|
-
|
|
169
|
-
column_assignment_list : (column_assignment "," )* column_assignment ","?
|
|
170
|
-
|
|
171
|
-
column_list : (IDENTIFIER "," )* IDENTIFIER ","?
|
|
172
|
-
|
|
173
|
-
import_statement: "import" (IDENTIFIER ".") * IDENTIFIER "as" IDENTIFIER
|
|
174
|
-
|
|
175
|
-
// persist_statement
|
|
176
|
-
persist_statement: "persist"i IDENTIFIER "into"i IDENTIFIER "from"i select_statement grain_clause?
|
|
177
|
-
|
|
178
|
-
// select statement
|
|
179
|
-
select_statement: "select"i select_list where? comment* order_by? comment* limit? comment*
|
|
180
|
-
|
|
181
|
-
// multiple_selects
|
|
182
|
-
multi_select_statement: select_statement ("merge" select_statement)+ "align"i align_clause where? comment* order_by? comment* limit? comment*
|
|
183
|
-
|
|
184
|
-
align_item: IDENTIFIER ":" IDENTIFIER ("," IDENTIFIER)* ","?
|
|
185
|
-
|
|
186
|
-
align_clause: align_item ("," align_item)* ","?
|
|
187
|
-
|
|
188
|
-
// merge statemment
|
|
189
|
-
merge_statement: "merge" IDENTIFIER ("," IDENTIFIER)* ","? comment*
|
|
190
|
-
|
|
191
|
-
// FUNCTION blocks
|
|
192
|
-
function: raw_function
|
|
193
|
-
function_binding_item: IDENTIFIER ":" data_type
|
|
194
|
-
function_binding_list: (function_binding_item ",")* function_binding_item ","?
|
|
195
|
-
raw_function: "bind" "sql" IDENTIFIER "(" function_binding_list ")" "-" ">" data_type "as"i MULTILINE_STRING
|
|
196
|
-
|
|
197
|
-
// user_id where state = Mexico
|
|
198
|
-
filter_item: "filter"i IDENTIFIER where
|
|
199
|
-
|
|
200
|
-
// rank/lag/lead
|
|
201
|
-
WINDOW_TYPE: ("row_number"i|"rank"i|"lag"i|"lead"i | "sum"i) /[\s]+/
|
|
202
|
-
|
|
203
|
-
window_item: WINDOW_TYPE (IDENTIFIER | select_transform | comment+ ) window_item_over? window_item_order?
|
|
204
|
-
|
|
205
|
-
window_item_over: ("OVER"i over_list)
|
|
206
|
-
|
|
207
|
-
window_item_order: ("ORDER"i? "BY"i order_list)
|
|
208
|
-
|
|
209
|
-
select_hide_modifier: "--"
|
|
210
|
-
select_partial_modifier: "~"
|
|
211
|
-
select_item: (select_hide_modifier | select_partial_modifier)? (IDENTIFIER | select_transform | comment+ )
|
|
212
|
-
|
|
213
|
-
select_list: ( select_item "," )* select_item ","?
|
|
214
|
-
|
|
215
|
-
// count(post_id) -> post_count
|
|
216
|
-
_assignment: ("-" ">") | "as"
|
|
217
|
-
select_transform : expr _assignment IDENTIFIER metadata?
|
|
218
|
-
|
|
219
|
-
metadata: "metadata" "(" IDENTIFIER "=" _string_lit ")"
|
|
220
|
-
|
|
221
|
-
limit: "LIMIT"i /[0-9]+/
|
|
222
|
-
|
|
223
|
-
!window_order: ("TOP"i | "BOTTOM"i)
|
|
224
|
-
|
|
225
|
-
window: window_order /[0-9]+/
|
|
226
|
-
|
|
227
|
-
window_order_by: "BY"i column_list
|
|
228
|
-
|
|
229
|
-
order_list: (expr ORDERING "," )* expr ORDERING ","?
|
|
230
|
-
|
|
231
|
-
over_list: (IDENTIFIER "," )* IDENTIFIER ","?
|
|
232
|
-
|
|
233
|
-
ORDERING: ("ASC"i | "DESC"i)
|
|
234
|
-
|
|
235
|
-
order_by: "ORDER"i "BY"i order_list
|
|
236
|
-
|
|
237
|
-
//WHERE STATEMENT
|
|
238
|
-
|
|
239
|
-
LOGICAL_OPERATOR: "AND"i | "OR"i
|
|
240
|
-
|
|
241
|
-
conditional: expr LOGICAL_OPERATOR (conditional | expr)
|
|
242
|
-
|
|
243
|
-
where: "WHERE"i (expr | conditional)
|
|
244
|
-
|
|
245
|
-
expr_reference: IDENTIFIER
|
|
246
|
-
|
|
247
|
-
!array_comparison: ( ("NOT"i "IN"i) | "IN"i)
|
|
248
|
-
|
|
249
|
-
COMPARISON_OPERATOR: (/is[\s]+not/ | "is" |"=" | ">" | "<" | ">=" | "<=" | "!=" )
|
|
250
|
-
|
|
251
|
-
comparison: (expr COMPARISON_OPERATOR expr)
|
|
252
|
-
|
|
253
|
-
subselect_comparison: expr array_comparison expr | (expr array_comparison expr_tuple)
|
|
254
|
-
|
|
255
|
-
expr_tuple: "(" (expr ",")* expr ","? ")"
|
|
256
|
-
|
|
257
|
-
//unnesting is a function
|
|
258
|
-
unnest: "UNNEST"i "(" expr ")"
|
|
259
|
-
//indexing into an expression is a function
|
|
260
|
-
index_access: expr "[" int_lit "]"
|
|
261
|
-
attr_access: expr "[" _string_lit "]"
|
|
262
|
-
|
|
263
|
-
parenthetical: "(" (conditional | expr) ")"
|
|
264
|
-
|
|
265
|
-
expr: window_item | filter_item | comparison | subselect_comparison | fgroup | aggregate_functions | unnest | _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions | literal | expr_reference | index_access | attr_access | parenthetical
|
|
266
|
-
|
|
267
|
-
// functions
|
|
268
|
-
|
|
269
|
-
fadd: ("add"i "(" expr "," expr ")" ) | ( expr "+" expr )
|
|
270
|
-
fsub: ("subtract"i "(" expr "," expr ")" ) | ( expr "-" expr )
|
|
271
|
-
fmul: ("multiply"i "(" expr "," expr ")" ) | ( expr "*" expr )
|
|
272
|
-
fdiv: ( "divide"i "(" expr "," expr ")") | ( expr "/" expr )
|
|
273
|
-
fmod: ( "mod"i "(" expr "," expr ")") | ( expr "%" expr )
|
|
274
|
-
fround: "round"i "(" expr "," expr ")"
|
|
275
|
-
fabs: "abs"i "(" expr ")"
|
|
276
|
-
|
|
277
|
-
_math_functions: fadd | fsub | fmul | fdiv | fround | fmod | fabs
|
|
278
|
-
|
|
279
|
-
//generic
|
|
280
|
-
fcast: "cast"i "(" expr "AS"i data_type ")"
|
|
281
|
-
concat: ("concat"i "(" (expr ",")* expr ")") | (expr "||" expr)
|
|
282
|
-
fcoalesce: "coalesce"i "(" (expr ",")* expr ")"
|
|
283
|
-
fcase_when: "WHEN"i (expr | conditional) "THEN"i expr
|
|
284
|
-
fcase_else: "ELSE"i expr
|
|
285
|
-
fcase: "CASE"i (fcase_when)* (fcase_else)? "END"i
|
|
286
|
-
len: "len"i "(" expr ")"
|
|
287
|
-
fnot: "NOT"i expr
|
|
288
|
-
|
|
289
|
-
_generic_functions: fcast | concat | fcoalesce | fcase | len | fnot
|
|
290
|
-
|
|
291
|
-
//constant
|
|
292
|
-
fcurrent_date: "current_date"i "(" ")"
|
|
293
|
-
fcurrent_datetime: "current_datetime"i "(" ")"
|
|
294
|
-
|
|
295
|
-
_constant_functions: fcurrent_date | fcurrent_datetime
|
|
296
|
-
|
|
297
|
-
//string
|
|
298
|
-
like: "like"i "(" expr "," _string_lit ")"
|
|
299
|
-
ilike: "ilike"i "(" expr "," _string_lit ")"
|
|
300
|
-
alt_like: expr "like"i expr
|
|
301
|
-
upper: "upper"i "(" expr ")"
|
|
302
|
-
lower: "lower"i "(" expr ")"
|
|
303
|
-
fsplit: "split"i "(" expr "," _string_lit ")"
|
|
304
|
-
fstrpos: "strpos"i "(" expr "," expr ")"
|
|
305
|
-
fsubstring: "substring"i "(" expr "," expr "," expr ")"
|
|
306
|
-
|
|
307
|
-
_string_functions: like | ilike | upper | lower | fsplit | fstrpos | fsubstring | alt_like
|
|
308
|
-
|
|
309
|
-
// special aggregate
|
|
310
|
-
fgroup: "group"i "(" expr ")" aggregate_over?
|
|
311
|
-
//aggregates
|
|
312
|
-
count: "count"i "(" expr ")"
|
|
313
|
-
count_distinct: "count_distinct"i "(" expr ")"
|
|
314
|
-
sum: "sum"i "(" expr ")"
|
|
315
|
-
avg: "avg"i "(" expr ")"
|
|
316
|
-
max: "max"i "(" expr ")"
|
|
317
|
-
min: "min"i "(" expr ")"
|
|
318
|
-
|
|
319
|
-
//aggregates can force a grain
|
|
320
|
-
aggregate_all: "*"
|
|
321
|
-
aggregate_over: ("BY"i (aggregate_all | over_list))
|
|
322
|
-
aggregate_functions: (count | count_distinct | sum | avg | max | min) aggregate_over?
|
|
323
|
-
|
|
324
|
-
// date functions
|
|
325
|
-
fdate: "date"i "(" expr ")"
|
|
326
|
-
fdatetime: "datetime"i "(" expr ")"
|
|
327
|
-
ftimestamp: "timestamp"i "(" expr ")"
|
|
328
|
-
|
|
329
|
-
fsecond: "second"i "(" expr ")"
|
|
330
|
-
fminute: "minute"i "(" expr ")"
|
|
331
|
-
fhour: "hour"i "(" expr ")"
|
|
332
|
-
fday: "day"i "(" expr ")"
|
|
333
|
-
fday_of_week: "day_of_week"i "(" expr ")"
|
|
334
|
-
fweek: "week"i "(" expr ")"
|
|
335
|
-
fmonth: "month"i "(" expr ")"
|
|
336
|
-
fquarter: "quarter"i "(" expr ")"
|
|
337
|
-
fyear: "year"i "(" expr ")"
|
|
338
|
-
|
|
339
|
-
DATE_PART: "DAY"i | "WEEK"i | "MONTH"i | "QUARTER"i | "YEAR"i | "MINUTE"i | "HOUR"i | "SECOND"i
|
|
340
|
-
fdate_trunc: "date_trunc"i "(" expr "," DATE_PART ")"
|
|
341
|
-
fdate_part: "date_part"i "(" expr "," DATE_PART ")"
|
|
342
|
-
fdate_add: "date_add"i "(" expr "," DATE_PART "," int_lit ")"
|
|
343
|
-
fdate_diff: "date_diff"i "(" expr "," expr "," DATE_PART ")"
|
|
344
|
-
|
|
345
|
-
_date_functions: fdate | fdate_add | fdate_diff | fdatetime | ftimestamp | fsecond | fminute | fhour | fday | fday_of_week | fweek | fmonth | fquarter | fyear | fdate_part | fdate_trunc
|
|
346
|
-
|
|
347
|
-
_static_functions: _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions
|
|
348
|
-
|
|
349
|
-
// base language constructs
|
|
350
|
-
IDENTIFIER: /[a-zA-Z_][a-zA-Z0-9_\\-\\.\-]*/
|
|
351
|
-
ADDRESS: /[a-zA-Z_][a-zA-Z0-9_\\-\\.\-\*]*/ | /`[a-zA-Z_][a-zA-Z0-9_\\-\\.\-\*]*`/
|
|
352
|
-
|
|
353
|
-
MULTILINE_STRING: /\'{3}(.*?)\'{3}/s
|
|
354
|
-
|
|
355
|
-
DOUBLE_STRING_CHARS: /(?:(?!\${)([^"\\]|\\.))+/+ // any character except "
|
|
356
|
-
SINGLE_STRING_CHARS: /(?:(?!\${)([^'\\]|\\.))+/+ // any character except '
|
|
357
|
-
_single_quote: "'" ( SINGLE_STRING_CHARS )* "'"
|
|
358
|
-
_double_quote: "\"" ( DOUBLE_STRING_CHARS )* "\""
|
|
359
|
-
_string_lit: _single_quote | _double_quote
|
|
360
|
-
|
|
361
|
-
MINUS: "-"
|
|
362
|
-
|
|
363
|
-
int_lit: MINUS? /[0-9]+/
|
|
364
|
-
|
|
365
|
-
float_lit: /[0-9]*\.[0-9]+/
|
|
366
|
-
|
|
367
|
-
array_lit: "[" (literal ",")* literal ","? "]"()
|
|
368
|
-
|
|
369
|
-
!bool_lit: "True"i | "False"i
|
|
370
|
-
|
|
371
|
-
!null_lit: "null"i
|
|
372
|
-
|
|
373
|
-
literal: _string_lit | int_lit | float_lit | bool_lit | null_lit | array_lit
|
|
374
|
-
|
|
375
|
-
MODIFIER: "Optional"i | "Partial"i | "Nullable"i
|
|
376
|
-
|
|
377
|
-
SHORTHAND_MODIFIER: "~"
|
|
378
|
-
|
|
379
|
-
struct_type: "struct" "<" ((data_type | IDENTIFIER) ",")* (data_type | IDENTIFIER) ","? ">"
|
|
380
|
-
|
|
381
|
-
list_type: "list" "<" data_type ">"
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
!data_type: "string"i | "number"i | "numeric"i | "map"i | "list"i | "array"i | "any"i | "int"i | "bigint" | "date"i | "datetime"i | "timestamp"i | "float"i | "bool"i | struct_type | list_type
|
|
385
|
-
|
|
386
|
-
PURPOSE: "key"i | "metric"i | "const"i | "constant"i
|
|
387
|
-
PROPERTY: "property"i
|
|
388
|
-
CONST: "const"i | "constant"i
|
|
389
|
-
AUTO: "AUTO"i
|
|
390
|
-
|
|
391
|
-
// meta functions
|
|
392
|
-
CONCEPTS: "CONCEPTS"i
|
|
393
|
-
DATASOURCES: "DATASOURCES"i
|
|
394
|
-
|
|
395
|
-
show_category: CONCEPTS | DATASOURCES
|
|
396
|
-
|
|
397
|
-
show_statement: "show"i ( show_category | select_statement | persist_statement) _TERMINATOR
|
|
398
|
-
|
|
399
|
-
%import common.WS_INLINE -> _WHITESPACE
|
|
400
|
-
%import common.WS
|
|
401
|
-
%ignore WS
|
|
402
|
-
""" # noqa: E501
|
|
403
|
-
|
|
404
|
-
PARSER = Lark(
|
|
405
|
-
grammar, start="start", propagate_positions=True, g_regex_flags=IGNORECASE
|
|
406
|
-
)
|
|
119
|
+
with open(join(dirname(__file__), "trilogy.lark"), "r") as f:
|
|
120
|
+
PARSER = Lark(
|
|
121
|
+
f.read(),
|
|
122
|
+
start="start",
|
|
123
|
+
propagate_positions=True,
|
|
124
|
+
g_regex_flags=IGNORECASE,
|
|
125
|
+
parser="lalr",
|
|
126
|
+
cache=True,
|
|
127
|
+
)
|
|
407
128
|
|
|
408
129
|
|
|
409
130
|
def parse_concept_reference(
|
|
@@ -567,6 +288,9 @@ class ParseToObjects(Transformer):
|
|
|
567
288
|
def IDENTIFIER(self, args) -> str:
|
|
568
289
|
return args.value
|
|
569
290
|
|
|
291
|
+
def concept_lit(self, args) -> Concept:
|
|
292
|
+
return self.environment.concepts.__getitem__(args[0])
|
|
293
|
+
|
|
570
294
|
def ADDRESS(self, args) -> str:
|
|
571
295
|
return args.value
|
|
572
296
|
|
|
@@ -622,19 +346,23 @@ class ParseToObjects(Transformer):
|
|
|
622
346
|
def column_assignment(self, meta: Meta, args):
|
|
623
347
|
# TODO -> deal with conceptual modifiers
|
|
624
348
|
modifiers = []
|
|
625
|
-
|
|
349
|
+
alias = args[0]
|
|
350
|
+
concept_list = args[1]
|
|
626
351
|
# recursively collect modifiers
|
|
627
|
-
|
|
628
|
-
modifiers
|
|
629
|
-
|
|
352
|
+
if len(concept_list) > 1:
|
|
353
|
+
modifiers += concept_list[:-1]
|
|
354
|
+
concept = concept_list[-1]
|
|
630
355
|
resolved = self.environment.concepts.__getitem__( # type: ignore
|
|
631
|
-
key=concept
|
|
356
|
+
key=concept, line_no=meta.line
|
|
632
357
|
)
|
|
633
|
-
return ColumnAssignment(alias=
|
|
358
|
+
return ColumnAssignment(alias=alias, modifiers=modifiers, concept=resolved)
|
|
634
359
|
|
|
635
360
|
def _TERMINATOR(self, args):
|
|
636
361
|
return None
|
|
637
362
|
|
|
363
|
+
def _static_functions(self, args):
|
|
364
|
+
return args[0]
|
|
365
|
+
|
|
638
366
|
def MODIFIER(self, args) -> Modifier:
|
|
639
367
|
return Modifier(args.value)
|
|
640
368
|
|
|
@@ -893,6 +621,9 @@ class ParseToObjects(Transformer):
|
|
|
893
621
|
assert len(args) == 1
|
|
894
622
|
return Comment(text=args[0].value)
|
|
895
623
|
|
|
624
|
+
def PARSE_COMMENT(self, args):
|
|
625
|
+
return Comment(text=args.value)
|
|
626
|
+
|
|
896
627
|
@v_args(meta=True)
|
|
897
628
|
def select_transform(self, meta, args) -> ConceptTransform:
|
|
898
629
|
|
|
@@ -962,7 +693,7 @@ class ParseToObjects(Transformer):
|
|
|
962
693
|
if isinstance(content, ConceptTransform):
|
|
963
694
|
return SelectItem(content=content, modifiers=modifiers)
|
|
964
695
|
return SelectItem(
|
|
965
|
-
content=
|
|
696
|
+
content=content,
|
|
966
697
|
modifiers=modifiers,
|
|
967
698
|
)
|
|
968
699
|
|
|
@@ -972,8 +703,12 @@ class ParseToObjects(Transformer):
|
|
|
972
703
|
def limit(self, args):
|
|
973
704
|
return Limit(count=int(args[0].value))
|
|
974
705
|
|
|
975
|
-
def
|
|
976
|
-
|
|
706
|
+
def ordering(self, args: list[str]):
|
|
707
|
+
base = args[0].lower()
|
|
708
|
+
if len(args) > 1:
|
|
709
|
+
null_sort = args[-1]
|
|
710
|
+
return Ordering(" ".join([base, "nulls", null_sort.lower()]))
|
|
711
|
+
return Ordering(base)
|
|
977
712
|
|
|
978
713
|
def order_list(self, args):
|
|
979
714
|
|
|
@@ -1001,7 +736,7 @@ class ParseToObjects(Transformer):
|
|
|
1001
736
|
return OrderBy(items=args[0])
|
|
1002
737
|
|
|
1003
738
|
def over_list(self, args):
|
|
1004
|
-
return [
|
|
739
|
+
return [x for x in args]
|
|
1005
740
|
|
|
1006
741
|
@v_args(meta=True)
|
|
1007
742
|
def merge_statement(self, meta: Meta, args) -> MergeStatement:
|
|
@@ -1044,9 +779,7 @@ class ParseToObjects(Transformer):
|
|
|
1044
779
|
# add the parsed objects of the import in
|
|
1045
780
|
self.parsed = {**self.parsed, **nparser.parsed}
|
|
1046
781
|
except Exception as e:
|
|
1047
|
-
raise ImportError(
|
|
1048
|
-
f"Unable to import file {dirname(target)}, parsing error: {e}"
|
|
1049
|
-
)
|
|
782
|
+
raise ImportError(f"Unable to import file {target}, parsing error: {e}")
|
|
1050
783
|
|
|
1051
784
|
for _, concept in nparser.environment.concepts.items():
|
|
1052
785
|
self.environment.add_concept(concept.with_namespace(alias))
|
|
@@ -1241,6 +974,19 @@ class ParseToObjects(Transformer):
|
|
|
1241
974
|
raise SyntaxError
|
|
1242
975
|
return Comparison(left=args[0], right=args[2], operator=args[1])
|
|
1243
976
|
|
|
977
|
+
def between_comparison(self, args) -> Conditional:
|
|
978
|
+
left_bound = args[1]
|
|
979
|
+
right_bound = args[2]
|
|
980
|
+
return Conditional(
|
|
981
|
+
left=Comparison(
|
|
982
|
+
left=args[0], right=left_bound, operator=ComparisonOperator.GTE
|
|
983
|
+
),
|
|
984
|
+
right=Comparison(
|
|
985
|
+
left=args[0], right=right_bound, operator=ComparisonOperator.LTE
|
|
986
|
+
),
|
|
987
|
+
operator=BooleanOperator.AND,
|
|
988
|
+
)
|
|
989
|
+
|
|
1244
990
|
@v_args(meta=True)
|
|
1245
991
|
def subselect_comparison(self, meta: Meta, args) -> SubselectComparison:
|
|
1246
992
|
right = args[2]
|
|
@@ -1297,13 +1043,23 @@ class ParseToObjects(Transformer):
|
|
|
1297
1043
|
type = args[0]
|
|
1298
1044
|
order_by = []
|
|
1299
1045
|
over = []
|
|
1300
|
-
|
|
1301
|
-
|
|
1046
|
+
index = None
|
|
1047
|
+
concept: Concept | None = None
|
|
1048
|
+
for item in args:
|
|
1049
|
+
if isinstance(item, int):
|
|
1050
|
+
index = item
|
|
1051
|
+
elif isinstance(item, WindowItemOrder):
|
|
1302
1052
|
order_by = item.contents
|
|
1303
1053
|
elif isinstance(item, WindowItemOver):
|
|
1304
1054
|
over = item.contents
|
|
1305
|
-
|
|
1306
|
-
|
|
1055
|
+
elif isinstance(item, str):
|
|
1056
|
+
concept = self.environment.concepts[item]
|
|
1057
|
+
elif isinstance(item, Concept):
|
|
1058
|
+
concept = item
|
|
1059
|
+
assert concept
|
|
1060
|
+
return WindowItem(
|
|
1061
|
+
type=type, content=concept, over=over, order_by=order_by, index=index
|
|
1062
|
+
)
|
|
1307
1063
|
|
|
1308
1064
|
def filter_item(self, args) -> FilterItem:
|
|
1309
1065
|
where: WhereClause
|
|
@@ -1501,6 +1257,9 @@ class ParseToObjects(Transformer):
|
|
|
1501
1257
|
)
|
|
1502
1258
|
return SubString(args)
|
|
1503
1259
|
|
|
1260
|
+
def logical_operator(self, args):
|
|
1261
|
+
return BooleanOperator(args[0].value.lower())
|
|
1262
|
+
|
|
1504
1263
|
@v_args(meta=True)
|
|
1505
1264
|
def lower(self, meta, args):
|
|
1506
1265
|
args = self.process_function_args(args, meta=meta)
|
|
@@ -1893,12 +1652,12 @@ class ParseToObjects(Transformer):
|
|
|
1893
1652
|
@v_args(meta=True)
|
|
1894
1653
|
def fcurrent_date(self, meta, args):
|
|
1895
1654
|
args = self.process_function_args(args, meta=meta)
|
|
1896
|
-
return CurrentDate(
|
|
1655
|
+
return CurrentDate([])
|
|
1897
1656
|
|
|
1898
1657
|
@v_args(meta=True)
|
|
1899
1658
|
def fcurrent_datetime(self, meta, args):
|
|
1900
1659
|
args = self.process_function_args(args, meta=meta)
|
|
1901
|
-
return CurrentDatetime(
|
|
1660
|
+
return CurrentDatetime([])
|
|
1902
1661
|
|
|
1903
1662
|
@v_args(meta=True)
|
|
1904
1663
|
def fnot(self, meta, args):
|
|
@@ -1914,10 +1673,14 @@ def unpack_visit_error(e: VisitError):
|
|
|
1914
1673
|
elif isinstance(e.orig_exc, (UndefinedConceptException, ImportError)):
|
|
1915
1674
|
raise e.orig_exc
|
|
1916
1675
|
elif isinstance(e.orig_exc, (ValidationError, TypeError)):
|
|
1917
|
-
raise InvalidSyntaxException(str(e.orig_exc))
|
|
1676
|
+
raise InvalidSyntaxException(str(e.orig_exc) + str(e.rule) + str(e.obj))
|
|
1918
1677
|
raise e
|
|
1919
1678
|
|
|
1920
1679
|
|
|
1680
|
+
def parse_text_raw(text: str, environment: Optional[Environment] = None):
|
|
1681
|
+
PARSER.parse(text)
|
|
1682
|
+
|
|
1683
|
+
|
|
1921
1684
|
def parse_text(text: str, environment: Optional[Environment] = None) -> Tuple[
|
|
1922
1685
|
Environment,
|
|
1923
1686
|
List[
|
|
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.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/concept_strategies_v3.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/__init__.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/basic_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/common.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/group_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/group_to_node.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/rowset_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/select_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/unnest_node.py
RENAMED
|
File without changes
|
{pytrilogy-0.0.1.111 → pytrilogy-0.0.1.113}/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
|
|
File without changes
|