pytrilogy 0.0.1.116__py3-none-any.whl → 0.0.1.118__py3-none-any.whl
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.116.dist-info → pytrilogy-0.0.1.118.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.1.116.dist-info → pytrilogy-0.0.1.118.dist-info}/RECORD +17 -17
- trilogy/__init__.py +1 -1
- trilogy/core/functions.py +15 -4
- trilogy/core/models.py +102 -11
- trilogy/core/optimization.py +19 -19
- trilogy/core/optimizations/predicate_pushdown.py +22 -5
- trilogy/dialect/base.py +5 -0
- trilogy/dialect/presto.py +3 -1
- trilogy/executor.py +29 -0
- trilogy/parsing/parse_engine.py +12 -2
- trilogy/parsing/render.py +5 -0
- trilogy/parsing/trilogy.lark +5 -3
- {pytrilogy-0.0.1.116.dist-info → pytrilogy-0.0.1.118.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.1.116.dist-info → pytrilogy-0.0.1.118.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.1.116.dist-info → pytrilogy-0.0.1.118.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.1.116.dist-info → pytrilogy-0.0.1.118.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=fYN8J2EkHXTX5cKVWpbBMQHilscbkhmZF-pMLoMqHFc,292
|
|
2
2
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
trilogy/constants.py,sha256=u2dNxhwy0v-6HrvG1GcpDVvuhzdTH5fuyYNCxDPlr2E,770
|
|
4
4
|
trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
|
|
5
|
-
trilogy/executor.py,sha256=
|
|
5
|
+
trilogy/executor.py,sha256=5cRbU4Rj7p1pNV76rfp1pz704Hx_0q8_O8HFURjgXxQ,11016
|
|
6
6
|
trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
|
|
7
7
|
trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
trilogy/utility.py,sha256=zM__8r29EsyDW7K9VOHz8yvZC2bXFzh7xKy3cL7GKsk,707
|
|
@@ -13,17 +13,17 @@ trilogy/core/env_processor.py,sha256=SU-jpaGfoWLe9sGTeQYG1qjVnwGQ7TwctmnJRlfzluc
|
|
|
13
13
|
trilogy/core/environment_helpers.py,sha256=mzBDHhdF9ssZ_-LY8CcaM_ddfJavkpRYrFImUd3cjXI,5972
|
|
14
14
|
trilogy/core/ergonomics.py,sha256=w3gwXdgrxNHCuaRdyKg73t6F36tj-wIjQf47WZkHmJk,1465
|
|
15
15
|
trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,561
|
|
16
|
-
trilogy/core/functions.py,sha256=
|
|
16
|
+
trilogy/core/functions.py,sha256=fWuuPOeWRAiukTkXvTEu_NCSbu6eUmogtLGpC4ey2FI,9465
|
|
17
17
|
trilogy/core/graph_models.py,sha256=oJUMSpmYhqXlavckHLpR07GJxuQ8dZ1VbB1fB0KaS8c,2036
|
|
18
18
|
trilogy/core/internal.py,sha256=jNGFHKENnbMiMCtAgsnLZYVSENDK4b5ALecXFZpTDzQ,1075
|
|
19
|
-
trilogy/core/models.py,sha256=
|
|
20
|
-
trilogy/core/optimization.py,sha256=
|
|
19
|
+
trilogy/core/models.py,sha256=Mp6MB8Gl2CZcPDWkofPX8Mym1ZvT0OUS0dKAUWCj7K8,116419
|
|
20
|
+
trilogy/core/optimization.py,sha256=oM3Ry7UpbpTSm2xNkmWx70OHd2V2vWRjM72sZpsZfb8,4116
|
|
21
21
|
trilogy/core/query_processor.py,sha256=clIRJ6IcsqIVBPKFsxt8bqCLsLyajvAu02MUIcKQhTo,15713
|
|
22
22
|
trilogy/core/optimizations/__init__.py,sha256=pxRzNzd2g8oRMy4f_ub5va6bNS2pd4hnyp9JBzTKc1E,300
|
|
23
23
|
trilogy/core/optimizations/base_optimization.py,sha256=tWWT-xnTbnEU-mNi_isMNbywm8B9WTRsNFwGpeh3rqE,468
|
|
24
24
|
trilogy/core/optimizations/inline_constant.py,sha256=neZOFjX7M2pzQ-8m-f8nApy_MfJuowX6SzcGwGFt5w4,927
|
|
25
25
|
trilogy/core/optimizations/inline_datasource.py,sha256=BSp54fwF4RRwInd-09pggemC7JuXj-uqGzi32ufeqYo,2171
|
|
26
|
-
trilogy/core/optimizations/predicate_pushdown.py,sha256=
|
|
26
|
+
trilogy/core/optimizations/predicate_pushdown.py,sha256=sIojWvoYp6k_ANCyVqxCpEyLY_GLmzsG-Sghj0cbk3k,4135
|
|
27
27
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
trilogy/core/processing/concept_strategies_v3.py,sha256=MYrpNMidqvPOg123RekOcqVTjcj03i_538gBo0MzoWE,23432
|
|
29
29
|
trilogy/core/processing/graph_utils.py,sha256=ulCJ4hYAISbUxLD6VM2fah9RBPGIXSEHEPeRBSFl0Rs,1197
|
|
@@ -50,14 +50,14 @@ trilogy/core/processing/nodes/select_node_v2.py,sha256=ERCflBFzKpD5SzweMevnJLyQn
|
|
|
50
50
|
trilogy/core/processing/nodes/unnest_node.py,sha256=JFtm90IVM-46aCYkTNIaJah6v9ApAfonjVhcVM1HmDE,1903
|
|
51
51
|
trilogy/core/processing/nodes/window_node.py,sha256=X7qxLUKd3tekjUUsmH_4vz5b-U89gMnGd04VBxuu2Ns,1280
|
|
52
52
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
-
trilogy/dialect/base.py,sha256=
|
|
53
|
+
trilogy/dialect/base.py,sha256=ycsxbUL68DsodOsHEgEoNLKMn5vgRN3sDIRfiH9fQDs,31719
|
|
54
54
|
trilogy/dialect/bigquery.py,sha256=9vxQn2BMv_oTGQSWQpoN5ho_OgqMWaHH9e-5vQVf44c,2906
|
|
55
55
|
trilogy/dialect/common.py,sha256=zWrYmvevlXznocw9uGHmY5Ws1rp_kICm9zA_ulTe4eg,2165
|
|
56
56
|
trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
|
|
57
57
|
trilogy/dialect/duckdb.py,sha256=Ddyt68sr8IL2HnZMenyytoD65FXwY_O2pz1McyS0bis,3075
|
|
58
58
|
trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
|
|
59
59
|
trilogy/dialect/postgres.py,sha256=r47xbCA7nfEYENofiVfLZ-SnReNfDmUmW4OSHVkkP4E,3206
|
|
60
|
-
trilogy/dialect/presto.py,sha256=
|
|
60
|
+
trilogy/dialect/presto.py,sha256=UxBodRiV3szpFcQlcjoJaGXEwAhZJf_OT7dHczYvO80,3092
|
|
61
61
|
trilogy/dialect/snowflake.py,sha256=N3HknYgN-fjD7BLX1Ucj-ss_ku2Ox8DgLsF3BIHutHo,2941
|
|
62
62
|
trilogy/dialect/sql_server.py,sha256=HX68vNTrcDaTnOxe6Zbx_PBgrO42e2VuThxO6CYQ2cY,3026
|
|
63
63
|
trilogy/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -70,14 +70,14 @@ trilogy/parsing/common.py,sha256=iR3fiiZ7w8VJuUGrQ0v06XGDXov81f4z1ZlFnj6y40E,580
|
|
|
70
70
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
71
71
|
trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
|
|
72
72
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
73
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
74
|
-
trilogy/parsing/render.py,sha256=
|
|
75
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
73
|
+
trilogy/parsing/parse_engine.py,sha256=F1ok96qT6EhKRKV1Q_YzfHxMFtNV8qAXopK8NaePgU4,57080
|
|
74
|
+
trilogy/parsing/render.py,sha256=TnLf5fg4wimpd9EvhLU-FMDwpyW9pesoedBZ0RrmWD4,11810
|
|
75
|
+
trilogy/parsing/trilogy.lark,sha256=1AZbQGpNmpm4KamAXA5IWcuOr2B8Gb8kUJcAOmKf_zY,10862
|
|
76
76
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
77
|
trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
|
|
78
|
-
pytrilogy-0.0.1.
|
|
79
|
-
pytrilogy-0.0.1.
|
|
80
|
-
pytrilogy-0.0.1.
|
|
81
|
-
pytrilogy-0.0.1.
|
|
82
|
-
pytrilogy-0.0.1.
|
|
83
|
-
pytrilogy-0.0.1.
|
|
78
|
+
pytrilogy-0.0.1.118.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
79
|
+
pytrilogy-0.0.1.118.dist-info/METADATA,sha256=tZ1kQcOTJ5JTYb5tqlfdo7g-o94TC0tKmcdqLtJ35bI,7878
|
|
80
|
+
pytrilogy-0.0.1.118.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
|
81
|
+
pytrilogy-0.0.1.118.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
82
|
+
pytrilogy-0.0.1.118.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
83
|
+
pytrilogy-0.0.1.118.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/core/functions.py
CHANGED
|
@@ -9,6 +9,7 @@ from trilogy.core.models import (
|
|
|
9
9
|
ListType,
|
|
10
10
|
StructType,
|
|
11
11
|
MapType,
|
|
12
|
+
NumericType,
|
|
12
13
|
)
|
|
13
14
|
from trilogy.core.enums import FunctionType, Purpose, Granularity, DatePart
|
|
14
15
|
from trilogy.core.exceptions import InvalidSyntaxException
|
|
@@ -21,7 +22,9 @@ def create_function_derived_concept(
|
|
|
21
22
|
namespace: str,
|
|
22
23
|
operator: FunctionType,
|
|
23
24
|
arguments: list[Concept],
|
|
24
|
-
output_type: Optional[
|
|
25
|
+
output_type: Optional[
|
|
26
|
+
DataType | ListType | StructType | MapType | NumericType
|
|
27
|
+
] = None,
|
|
25
28
|
output_purpose: Optional[Purpose] = None,
|
|
26
29
|
) -> Concept:
|
|
27
30
|
purpose = (
|
|
@@ -49,16 +52,24 @@ def argument_to_purpose(arg) -> Purpose:
|
|
|
49
52
|
if isinstance(arg, Function):
|
|
50
53
|
return arg.output_purpose
|
|
51
54
|
elif isinstance(arg, AggregateWrapper):
|
|
55
|
+
base = arg.function.output_purpose
|
|
56
|
+
if arg.by and base == Purpose.METRIC:
|
|
57
|
+
return Purpose.PROPERTY
|
|
52
58
|
return arg.function.output_purpose
|
|
53
59
|
elif isinstance(arg, Parenthetical):
|
|
54
60
|
return argument_to_purpose(arg.content)
|
|
55
61
|
elif isinstance(arg, WindowItem):
|
|
56
62
|
return Purpose.PROPERTY
|
|
57
63
|
elif isinstance(arg, Concept):
|
|
64
|
+
base = arg.purpose
|
|
65
|
+
if (
|
|
66
|
+
isinstance(arg.lineage, AggregateWrapper)
|
|
67
|
+
and arg.lineage.by
|
|
68
|
+
and base == Purpose.METRIC
|
|
69
|
+
):
|
|
70
|
+
return Purpose.PROPERTY
|
|
58
71
|
return arg.purpose
|
|
59
|
-
elif isinstance(arg, (int, float, str, bool, list)):
|
|
60
|
-
return Purpose.CONSTANT
|
|
61
|
-
elif isinstance(arg, DataType):
|
|
72
|
+
elif isinstance(arg, (int, float, str, bool, list, NumericType, DataType)):
|
|
62
73
|
return Purpose.CONSTANT
|
|
63
74
|
elif isinstance(arg, DatePart):
|
|
64
75
|
return Purpose.CONSTANT
|
trilogy/core/models.py
CHANGED
|
@@ -105,7 +105,9 @@ def get_concept_arguments(expr) -> List["Concept"]:
|
|
|
105
105
|
return output
|
|
106
106
|
|
|
107
107
|
|
|
108
|
-
ALL_TYPES = Union[
|
|
108
|
+
ALL_TYPES = Union[
|
|
109
|
+
"DataType", "MapType", "ListType", "NumericType", "StructType", "Concept"
|
|
110
|
+
]
|
|
109
111
|
|
|
110
112
|
NAMESPACED_TYPES = Union[
|
|
111
113
|
"WindowItem",
|
|
@@ -147,6 +149,11 @@ class SelectGrain(ABC):
|
|
|
147
149
|
raise NotImplementedError
|
|
148
150
|
|
|
149
151
|
|
|
152
|
+
class ConstantInlineable(ABC):
|
|
153
|
+
def inline_concept(self, concept: Concept):
|
|
154
|
+
raise NotImplementedError
|
|
155
|
+
|
|
156
|
+
|
|
150
157
|
class DataType(Enum):
|
|
151
158
|
# PRIMITIVES
|
|
152
159
|
STRING = "string"
|
|
@@ -176,6 +183,19 @@ class DataType(Enum):
|
|
|
176
183
|
return self
|
|
177
184
|
|
|
178
185
|
|
|
186
|
+
class NumericType(BaseModel):
|
|
187
|
+
precision: int = 20
|
|
188
|
+
scale: int = 5
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def data_type(self):
|
|
192
|
+
return DataType.NUMERIC
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def value(self):
|
|
196
|
+
return self.data_type.value
|
|
197
|
+
|
|
198
|
+
|
|
179
199
|
class ListType(BaseModel):
|
|
180
200
|
model_config = ConfigDict(frozen=True)
|
|
181
201
|
type: ALL_TYPES
|
|
@@ -192,7 +212,9 @@ class ListType(BaseModel):
|
|
|
192
212
|
return self.data_type.value
|
|
193
213
|
|
|
194
214
|
@property
|
|
195
|
-
def value_data_type(
|
|
215
|
+
def value_data_type(
|
|
216
|
+
self,
|
|
217
|
+
) -> DataType | StructType | MapType | ListType | NumericType:
|
|
196
218
|
if isinstance(self.type, Concept):
|
|
197
219
|
return self.type.datatype
|
|
198
220
|
return self.type
|
|
@@ -270,7 +292,7 @@ def empty_grain() -> Grain:
|
|
|
270
292
|
|
|
271
293
|
class Concept(Namespaced, SelectGrain, BaseModel):
|
|
272
294
|
name: str
|
|
273
|
-
datatype: DataType | ListType | StructType | MapType
|
|
295
|
+
datatype: DataType | ListType | StructType | MapType | NumericType
|
|
274
296
|
purpose: Purpose
|
|
275
297
|
metadata: Optional[Metadata] = Field(
|
|
276
298
|
default_factory=lambda: Metadata(description=None, line_number=None),
|
|
@@ -790,12 +812,12 @@ class LooseConceptList(BaseModel):
|
|
|
790
812
|
class Function(Namespaced, SelectGrain, BaseModel):
|
|
791
813
|
operator: FunctionType
|
|
792
814
|
arg_count: int = Field(default=1)
|
|
793
|
-
output_datatype: DataType | ListType | StructType | MapType
|
|
815
|
+
output_datatype: DataType | ListType | StructType | MapType | NumericType
|
|
794
816
|
output_purpose: Purpose
|
|
795
817
|
valid_inputs: Optional[
|
|
796
818
|
Union[
|
|
797
|
-
Set[DataType | ListType | StructType],
|
|
798
|
-
List[Set[DataType | ListType | StructType]],
|
|
819
|
+
Set[DataType | ListType | StructType | NumericType],
|
|
820
|
+
List[Set[DataType | ListType | StructType] | NumericType],
|
|
799
821
|
]
|
|
800
822
|
] = None
|
|
801
823
|
arguments: Sequence[
|
|
@@ -808,6 +830,7 @@ class Function(Namespaced, SelectGrain, BaseModel):
|
|
|
808
830
|
str,
|
|
809
831
|
DataType,
|
|
810
832
|
ListType,
|
|
833
|
+
NumericType,
|
|
811
834
|
DatePart,
|
|
812
835
|
"Parenthetical",
|
|
813
836
|
CaseWhen,
|
|
@@ -1499,7 +1522,7 @@ class DatasourceMetadata(BaseModel):
|
|
|
1499
1522
|
|
|
1500
1523
|
class MergeStatement(Namespaced, BaseModel):
|
|
1501
1524
|
concepts: List[Concept]
|
|
1502
|
-
datatype: DataType | ListType | StructType | MapType
|
|
1525
|
+
datatype: DataType | ListType | StructType | MapType | NumericType
|
|
1503
1526
|
|
|
1504
1527
|
@cached_property
|
|
1505
1528
|
def concepts_lcl(self):
|
|
@@ -2057,6 +2080,9 @@ class CTE(BaseModel):
|
|
|
2057
2080
|
if concept.address in self.source_map:
|
|
2058
2081
|
removed = removed.union(self.source_map[concept.address])
|
|
2059
2082
|
del self.source_map[concept.address]
|
|
2083
|
+
|
|
2084
|
+
if self.condition:
|
|
2085
|
+
self.condition = self.condition.inline_constant(concept)
|
|
2060
2086
|
# if we've entirely removed the need to join to someplace to get the concept
|
|
2061
2087
|
# drop the join as well.
|
|
2062
2088
|
for removed_cte in removed:
|
|
@@ -2745,7 +2771,7 @@ class LazyEnvironment(Environment):
|
|
|
2745
2771
|
return super().__getattribute__(name)
|
|
2746
2772
|
|
|
2747
2773
|
|
|
2748
|
-
class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
2774
|
+
class Comparison(ConceptArgs, Namespaced, ConstantInlineable, SelectGrain, BaseModel):
|
|
2749
2775
|
left: Union[
|
|
2750
2776
|
int,
|
|
2751
2777
|
str,
|
|
@@ -2807,6 +2833,32 @@ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
|
2807
2833
|
def __str__(self):
|
|
2808
2834
|
return self.__repr__()
|
|
2809
2835
|
|
|
2836
|
+
def inline_constant(self, constant: Concept) -> "Comparison":
|
|
2837
|
+
assert isinstance(constant.lineage, Function)
|
|
2838
|
+
new_val = constant.lineage.arguments[0]
|
|
2839
|
+
if isinstance(self.left, ConstantInlineable):
|
|
2840
|
+
new_left = self.left.inline_constant(constant)
|
|
2841
|
+
elif self.left == constant:
|
|
2842
|
+
new_left = new_val
|
|
2843
|
+
else:
|
|
2844
|
+
new_left = self.left
|
|
2845
|
+
|
|
2846
|
+
if isinstance(self.right, ConstantInlineable):
|
|
2847
|
+
new_right = self.right.inline_constant(constant)
|
|
2848
|
+
elif self.right == constant:
|
|
2849
|
+
new_right = new_val
|
|
2850
|
+
else:
|
|
2851
|
+
new_right = self.right
|
|
2852
|
+
|
|
2853
|
+
if self.right == constant:
|
|
2854
|
+
new_right = new_val
|
|
2855
|
+
|
|
2856
|
+
return Comparison(
|
|
2857
|
+
left=new_left,
|
|
2858
|
+
right=new_right,
|
|
2859
|
+
operator=self.operator,
|
|
2860
|
+
)
|
|
2861
|
+
|
|
2810
2862
|
def with_namespace(self, namespace: str):
|
|
2811
2863
|
return self.__class__(
|
|
2812
2864
|
left=(
|
|
@@ -2983,7 +3035,7 @@ class CaseElse(Namespaced, SelectGrain, BaseModel):
|
|
|
2983
3035
|
)
|
|
2984
3036
|
|
|
2985
3037
|
|
|
2986
|
-
class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
3038
|
+
class Conditional(ConceptArgs, Namespaced, ConstantInlineable, SelectGrain, BaseModel):
|
|
2987
3039
|
left: Union[
|
|
2988
3040
|
int,
|
|
2989
3041
|
str,
|
|
@@ -3029,6 +3081,32 @@ class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
|
3029
3081
|
def __repr__(self):
|
|
3030
3082
|
return f"{str(self.left)} {self.operator.value} {str(self.right)}"
|
|
3031
3083
|
|
|
3084
|
+
def inline_constant(self, constant: Concept) -> "Conditional":
|
|
3085
|
+
assert isinstance(constant.lineage, Function)
|
|
3086
|
+
new_val = constant.lineage.arguments[0]
|
|
3087
|
+
if isinstance(self.left, ConstantInlineable):
|
|
3088
|
+
new_left = self.left.inline_constant(constant)
|
|
3089
|
+
elif self.left == constant:
|
|
3090
|
+
new_left = new_val
|
|
3091
|
+
else:
|
|
3092
|
+
new_left = self.left
|
|
3093
|
+
|
|
3094
|
+
if isinstance(self.right, ConstantInlineable):
|
|
3095
|
+
new_right = self.right.inline_constant(constant)
|
|
3096
|
+
elif self.right == constant:
|
|
3097
|
+
new_right = new_val
|
|
3098
|
+
else:
|
|
3099
|
+
new_right = self.right
|
|
3100
|
+
|
|
3101
|
+
if self.right == constant:
|
|
3102
|
+
new_right = new_val
|
|
3103
|
+
|
|
3104
|
+
return Conditional(
|
|
3105
|
+
left=new_left,
|
|
3106
|
+
right=new_right,
|
|
3107
|
+
operator=self.operator,
|
|
3108
|
+
)
|
|
3109
|
+
|
|
3032
3110
|
def with_namespace(self, namespace: str):
|
|
3033
3111
|
return Conditional(
|
|
3034
3112
|
left=(
|
|
@@ -3368,7 +3446,9 @@ class RowsetItem(Namespaced, BaseModel):
|
|
|
3368
3446
|
return [self.content]
|
|
3369
3447
|
|
|
3370
3448
|
|
|
3371
|
-
class Parenthetical(
|
|
3449
|
+
class Parenthetical(
|
|
3450
|
+
ConceptArgs, Namespaced, ConstantInlineable, SelectGrain, BaseModel
|
|
3451
|
+
):
|
|
3372
3452
|
content: "Expr"
|
|
3373
3453
|
|
|
3374
3454
|
def __str__(self):
|
|
@@ -3402,6 +3482,15 @@ class Parenthetical(ConceptArgs, Namespaced, SelectGrain, BaseModel):
|
|
|
3402
3482
|
)
|
|
3403
3483
|
)
|
|
3404
3484
|
|
|
3485
|
+
def inline_constant(self, concept: Concept):
|
|
3486
|
+
return Parenthetical(
|
|
3487
|
+
content=(
|
|
3488
|
+
self.content.inline_constant(concept)
|
|
3489
|
+
if isinstance(self.content, ConstantInlineable)
|
|
3490
|
+
else self.content
|
|
3491
|
+
)
|
|
3492
|
+
)
|
|
3493
|
+
|
|
3405
3494
|
@property
|
|
3406
3495
|
def concept_arguments(self) -> List[Concept]:
|
|
3407
3496
|
base: List[Concept] = []
|
|
@@ -3498,7 +3587,7 @@ def list_to_wrapper(args):
|
|
|
3498
3587
|
return ListWrapper(args, type=types[0])
|
|
3499
3588
|
|
|
3500
3589
|
|
|
3501
|
-
def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType:
|
|
3590
|
+
def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType | NumericType:
|
|
3502
3591
|
if isinstance(arg, Function):
|
|
3503
3592
|
return arg.output_datatype
|
|
3504
3593
|
elif isinstance(arg, Concept):
|
|
@@ -3511,6 +3600,8 @@ def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType:
|
|
|
3511
3600
|
return DataType.STRING
|
|
3512
3601
|
elif isinstance(arg, float):
|
|
3513
3602
|
return DataType.FLOAT
|
|
3603
|
+
elif isinstance(arg, NumericType):
|
|
3604
|
+
return arg
|
|
3514
3605
|
elif isinstance(arg, ListWrapper):
|
|
3515
3606
|
return ListType(type=arg.type)
|
|
3516
3607
|
elif isinstance(arg, AggregateWrapper):
|
trilogy/core/optimization.py
CHANGED
|
@@ -50,7 +50,9 @@ def is_direct_return_eligible(
|
|
|
50
50
|
if isinstance(select, (PersistStatement, MultiSelectStatement)):
|
|
51
51
|
return False
|
|
52
52
|
derived_concepts = [
|
|
53
|
-
c
|
|
53
|
+
c
|
|
54
|
+
for c in cte.source.output_concepts + cte.source.hidden_concepts
|
|
55
|
+
if c not in cte.source.input_concepts
|
|
54
56
|
]
|
|
55
57
|
eligible = True
|
|
56
58
|
conditions = (
|
|
@@ -90,26 +92,9 @@ def sort_select_output(cte: CTE, query: SelectStatement | MultiSelectStatement):
|
|
|
90
92
|
|
|
91
93
|
def optimize_ctes(
|
|
92
94
|
input: list[CTE], root_cte: CTE, select: SelectStatement | MultiSelectStatement
|
|
93
|
-
):
|
|
95
|
+
) -> list[CTE]:
|
|
94
96
|
complete = False
|
|
95
97
|
REGISTERED_RULES: list["OptimizationRule"] = []
|
|
96
|
-
|
|
97
|
-
if CONFIG.optimizations.datasource_inlining:
|
|
98
|
-
REGISTERED_RULES.append(InlineDatasource())
|
|
99
|
-
if CONFIG.optimizations.predicate_pushdown:
|
|
100
|
-
REGISTERED_RULES.append(PredicatePushdown())
|
|
101
|
-
if CONFIG.optimizations.constant_inlining:
|
|
102
|
-
REGISTERED_RULES.append(InlineConstant())
|
|
103
|
-
loops = 0
|
|
104
|
-
while not complete and (loops <= MAX_OPTIMIZATION_LOOPS):
|
|
105
|
-
actions_taken = False
|
|
106
|
-
for rule in REGISTERED_RULES:
|
|
107
|
-
for cte in input:
|
|
108
|
-
inverse_map = gen_inverse_map(input)
|
|
109
|
-
actions_taken = rule.optimize(cte, inverse_map)
|
|
110
|
-
complete = not actions_taken
|
|
111
|
-
loops += 1
|
|
112
|
-
|
|
113
98
|
if CONFIG.optimizations.direct_return and is_direct_return_eligible(
|
|
114
99
|
root_cte, select
|
|
115
100
|
):
|
|
@@ -127,5 +112,20 @@ def optimize_ctes(
|
|
|
127
112
|
root_cte.condition = select.where_clause.conditional
|
|
128
113
|
root_cte.requires_nesting = False
|
|
129
114
|
sort_select_output(root_cte, select)
|
|
115
|
+
if CONFIG.optimizations.datasource_inlining:
|
|
116
|
+
REGISTERED_RULES.append(InlineDatasource())
|
|
117
|
+
if CONFIG.optimizations.predicate_pushdown:
|
|
118
|
+
REGISTERED_RULES.append(PredicatePushdown())
|
|
119
|
+
if CONFIG.optimizations.constant_inlining:
|
|
120
|
+
REGISTERED_RULES.append(InlineConstant())
|
|
121
|
+
loops = 0
|
|
122
|
+
while not complete and (loops <= MAX_OPTIMIZATION_LOOPS):
|
|
123
|
+
actions_taken = False
|
|
124
|
+
for rule in REGISTERED_RULES:
|
|
125
|
+
for cte in input:
|
|
126
|
+
inverse_map = gen_inverse_map(input)
|
|
127
|
+
actions_taken = actions_taken or rule.optimize(cte, inverse_map)
|
|
128
|
+
complete = not actions_taken
|
|
129
|
+
loops += 1
|
|
130
130
|
|
|
131
131
|
return filter_irrelevant_ctes(input, root_cte)
|
|
@@ -2,8 +2,8 @@ from trilogy.core.models import (
|
|
|
2
2
|
CTE,
|
|
3
3
|
Conditional,
|
|
4
4
|
BooleanOperator,
|
|
5
|
+
Datasource,
|
|
5
6
|
)
|
|
6
|
-
from trilogy.constants import logger
|
|
7
7
|
from trilogy.core.optimizations.base_optimization import OptimizationRule
|
|
8
8
|
|
|
9
9
|
|
|
@@ -48,16 +48,31 @@ class PredicatePushdown(OptimizationRule):
|
|
|
48
48
|
candidates = cte.condition.decompose()
|
|
49
49
|
else:
|
|
50
50
|
candidates = [cte.condition]
|
|
51
|
-
|
|
51
|
+
self.log(f"Have {len(candidates)} candidates to try to push down")
|
|
52
52
|
for candidate in candidates:
|
|
53
53
|
conditions = {x.address for x in candidate.concept_arguments}
|
|
54
54
|
for parent_cte in cte.parent_ctes:
|
|
55
|
+
if is_child_of(cte.condition, parent_cte.condition):
|
|
56
|
+
continue
|
|
55
57
|
materialized = {k for k, v in parent_cte.source_map.items() if v != []}
|
|
58
|
+
# if it's a root datasource, we can filter on _any_ of the output concepts
|
|
59
|
+
if parent_cte.is_root_datasource:
|
|
60
|
+
extra_check = {
|
|
61
|
+
x.address
|
|
62
|
+
for x in parent_cte.source.datasources[0].output_concepts
|
|
63
|
+
}
|
|
64
|
+
if conditions.issubset(extra_check):
|
|
65
|
+
for x in conditions:
|
|
66
|
+
if x not in materialized:
|
|
67
|
+
materialized.add(x)
|
|
68
|
+
parent_cte.source_map[x] = [
|
|
69
|
+
parent_cte.source.datasources[0].name
|
|
70
|
+
]
|
|
56
71
|
if conditions.issubset(materialized):
|
|
57
72
|
if all(
|
|
58
73
|
[
|
|
59
74
|
is_child_of(candidate, child.condition)
|
|
60
|
-
for child in inverse_map
|
|
75
|
+
for child in inverse_map.get(parent_cte.name, [])
|
|
61
76
|
]
|
|
62
77
|
):
|
|
63
78
|
self.log(
|
|
@@ -73,14 +88,16 @@ class PredicatePushdown(OptimizationRule):
|
|
|
73
88
|
parent_cte.condition = candidate
|
|
74
89
|
optimized = True
|
|
75
90
|
else:
|
|
76
|
-
|
|
91
|
+
self.log(
|
|
92
|
+
f"conditions {conditions} not subset of parent {parent_cte.name} parent has {materialized} "
|
|
93
|
+
)
|
|
77
94
|
|
|
78
95
|
if all(
|
|
79
96
|
[
|
|
80
97
|
is_child_of(cte.condition, parent_cte.condition)
|
|
81
98
|
for parent_cte in cte.parent_ctes
|
|
82
99
|
]
|
|
83
|
-
):
|
|
100
|
+
) and not any([isinstance(x, Datasource) for x in cte.source.datasources]):
|
|
84
101
|
self.log("All parents have same filter, removing filter")
|
|
85
102
|
cte.condition = None
|
|
86
103
|
optimized = True
|
trilogy/dialect/base.py
CHANGED
|
@@ -46,6 +46,7 @@ from trilogy.core.models import (
|
|
|
46
46
|
ImportStatement,
|
|
47
47
|
RawSQLStatement,
|
|
48
48
|
ProcessedRawSQLStatement,
|
|
49
|
+
NumericType,
|
|
49
50
|
)
|
|
50
51
|
from trilogy.core.query_processor import process_query, process_persist
|
|
51
52
|
from trilogy.dialect.common import render_join
|
|
@@ -97,6 +98,7 @@ DATATYPE_MAP = {
|
|
|
97
98
|
DataType.INTEGER: "int",
|
|
98
99
|
DataType.FLOAT: "float",
|
|
99
100
|
DataType.BOOL: "bool",
|
|
101
|
+
DataType.NUMERIC: "numeric",
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
|
|
@@ -334,6 +336,7 @@ class BaseDialect:
|
|
|
334
336
|
Parenthetical,
|
|
335
337
|
AggregateWrapper,
|
|
336
338
|
MagicConstants,
|
|
339
|
+
NumericType,
|
|
337
340
|
ListType,
|
|
338
341
|
ListWrapper[int],
|
|
339
342
|
ListWrapper[str],
|
|
@@ -439,6 +442,8 @@ class BaseDialect:
|
|
|
439
442
|
return str(e.value)
|
|
440
443
|
elif isinstance(e, DatePart):
|
|
441
444
|
return str(e.value)
|
|
445
|
+
elif isinstance(e, NumericType):
|
|
446
|
+
return f"{self.DATATYPE_MAP[DataType.NUMERIC]}({e.precision},{e.scale})"
|
|
442
447
|
elif isinstance(e, MagicConstants):
|
|
443
448
|
if e == MagicConstants.NULL:
|
|
444
449
|
return "null"
|
trilogy/dialect/presto.py
CHANGED
|
@@ -4,6 +4,7 @@ from jinja2 import Template
|
|
|
4
4
|
|
|
5
5
|
from trilogy.core.enums import FunctionType, WindowType
|
|
6
6
|
from trilogy.dialect.base import BaseDialect
|
|
7
|
+
from trilogy.core.models import DataType
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
WINDOW_FUNCTION_MAP: Mapping[WindowType, Callable[[Any, Any, Any], str]] = {}
|
|
@@ -26,7 +27,7 @@ FUNCTION_MAP = {
|
|
|
26
27
|
FunctionType.WEEK: lambda x: f"EXTRACT(WEEK from {x[0]})",
|
|
27
28
|
FunctionType.QUARTER: lambda x: f"EXTRACT(QUARTER from {x[0]})",
|
|
28
29
|
# math
|
|
29
|
-
FunctionType.DIVIDE: lambda x: f"
|
|
30
|
+
FunctionType.DIVIDE: lambda x: f"{x[0]}/{x[1]}",
|
|
30
31
|
FunctionType.DATE_ADD: lambda x: f"DATE_ADD('{x[1]}', {x[2]}, {x[0]})",
|
|
31
32
|
FunctionType.CURRENT_DATE: lambda x: "CURRENT_DATE",
|
|
32
33
|
FunctionType.CURRENT_DATETIME: lambda x: "CURRENT_TIMESTAMP",
|
|
@@ -80,6 +81,7 @@ class PrestoDialect(BaseDialect):
|
|
|
80
81
|
}
|
|
81
82
|
QUOTE_CHARACTER = '"'
|
|
82
83
|
SQL_TEMPLATE = SQL_TEMPLATE
|
|
84
|
+
DATATYPE_MAP = {**BaseDialect.DATATYPE_MAP, DataType.NUMERIC: "DECIMAL"}
|
|
83
85
|
|
|
84
86
|
|
|
85
87
|
class TrinoDialect(PrestoDialect):
|
trilogy/executor.py
CHANGED
|
@@ -16,6 +16,8 @@ from trilogy.core.models import (
|
|
|
16
16
|
PersistStatement,
|
|
17
17
|
ShowStatement,
|
|
18
18
|
Concept,
|
|
19
|
+
ConceptDeclarationStatement,
|
|
20
|
+
Datasource,
|
|
19
21
|
)
|
|
20
22
|
from trilogy.dialect.base import BaseDialect
|
|
21
23
|
from trilogy.dialect.enums import Dialects
|
|
@@ -100,6 +102,33 @@ class Executor(object):
|
|
|
100
102
|
def execute_query(self, query) -> CursorResult:
|
|
101
103
|
raise NotImplementedError("Cannot execute type {}".format(type(query)))
|
|
102
104
|
|
|
105
|
+
@execute_query.register
|
|
106
|
+
def _(self, query: ConceptDeclarationStatement) -> CursorResult:
|
|
107
|
+
concept = query.concept
|
|
108
|
+
return MockResult(
|
|
109
|
+
[
|
|
110
|
+
{
|
|
111
|
+
"address": concept.address,
|
|
112
|
+
"type": concept.datatype.value,
|
|
113
|
+
"purpose": concept.purpose.value,
|
|
114
|
+
"derivation": concept.derivation.value,
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
["address", "type", "purpose", "derivation"],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
@execute_query.register
|
|
121
|
+
def _(self, query: Datasource) -> CursorResult:
|
|
122
|
+
|
|
123
|
+
return MockResult(
|
|
124
|
+
[
|
|
125
|
+
{
|
|
126
|
+
"name": query.name,
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
["name"],
|
|
130
|
+
)
|
|
131
|
+
|
|
103
132
|
@execute_query.register
|
|
104
133
|
def _(self, query: SelectStatement) -> CursorResult:
|
|
105
134
|
sql = self.generator.generate_queries(
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -103,6 +103,7 @@ from trilogy.core.models import (
|
|
|
103
103
|
RowsetDerivationStatement,
|
|
104
104
|
LooseConceptList,
|
|
105
105
|
list_to_wrapper,
|
|
106
|
+
NumericType,
|
|
106
107
|
)
|
|
107
108
|
from trilogy.parsing.exceptions import ParseError
|
|
108
109
|
from trilogy.utility import string_to_hash
|
|
@@ -309,7 +310,9 @@ class ParseToObjects(Transformer):
|
|
|
309
310
|
|
|
310
311
|
@v_args(meta=True)
|
|
311
312
|
def struct_type(self, meta: Meta, args) -> StructType:
|
|
312
|
-
final: list[
|
|
313
|
+
final: list[
|
|
314
|
+
DataType | MapType | ListType | StructType | NumericType | Concept
|
|
315
|
+
] = []
|
|
313
316
|
for arg in args:
|
|
314
317
|
if not isinstance(arg, (DataType, ListType, StructType)):
|
|
315
318
|
new = self.environment.concepts.__getitem__( # type: ignore
|
|
@@ -323,12 +326,17 @@ class ParseToObjects(Transformer):
|
|
|
323
326
|
def list_type(self, args) -> ListType:
|
|
324
327
|
return ListType(type=args[0])
|
|
325
328
|
|
|
326
|
-
def
|
|
329
|
+
def numeric_type(self, args) -> NumericType:
|
|
330
|
+
return NumericType(precision=args[0], scale=args[1])
|
|
331
|
+
|
|
332
|
+
def data_type(self, args) -> DataType | ListType | StructType | NumericType:
|
|
327
333
|
resolved = args[0]
|
|
328
334
|
if isinstance(resolved, StructType):
|
|
329
335
|
return resolved
|
|
330
336
|
elif isinstance(resolved, ListType):
|
|
331
337
|
return resolved
|
|
338
|
+
elif isinstance(resolved, NumericType):
|
|
339
|
+
return resolved
|
|
332
340
|
return DataType(args[0].lower())
|
|
333
341
|
|
|
334
342
|
def array_comparison(self, args) -> ComparisonOperator:
|
|
@@ -1551,6 +1559,8 @@ class ParseToObjects(Transformer):
|
|
|
1551
1559
|
DataType.STRING,
|
|
1552
1560
|
DataType.FLOAT,
|
|
1553
1561
|
DataType.NUMBER,
|
|
1562
|
+
DataType.NUMERIC,
|
|
1563
|
+
DataType.BOOL,
|
|
1554
1564
|
},
|
|
1555
1565
|
arg_count=2,
|
|
1556
1566
|
)
|
trilogy/parsing/render.py
CHANGED
|
@@ -39,6 +39,7 @@ from trilogy.core.models import (
|
|
|
39
39
|
AlignClause,
|
|
40
40
|
AlignItem,
|
|
41
41
|
RawSQLStatement,
|
|
42
|
+
NumericType,
|
|
42
43
|
)
|
|
43
44
|
from trilogy.core.enums import Modifier
|
|
44
45
|
|
|
@@ -172,6 +173,10 @@ class Renderer:
|
|
|
172
173
|
def _(self, arg: DataType):
|
|
173
174
|
return arg.value
|
|
174
175
|
|
|
176
|
+
@to_string.register
|
|
177
|
+
def _(self, arg: "NumericType"):
|
|
178
|
+
return f"""Numeric({arg.precision},{arg.scale})"""
|
|
179
|
+
|
|
175
180
|
@to_string.register
|
|
176
181
|
def _(self, arg: ListWrapper):
|
|
177
182
|
return "[" + ", ".join([self.to_string(x) for x in arg]) + "]"
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -280,12 +280,14 @@
|
|
|
280
280
|
|
|
281
281
|
SHORTHAND_MODIFIER: "~"
|
|
282
282
|
|
|
283
|
-
struct_type: "struct" "<" ((data_type | IDENTIFIER) ",")* (data_type | IDENTIFIER) ","? ">"
|
|
283
|
+
struct_type: "struct"i "<" ((data_type | IDENTIFIER) ",")* (data_type | IDENTIFIER) ","? ">"
|
|
284
284
|
|
|
285
|
-
list_type: "list" "<" data_type ">"
|
|
285
|
+
list_type: "list"i "<" data_type ">"
|
|
286
286
|
|
|
287
|
+
numeric_type: "numeric"i "(" int_lit "," int_lit ")"
|
|
287
288
|
|
|
288
|
-
|
|
289
|
+
|
|
290
|
+
!data_type: "string"i | "number"i | "numeric"i | "map"i | "list"i | "array"i | "any"i | "int"i | "bigint"i | "date"i | "datetime"i | "timestamp"i | "float"i | "bool"i | numeric_type | struct_type | list_type
|
|
289
291
|
|
|
290
292
|
PURPOSE: "key"i | "metric"i | CONST
|
|
291
293
|
PROPERTY: "property"i
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|