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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.116
3
+ Version: 0.0.1.118
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,8 +1,8 @@
1
- trilogy/__init__.py,sha256=0jWazkuszqLHMaM49JtN8aEur-f9lwCZ5PqZbNhO10E,292
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=auuDykCHeqlRWIHOfBfgIIIntEctWaUC-VPJr1DQbYk,10217
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=zkRReytiotOBAW-a3Ri5eoejZDYTt2-7Op80ZxZxUmw,9129
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=_k_ZqrsZ6HYNz3CJ8yiOVXMej1pHb_QO0KZNxkDyvno,113767
20
- trilogy/core/optimization.py,sha256=1xAFn7aw8skqDFUQCel5xJc0hVUYHs-oW1DckN8Z4n4,4043
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=s5BMLADcQy4_UsMCchHTsInaJUtLGh1l2kTpqzAzcs4,3199
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=ro9fKV6RrPggUVmJPlIEdySLsR3Pq5rm0UMQho6Bx_k,31518
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=8zjRn8AeYXZQGuUi-afyBWLet8o-LSt6gm5IH7bTdiw,2987
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=cT1IoLP0LzykNX0UfH9-eu8PzjfzSNCtZ_hZvf58TRg,56761
74
- trilogy/parsing/render.py,sha256=cOKHvl2ZrpkD4lpng9iXc37gc3XyZ1e51woPxB7QqsI,11672
75
- trilogy/parsing/trilogy.lark,sha256=xYpVKneNhXruxIyzFqcK1b8-NSqzzEwtPrTxmpXd_Vc,10786
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.116.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
79
- pytrilogy-0.0.1.116.dist-info/METADATA,sha256=kyUsh-1RPnsedKLexiwJCKTl9ZzZHl5Uu4LUQcV9hj0,7878
80
- pytrilogy-0.0.1.116.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
81
- pytrilogy-0.0.1.116.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
82
- pytrilogy-0.0.1.116.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
83
- pytrilogy-0.0.1.116.dist-info/RECORD,,
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
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.1.116"
7
+ __version__ = "0.0.1.118"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
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[DataType | ListType | StructType | MapType] = None,
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["DataType", "MapType", "ListType", "StructType", "Concept"]
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(self) -> DataType | StructType | MapType | ListType:
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(ConceptArgs, Namespaced, SelectGrain, BaseModel):
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):
@@ -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 for c in cte.source.output_concepts if c not in cte.source.input_concepts
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
- logger.info(f"Have {len(candidates)} candidates to try to push down")
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[parent_cte.name]
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
- logger.info("conditions not subset of parent materialized")
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"SAFE_DIVIDE({x[0]},{x[1]})",
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(
@@ -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[DataType | MapType | ListType | StructType | Concept] = []
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 data_type(self, args) -> DataType | ListType | StructType:
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]) + "]"
@@ -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
- !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
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