pytrilogy 0.0.1.116__py3-none-any.whl → 0.0.1.117__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.117
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,4 +1,4 @@
1
- trilogy/__init__.py,sha256=0jWazkuszqLHMaM49JtN8aEur-f9lwCZ5PqZbNhO10E,292
1
+ trilogy/__init__.py,sha256=1wHuIjygzuHzrQfHKZQ2GJsmw4tMvEXzwEJMg9Lb3Zc,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
@@ -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=hXp-b29w3vNHQHpTU-VPXJqJaLferNwa681xQ3pf8R0,9129
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=U8gYAvRoob4uj3f-j3N5EwkbBs_tKmcA48IGwmhqrbM,114203
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.117.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
79
+ pytrilogy-0.0.1.117.dist-info/METADATA,sha256=jrHvRWl_dtmpVu_aw08SS-whIymZ6l051tcpYmPQPD0,7878
80
+ pytrilogy-0.0.1.117.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
81
+ pytrilogy-0.0.1.117.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
82
+ pytrilogy-0.0.1.117.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
83
+ pytrilogy-0.0.1.117.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.117"
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 = (
@@ -56,9 +59,7 @@ def argument_to_purpose(arg) -> Purpose:
56
59
  return Purpose.PROPERTY
57
60
  elif isinstance(arg, Concept):
58
61
  return arg.purpose
59
- elif isinstance(arg, (int, float, str, bool, list)):
60
- return Purpose.CONSTANT
61
- elif isinstance(arg, DataType):
62
+ elif isinstance(arg, (int, float, str, bool, list, NumericType, DataType)):
62
63
  return Purpose.CONSTANT
63
64
  elif isinstance(arg, DatePart):
64
65
  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",
@@ -176,6 +178,19 @@ class DataType(Enum):
176
178
  return self
177
179
 
178
180
 
181
+ class NumericType(BaseModel):
182
+ precision: int = 20
183
+ scale: int = 5
184
+
185
+ @property
186
+ def data_type(self):
187
+ return DataType.NUMERIC
188
+
189
+ @property
190
+ def value(self):
191
+ return self.data_type.value
192
+
193
+
179
194
  class ListType(BaseModel):
180
195
  model_config = ConfigDict(frozen=True)
181
196
  type: ALL_TYPES
@@ -192,7 +207,9 @@ class ListType(BaseModel):
192
207
  return self.data_type.value
193
208
 
194
209
  @property
195
- def value_data_type(self) -> DataType | StructType | MapType | ListType:
210
+ def value_data_type(
211
+ self,
212
+ ) -> DataType | StructType | MapType | ListType | NumericType:
196
213
  if isinstance(self.type, Concept):
197
214
  return self.type.datatype
198
215
  return self.type
@@ -270,7 +287,7 @@ def empty_grain() -> Grain:
270
287
 
271
288
  class Concept(Namespaced, SelectGrain, BaseModel):
272
289
  name: str
273
- datatype: DataType | ListType | StructType | MapType
290
+ datatype: DataType | ListType | StructType | MapType | NumericType
274
291
  purpose: Purpose
275
292
  metadata: Optional[Metadata] = Field(
276
293
  default_factory=lambda: Metadata(description=None, line_number=None),
@@ -790,12 +807,12 @@ class LooseConceptList(BaseModel):
790
807
  class Function(Namespaced, SelectGrain, BaseModel):
791
808
  operator: FunctionType
792
809
  arg_count: int = Field(default=1)
793
- output_datatype: DataType | ListType | StructType | MapType
810
+ output_datatype: DataType | ListType | StructType | MapType | NumericType
794
811
  output_purpose: Purpose
795
812
  valid_inputs: Optional[
796
813
  Union[
797
- Set[DataType | ListType | StructType],
798
- List[Set[DataType | ListType | StructType]],
814
+ Set[DataType | ListType | StructType | NumericType],
815
+ List[Set[DataType | ListType | StructType] | NumericType],
799
816
  ]
800
817
  ] = None
801
818
  arguments: Sequence[
@@ -808,6 +825,7 @@ class Function(Namespaced, SelectGrain, BaseModel):
808
825
  str,
809
826
  DataType,
810
827
  ListType,
828
+ NumericType,
811
829
  DatePart,
812
830
  "Parenthetical",
813
831
  CaseWhen,
@@ -1499,7 +1517,7 @@ class DatasourceMetadata(BaseModel):
1499
1517
 
1500
1518
  class MergeStatement(Namespaced, BaseModel):
1501
1519
  concepts: List[Concept]
1502
- datatype: DataType | ListType | StructType | MapType
1520
+ datatype: DataType | ListType | StructType | MapType | NumericType
1503
1521
 
1504
1522
  @cached_property
1505
1523
  def concepts_lcl(self):
@@ -3498,7 +3516,7 @@ def list_to_wrapper(args):
3498
3516
  return ListWrapper(args, type=types[0])
3499
3517
 
3500
3518
 
3501
- def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType:
3519
+ def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType | NumericType:
3502
3520
  if isinstance(arg, Function):
3503
3521
  return arg.output_datatype
3504
3522
  elif isinstance(arg, Concept):
@@ -3511,6 +3529,8 @@ def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType:
3511
3529
  return DataType.STRING
3512
3530
  elif isinstance(arg, float):
3513
3531
  return DataType.FLOAT
3532
+ elif isinstance(arg, NumericType):
3533
+ return arg
3514
3534
  elif isinstance(arg, ListWrapper):
3515
3535
  return ListType(type=arg.type)
3516
3536
  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):
@@ -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