pytrilogy 0.0.3.71__py3-none-any.whl → 0.0.3.73__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.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.71
3
+ Version: 0.0.3.73
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,7 +1,7 @@
1
- pytrilogy-0.0.3.71.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=M3ozbgvpiJ9h44b-oCogBhygSVgnDOV37ejNFOYUV5w,303
1
+ pytrilogy-0.0.3.73.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=QMg3__Goia6PP_-WfMP9ZmROmpZ0XzX4rCz9MvpBb_w,303
3
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- trilogy/constants.py,sha256=lv_aJWP6dn6e2aF4BAE72jbnNtceFddfqtiDSsvzno0,1692
4
+ trilogy/constants.py,sha256=eKb_EJvSqjN9tGbdVEViwdtwwh8fZ3-jpOEDqL71y70,1691
5
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
6
6
  trilogy/executor.py,sha256=BolR6UwgDOdIcDG0gw_OSaB23rISgIn8Dzdll0kODmg,16506
7
7
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
@@ -11,12 +11,12 @@ trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
11
11
  trilogy/authoring/__init__.py,sha256=h-Ag7vT76tsjib9BfjOgI-yVpuJDgpn2TSps-ibRAj8,2593
12
12
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
14
- trilogy/core/enums.py,sha256=2sgoVzHQpscIx_WUqJMH0oGxtcg41gN0l1qkRjzK2iI,7803
14
+ trilogy/core/enums.py,sha256=fog9j6YU22LXuoArKsNJn-UFIEAVk06LI_-Zdg2UDv0,7841
15
15
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
16
16
  trilogy/core/environment_helpers.py,sha256=VvPIiFemqaLLpIpLIqprfu63K7muZ1YzNg7UZIUph8w,8267
17
17
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
18
18
  trilogy/core/exceptions.py,sha256=jYEduuMehcMkmCpf-OC_taELPZm7qNfeSNzIWkDYScs,707
19
- trilogy/core/functions.py,sha256=R8_aOe2mNRgOLmsnI9pG_GOU3I7kFPTnXQzplN2d7Dw,29343
19
+ trilogy/core/functions.py,sha256=xT4DR6IRTt3N6TZ_LEcSV2nvLUWFGfajd5vQZd2Pw7k,29512
20
20
  trilogy/core/graph_models.py,sha256=BYhJzHKSgnZHVLJs1CfsgrxTPHqKqPNeA64RlozGY0A,3498
21
21
  trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
22
22
  trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
@@ -24,12 +24,12 @@ trilogy/core/query_processor.py,sha256=5aFgv-2LVM1Uku9cR_tFuTRDwyLnxc95bCMAHeFy2
24
24
  trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
25
25
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  trilogy/core/models/author.py,sha256=Wz_6yEZS5EyXJ5wHHl-C44ikFka5XftBYOjNRK98Hfw,77790
27
- trilogy/core/models/build.py,sha256=BNHzfqD5NWCKHntvnswvDrtCD1JFgvPedx23cPq942U,65405
27
+ trilogy/core/models/build.py,sha256=59KmJDY1lzJMXuAZjxvT7K5IhIga9k2xwhzQBbx5zyo,65688
28
28
  trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
29
29
  trilogy/core/models/core.py,sha256=EMAuWTngoNVGCdfNrAY7_k6g528iodNQLwPRVip-8DA,10980
30
30
  trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
31
31
  trilogy/core/models/environment.py,sha256=TBbPfsXHpJK49QKuqHwhgZD4PwHiSAYjXmTTTomRE7o,27861
32
- trilogy/core/models/execute.py,sha256=A4SkqmOW9XrbgPDhP7LnS9dUq-Tw4vgzVcQbkkB2ljA,41706
32
+ trilogy/core/models/execute.py,sha256=Erufst3yroU3PgNJMWF5lICm14AkH02laYeesSB01UU,41705
33
33
  trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
34
34
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
35
35
  trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
@@ -41,7 +41,7 @@ trilogy/core/processing/discovery_node_factory.py,sha256=I3JJxoF-u8OVvqXXAOhvMg2
41
41
  trilogy/core/processing/discovery_utility.py,sha256=3xdd1ypKappSDm0SJs7WtW5YegL80SlYhDQlkNePp4E,4549
42
42
  trilogy/core/processing/discovery_validation.py,sha256=fGWJmKpgEd1f4RkK-fYOBUT1cwsJnahwXFAdRlou7MI,5365
43
43
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
44
- trilogy/core/processing/utility.py,sha256=b1F3NT7-MP_-U4KmpC52BOAwLu6mybfndeA1iiZwChw,22016
44
+ trilogy/core/processing/utility.py,sha256=ChD1lP2iXtfsI1bS08OZsJ2o9t877OhNXrDvyEgYdmY,22581
45
45
  trilogy/core/processing/node_generators/__init__.py,sha256=w8TQQgNhyAra6JQHdg1_Ags4BGyxjXYruu6UeC5yOkI,873
46
46
  trilogy/core/processing/node_generators/basic_node.py,sha256=luN8LftafZepoFgDRv4gmvEGFlOI2j0icJ5fz4UT7uo,5165
47
47
  trilogy/core/processing/node_generators/common.py,sha256=PdysdroW9DUADP7f5Wv_GKPUyCTROZV1g3L45fawxi8,9443
@@ -57,7 +57,7 @@ trilogy/core/processing/node_generators/select_node.py,sha256=Ta1G39V94gjX_AgyZD
57
57
  trilogy/core/processing/node_generators/synonym_node.py,sha256=AnAsa_Wj50NJ_IK0HSgab_7klYmKVrv0WI1uUe-GvEY,3766
58
58
  trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
59
59
  trilogy/core/processing/node_generators/unnest_node.py,sha256=ueOQtoTf2iJHO09RzWHDFQ5iKZq2fVhGf2KAF2U2kU8,2677
60
- trilogy/core/processing/node_generators/window_node.py,sha256=GP3Hvkbb0TDA6ef7W7bmvQEHVH-NRIfBT_0W4fcH3g4,6529
60
+ trilogy/core/processing/node_generators/window_node.py,sha256=A90linr4pkZtTNfn9k2YNLqrJ_SFII3lbHxB-BC6mI8,6688
61
61
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=mKCDHxr2eyrdozXEHq_HvuwtBgnb9JPlImJIx6JGX34,7834
63
63
  trilogy/core/processing/nodes/__init__.py,sha256=zTge1EzwzEydlcMliIFO_TT7h7lS8l37lyZuQDir1h0,5487
@@ -76,28 +76,28 @@ trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
76
76
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
77
77
  trilogy/core/statements/execute.py,sha256=rqfuoMuXPcH7L7TmE1dSiZ_K_A1ohB8whVMfGimZBOk,1294
78
78
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
- trilogy/dialect/base.py,sha256=YC8GROz9iwRFrUuLdYdzJNKXOliYecmfEqXJtTQGRVE,45187
80
- trilogy/dialect/bigquery.py,sha256=e19dGcarapgA0x5_Xmq2StyHzuDWPOOPaR4elkWXwug,4203
81
- trilogy/dialect/common.py,sha256=hhzuMTFW9QQIP7TKLT9BlJy6lw2R03a68jKQ-7t4-2c,6070
79
+ trilogy/dialect/base.py,sha256=5FI4_XDdSavdHeC3VhSI8xrvQagFyRRq6Ttsh70ajo4,45241
80
+ trilogy/dialect/bigquery.py,sha256=8xhEu0z_lKANjbvzvBbC7CeKrJf1iP8YyrHqNale-ug,4351
81
+ trilogy/dialect/common.py,sha256=tSthIZOXXRPQ4KeMKnDDsH7KlTmf2EVqigVtLyoc4zc,6071
82
82
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
83
83
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
84
84
  trilogy/dialect/duckdb.py,sha256=gsXhPKX0D7ykJ9RFK9qx8uBTjLgtHu6PYv6GlBFtnJE,4448
85
85
  trilogy/dialect/enums.py,sha256=FRNYQ5-w-B6-X0yXKNU5g9GowsMlERFogTC5u2nxL_s,4740
86
- trilogy/dialect/postgres.py,sha256=VH4EB4myjIeZTHeFU6vK00GxY9c53rCBjg2mLbdaCEE,3254
87
- trilogy/dialect/presto.py,sha256=Wd0yHq3EOSfCOy7lWPfCr13JHO3olsm8qUXgml-oTm0,3529
88
- trilogy/dialect/snowflake.py,sha256=LQIcHuyuGZXbxrv6sH17aLXLzw7yFVuRoE9M4doNk5k,3187
89
- trilogy/dialect/sql_server.py,sha256=z2Vg7Qvw83rbGiEFIvHHLqVWJTWiz2xs76kpQj4HdTU,3131
86
+ trilogy/dialect/postgres.py,sha256=el2PKwfyvWGk5EZtLudqAH5ewLitY1sFHJiocBSyxyM,3393
87
+ trilogy/dialect/presto.py,sha256=yzSF8SZ6o1dizj1UueAa7S3lR0qNYJdSXbF78EHyhY0,3668
88
+ trilogy/dialect/snowflake.py,sha256=T6_mKfhpDazB1xQxqFLS2AJwzwzBcPYY6_qxRnAtFBs,3326
89
+ trilogy/dialect/sql_server.py,sha256=HnUEvb8Yjl6MnMWTITzpFPZgDajhGzfDPz5A8dDerak,3279
90
90
  trilogy/hooks/__init__.py,sha256=T3SF3phuUDPLXKGRVE_Lf9mzuwoXWyaLolncR_1kY30,144
91
91
  trilogy/hooks/base_hook.py,sha256=I_l-NBMNC7hKTDx1JgHZPVOOCvLQ36m2oIGaR5EUMXY,1180
92
92
  trilogy/hooks/graph_hook.py,sha256=5BfR7Dt0bgEsCLgwjowgCsVkboGYfVJGOz8g9mqpnos,4756
93
93
  trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
94
94
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
95
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
- trilogy/parsing/common.py,sha256=_5UEnLtu40VQ8gb6wg3GtSrxf6IONhEOntmdsm0X4lU,30961
96
+ trilogy/parsing/common.py,sha256=yV1AckK0h8u1OFeGQBTMu-wuW5m63c5CcZuPicsTH_w,30660
97
97
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
98
98
  trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
99
99
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
100
- trilogy/parsing/parse_engine.py,sha256=vYhGmSJXi5TcLvt2mujISucQc35j4kHa78hj5ip9gGQ,72564
100
+ trilogy/parsing/parse_engine.py,sha256=YFIWTkWrUJ8l-KoCVwPs1hodj3BhqX-DHbBXWQN5akc,72776
101
101
  trilogy/parsing/render.py,sha256=gGCFj2ue0UoaU2MR6qHGMAHXkYRMkTmHjnBowdcgFMY,19603
102
102
  trilogy/parsing/trilogy.lark,sha256=1RIqA7zrGuqDJYSv9yHGSw0vdIfGOLPOnc4hSBRSTVU,14346
103
103
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -107,11 +107,11 @@ trilogy/std/date.preql,sha256=HWZm4t4HWyxr5geWRsY05RnHBVDMci8z8YA2cu0-OOw,188
107
107
  trilogy/std/display.preql,sha256=nm7lox87Xf6lBvXCVCS6x2HskguMKzndEBucJ5pktzk,175
108
108
  trilogy/std/geography.preql,sha256=qLnHmDU5EnvjTbfqZF-NEclSYM5_e9rZra7QjV01rZ4,582
109
109
  trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
110
- trilogy/std/net.preql,sha256=-bMV6dyofskl4Kvows-iQ4JCxjVUwsZOeWCy8JO5Ftw,135
110
+ trilogy/std/net.preql,sha256=7l7MqIjs6TDCpO6dBAoNJU81Ex255jZRK36kBgE1GDs,158
111
111
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
112
112
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
113
- pytrilogy-0.0.3.71.dist-info/METADATA,sha256=fjanpiqcWQJVfNIXoj2k04p5-0miJb_Rcxc0lZM7moo,9734
114
- pytrilogy-0.0.3.71.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
- pytrilogy-0.0.3.71.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
116
- pytrilogy-0.0.3.71.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
117
- pytrilogy-0.0.3.71.dist-info/RECORD,,
113
+ pytrilogy-0.0.3.73.dist-info/METADATA,sha256=hMwyLjCer0xl2zhn1XH4PijoJrNy7oXV9qKyHIDfUWA,9734
114
+ pytrilogy-0.0.3.73.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
+ pytrilogy-0.0.3.73.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
116
+ pytrilogy-0.0.3.73.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
117
+ pytrilogy-0.0.3.73.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.71"
7
+ __version__ = "0.0.3.73"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/constants.py CHANGED
@@ -37,7 +37,7 @@ class Comments:
37
37
  show: bool = False
38
38
  basic: bool = True
39
39
  joins: bool = True
40
- nullable: bool = False
40
+ nullable: bool = True
41
41
  partial: bool = True
42
42
 
43
43
 
trilogy/core/enums.py CHANGED
@@ -134,6 +134,7 @@ class FunctionType(Enum):
134
134
  CAST = "cast"
135
135
  CONCAT = "concat"
136
136
  CONSTANT = "constant"
137
+ TYPED_CONSTANT = "typed_constant"
137
138
  COALESCE = "coalesce"
138
139
  IS_NULL = "isnull"
139
140
  NULLIF = "nullif"
trilogy/core/functions.py CHANGED
@@ -704,6 +704,11 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
704
704
  output_purpose=Purpose.CONSTANT,
705
705
  arg_count=1,
706
706
  ),
707
+ FunctionType.TYPED_CONSTANT: FunctionConfig(
708
+ output_purpose=Purpose.CONSTANT,
709
+ output_type_function=get_cast_output_type,
710
+ arg_count=2,
711
+ ),
707
712
  FunctionType.IS_NULL: FunctionConfig(
708
713
  output_purpose=Purpose.PROPERTY,
709
714
  output_type=DataType.BOOL,
@@ -1486,6 +1486,17 @@ def get_canonical_pseudonyms(environment: Environment) -> dict[str, set[str]]:
1486
1486
  return roots
1487
1487
 
1488
1488
 
1489
+ def requires_concept_nesting(
1490
+ expr,
1491
+ ) -> AggregateWrapper | WindowItem | FilterItem | Function | None:
1492
+ if isinstance(expr, (AggregateWrapper, WindowItem, FilterItem)):
1493
+ return expr
1494
+ if isinstance(expr, Function) and expr.operator == FunctionType.GROUP:
1495
+ # group by requires nesting
1496
+ return expr
1497
+ return None
1498
+
1499
+
1489
1500
  class Factory:
1490
1501
 
1491
1502
  def __init__(
@@ -1509,11 +1520,12 @@ class Factory:
1509
1520
  | WindowItem
1510
1521
  | FilterItem
1511
1522
  | Function
1512
- | ListWrapper[Any]
1513
- | MapWrapper[Any, Any]
1523
+ | ListWrapper
1524
+ | MapWrapper
1514
1525
  | int
1515
1526
  | float
1516
1527
  | str
1528
+ | date
1517
1529
  ),
1518
1530
  ) -> tuple[Concept, BuildConcept]:
1519
1531
  from trilogy.parsing.common import arbitrary_to_concept
@@ -1572,7 +1584,8 @@ class Factory:
1572
1584
 
1573
1585
  raw_args: list[Concept | FuncArgs] = []
1574
1586
  for arg in base.arguments:
1575
- # to do proper discovery, we need to inject virtual intermediate ocncepts
1587
+ # to do proper discovery, we need to inject virtual intermediate concepts
1588
+ # we don't use requires_concept_nesting here by design
1576
1589
  if isinstance(arg, (AggregateWrapper, FilterItem, WindowItem)):
1577
1590
  narg, _ = self.instantiate_concept(arg)
1578
1591
  raw_args.append(narg)
@@ -1640,11 +1653,10 @@ class Factory:
1640
1653
  def _(self, base: CaseWhen) -> BuildCaseWhen:
1641
1654
 
1642
1655
  comparison = base.comparison
1643
- if isinstance(comparison, (AggregateWrapper, FilterItem, WindowItem)):
1644
- comparison, _ = self.instantiate_concept(comparison)
1645
1656
  expr: Concept | FuncArgs = base.expr
1646
- if isinstance(expr, (AggregateWrapper, FilterItem, WindowItem)):
1647
- expr, _ = self.instantiate_concept(expr)
1657
+ validation = requires_concept_nesting(expr)
1658
+ if validation:
1659
+ expr, _ = self.instantiate_concept(validation)
1648
1660
  return BuildCaseWhen.model_construct(
1649
1661
  comparison=self.build(comparison),
1650
1662
  expr=self.build(expr),
@@ -1653,8 +1665,9 @@ class Factory:
1653
1665
  @build.register
1654
1666
  def _(self, base: CaseElse) -> BuildCaseElse:
1655
1667
  expr: Concept | FuncArgs = base.expr
1656
- if isinstance(expr, (AggregateWrapper, FilterItem, WindowItem)):
1657
- expr, _ = self.instantiate_concept(expr)
1668
+ validation = requires_concept_nesting(expr)
1669
+ if validation:
1670
+ expr, _ = self.instantiate_concept(validation)
1658
1671
  return BuildCaseElse.model_construct(expr=self.build(expr))
1659
1672
 
1660
1673
  @build.register
@@ -1753,10 +1766,9 @@ class Factory:
1753
1766
  def _(self, base: OrderItem) -> BuildOrderItem:
1754
1767
 
1755
1768
  bexpr: Any
1756
- if isinstance(base.expr, (AggregateWrapper, WindowItem, FilterItem)) or (
1757
- isinstance(base.expr, Function) and base.expr.operator == FunctionType.GROUP
1758
- ):
1759
- bexpr, _ = self.instantiate_concept(base.expr)
1769
+ validation = requires_concept_nesting(base.expr)
1770
+ if validation:
1771
+ bexpr, _ = self.instantiate_concept(validation)
1760
1772
  else:
1761
1773
  bexpr = base.expr
1762
1774
  return BuildOrderItem.model_construct(
@@ -1781,8 +1793,9 @@ class Factory:
1781
1793
  def _(self, base: WindowItem) -> BuildWindowItem:
1782
1794
 
1783
1795
  content: Concept | FuncArgs = base.content
1784
- if isinstance(content, (AggregateWrapper, FilterItem, WindowItem)):
1785
- content, _ = self.instantiate_concept(content)
1796
+ validation = requires_concept_nesting(base.content)
1797
+ if validation:
1798
+ content, _ = self.instantiate_concept(validation)
1786
1799
  final_by = []
1787
1800
  for x in base.order_by:
1788
1801
  if (
@@ -1811,6 +1824,7 @@ class Factory:
1811
1824
  @build.register
1812
1825
  def _(self, base: SubselectComparison) -> BuildSubselectComparison:
1813
1826
  right: Any = base.right
1827
+ # this has specialized logic - include all Functions
1814
1828
  if isinstance(base.right, (AggregateWrapper, WindowItem, FilterItem, Function)):
1815
1829
  right_c, _ = self.instantiate_concept(base.right)
1816
1830
  right = right_c
@@ -1824,12 +1838,14 @@ class Factory:
1824
1838
  def _(self, base: Comparison) -> BuildComparison:
1825
1839
 
1826
1840
  left = base.left
1827
- if isinstance(left, (AggregateWrapper, WindowItem, FilterItem)):
1828
- left_c, _ = self.instantiate_concept(left)
1841
+ validation = requires_concept_nesting(base.left)
1842
+ if validation:
1843
+ left_c, _ = self.instantiate_concept(validation)
1829
1844
  left = left_c # type: ignore
1830
1845
  right = base.right
1831
- if isinstance(right, (AggregateWrapper, WindowItem, FilterItem)):
1832
- right_c, _ = self.instantiate_concept(right)
1846
+ validation = requires_concept_nesting(base.right)
1847
+ if validation:
1848
+ right_c, _ = self.instantiate_concept(validation)
1833
1849
  right = right_c # type: ignore
1834
1850
  return BuildComparison.model_construct(
1835
1851
  left=self.handle_constant(self.build(left)),
@@ -501,7 +501,6 @@ class BaseJoin(BaseModel):
501
501
  f"Cannot join a dataself to itself, joining {self.left_datasource} and"
502
502
  f" {self.right_datasource}"
503
503
  )
504
-
505
504
  # Early returns maintained as in original code
506
505
  if self.concept_pairs or self.concepts == []:
507
506
  return self
@@ -39,6 +39,7 @@ def resolve_window_parent_concepts(
39
39
  base += item.concept_arguments
40
40
  if concept.grain:
41
41
  for gitem in concept.grain.components:
42
+ logger.info(f"{LOGGER_PREFIX} appending grain item {gitem} to base")
42
43
  base.append(environment.concepts[gitem])
43
44
  return concept.lineage.content, unique(base, "address")
44
45
 
@@ -55,7 +56,7 @@ def gen_window_node(
55
56
  ) -> StrategyNode | None:
56
57
  base, parent_concepts = resolve_window_parent_concepts(concept, environment)
57
58
  logger.info(
58
- f"{padding(depth)}{LOGGER_PREFIX} generating window node for {concept} with parents {parent_concepts} and optional {local_optional}"
59
+ f"{padding(depth)}{LOGGER_PREFIX} generating window node for {concept} with parents {[x.address for x in parent_concepts]} and optional {local_optional}"
59
60
  )
60
61
  equivalent_optional = [
61
62
  x
@@ -68,6 +69,8 @@ def gen_window_node(
68
69
  # append in keys to get the right grain
69
70
  if concept.keys:
70
71
  for item in concept.keys:
72
+ if item in targets:
73
+ continue
71
74
  logger.info(
72
75
  f"{padding(depth)}{LOGGER_PREFIX} appending search for key {item}"
73
76
  )
@@ -13,6 +13,7 @@ from trilogy.core.enums import (
13
13
  FunctionClass,
14
14
  Granularity,
15
15
  JoinType,
16
+ Modifier,
16
17
  Purpose,
17
18
  )
18
19
  from trilogy.core.models.build import (
@@ -355,6 +356,20 @@ def reduce_concept_pairs(input: list[ConceptPair]) -> list[ConceptPair]:
355
356
  return final
356
357
 
357
358
 
359
+ def get_modifiers(
360
+ concept: str,
361
+ join: JoinOrderOutput,
362
+ ds_node_map: dict[str, QueryDatasource | BuildDatasource],
363
+ ):
364
+ base = []
365
+
366
+ if join.right and concept in ds_node_map[join.right].nullable_concepts:
367
+ base.append(Modifier.NULLABLE)
368
+ if join.left and concept in ds_node_map[join.left].nullable_concepts:
369
+ base.append(Modifier.NULLABLE)
370
+ return list(set(base))
371
+
372
+
358
373
  def get_node_joins(
359
374
  datasources: List[QueryDatasource | BuildDatasource],
360
375
  environment: BuildEnvironment,
@@ -400,6 +415,9 @@ def get_node_joins(
400
415
  concept_map[concept], ds_node_map[j.right]
401
416
  ),
402
417
  existing_datasource=ds_node_map[k],
418
+ modifiers=get_modifiers(
419
+ concept_map[concept].address, j, ds_node_map
420
+ ),
403
421
  )
404
422
  for k, v in j.keys.items()
405
423
  for concept in v
trilogy/dialect/base.py CHANGED
@@ -163,6 +163,7 @@ FUNCTION_MAP = {
163
163
  FunctionType.ALIAS: lambda x: f"{x[0]}",
164
164
  FunctionType.GROUP: lambda x: f"{x[0]}",
165
165
  FunctionType.CONSTANT: lambda x: f"{x[0]}",
166
+ FunctionType.TYPED_CONSTANT: lambda x: f"{x[0]}",
166
167
  FunctionType.COALESCE: lambda x: f"coalesce({','.join(x)})",
167
168
  FunctionType.NULLIF: lambda x: f"nullif({x[0]},{x[1]})",
168
169
  FunctionType.CAST: lambda x: f"cast({x[0]} as {x[1]})",
@@ -43,7 +43,8 @@ FUNCTION_MAP = {
43
43
 
44
44
  FUNCTION_GRAIN_MATCH_MAP = {
45
45
  **FUNCTION_MAP,
46
- FunctionType.COUNT: lambda args: "1",
46
+ FunctionType.COUNT_DISTINCT: lambda args: f"CASE WHEN{args[0]} IS NOT NULL THEN 1 ELSE 0 END",
47
+ FunctionType.COUNT: lambda args: f"CASE WHEN {args[0]} IS NOT NULL THEN 1 ELSE 0 END",
47
48
  FunctionType.SUM: lambda args: f"{args[0]}",
48
49
  FunctionType.AVG: lambda args: f"{args[0]}",
49
50
  }
trilogy/dialect/common.py CHANGED
@@ -20,6 +20,7 @@ from trilogy.core.models.execute import (
20
20
 
21
21
 
22
22
  def null_wrapper(lval: str, rval: str, modifiers: list[Modifier]) -> str:
23
+
23
24
  if Modifier.NULLABLE in modifiers:
24
25
  return f"({lval} = {rval} or ({lval} is null and {rval} is null))"
25
26
  return f"{lval} = {rval}"
@@ -37,7 +37,8 @@ FUNCTION_MAP = {
37
37
 
38
38
  FUNCTION_GRAIN_MATCH_MAP = {
39
39
  **FUNCTION_MAP,
40
- FunctionType.COUNT: lambda args: f"{args[0]}",
40
+ FunctionType.COUNT_DISTINCT: lambda args: f"CASE WHEN{args[0]} IS NOT NULL THEN 1 ELSE 0 END",
41
+ FunctionType.COUNT: lambda args: f"CASE WHEN {args[0]} IS NOT NULL THEN 1 ELSE 0 END",
41
42
  FunctionType.SUM: lambda args: f"{args[0]}",
42
43
  FunctionType.AVG: lambda args: f"{args[0]}",
43
44
  }
trilogy/dialect/presto.py CHANGED
@@ -39,7 +39,8 @@ FUNCTION_MAP = {
39
39
 
40
40
  FUNCTION_GRAIN_MATCH_MAP = {
41
41
  **FUNCTION_MAP,
42
- FunctionType.COUNT: lambda args: f"{args[0]}",
42
+ FunctionType.COUNT_DISTINCT: lambda args: f"CASE WHEN{args[0]} IS NOT NULL THEN 1 ELSE 0 END",
43
+ FunctionType.COUNT: lambda args: f"CASE WHEN {args[0]} IS NOT NULL THEN 1 ELSE 0 END",
43
44
  FunctionType.SUM: lambda args: f"{args[0]}",
44
45
  FunctionType.AVG: lambda args: f"{args[0]}",
45
46
  }
@@ -36,7 +36,8 @@ FUNCTION_MAP = {
36
36
 
37
37
  FUNCTION_GRAIN_MATCH_MAP = {
38
38
  **FUNCTION_MAP,
39
- FunctionType.COUNT: lambda args: f"{args[0]}",
39
+ FunctionType.COUNT_DISTINCT: lambda args: f"CASE WHEN{args[0]} IS NOT NULL THEN 1 ELSE 0 END",
40
+ FunctionType.COUNT: lambda args: f"CASE WHEN {args[0]} IS NOT NULL THEN 1 ELSE 0 END",
40
41
  FunctionType.SUM: lambda args: f"{args[0]}",
41
42
  FunctionType.AVG: lambda args: f"{args[0]}",
42
43
  }
@@ -31,7 +31,8 @@ FUNCTION_MAP = {
31
31
  # we may return a static value
32
32
  FUNCTION_GRAIN_MATCH_MAP = {
33
33
  **FUNCTION_MAP,
34
- FunctionType.COUNT: lambda args: "1",
34
+ FunctionType.COUNT_DISTINCT: lambda args: f"CASE WHEN{args[0]} IS NOT NULL THEN 1 ELSE 0 END",
35
+ FunctionType.COUNT: lambda args: f"CASE WHEN {args[0]} IS NOT NULL THEN 1 ELSE 0 END",
35
36
  FunctionType.SUM: lambda args: f"{args[0]}",
36
37
  FunctionType.AVG: lambda args: f"{args[0]}",
37
38
  }
trilogy/parsing/common.py CHANGED
@@ -173,7 +173,9 @@ def concept_list_to_keys(
173
173
 
174
174
 
175
175
  def constant_to_concept(
176
- parent: ListWrapper | TupleWrapper | MapWrapper | int | float | str,
176
+ parent: (
177
+ ListWrapper | TupleWrapper | MapWrapper | int | float | str | date | datetime
178
+ ),
177
179
  name: str,
178
180
  namespace: str,
179
181
  metadata: Metadata | None = None,
@@ -368,9 +370,9 @@ def _get_relevant_parent_concepts(arg) -> tuple[list[ConceptRef], bool]:
368
370
  return get_concept_arguments(arg), False
369
371
 
370
372
 
371
- def get_relevant_parent_concepts(arg):
372
- results = _get_relevant_parent_concepts(arg)
373
- return results
373
+ def get_relevant_parent_concepts(arg) -> tuple[list[ConceptRef], bool]:
374
+ concepts, status = _get_relevant_parent_concepts(arg)
375
+ return unique(concepts, "address"), status
374
376
 
375
377
 
376
378
  def group_function_to_concept(
@@ -626,20 +628,11 @@ def window_item_to_concept(
626
628
 
627
629
  # when including the order by in discovery grain
628
630
  if parent.order_by:
631
+
629
632
  grain_components = parent.over + [bcontent.output]
630
633
  for item in parent.order_by:
631
- # confirm that it's not just an aggregate at the grain of the stuff we're already keying of of
632
- # in which case we can ignore contributions
633
- if (
634
- isinstance(item.expr, AggregateWrapper)
635
- and set([x.address for x in item.expr.by]) == keys
636
- ):
637
- continue
638
- elif isinstance(item.expr, AggregateWrapper):
639
-
640
- grain_components += item.expr.by
641
- else:
642
- grain_components += item.concept_arguments
634
+ relevant, _ = get_relevant_parent_concepts(item.expr)
635
+ grain_components += relevant
643
636
  else:
644
637
  grain_components = parent.over + [bcontent.output]
645
638
 
@@ -842,6 +835,7 @@ def arbitrary_to_concept(
842
835
  | int
843
836
  | float
844
837
  | str
838
+ | date
845
839
  ),
846
840
  environment: Environment,
847
841
  namespace: str | None = None,
@@ -640,6 +640,7 @@ class ParseToObjects(Transformer):
640
640
 
641
641
  @v_args(meta=True)
642
642
  def concept_derivation(self, meta: Meta, args) -> ConceptDerivationStatement:
643
+
643
644
  if len(args) > 3:
644
645
  metadata = args[3]
645
646
  else:
@@ -733,6 +734,7 @@ class ParseToObjects(Transformer):
733
734
 
734
735
  @v_args(meta=True)
735
736
  def constant_derivation(self, meta: Meta, args) -> Concept:
737
+
736
738
  if len(args) > 3:
737
739
  metadata = args[3]
738
740
  else:
@@ -1405,6 +1407,7 @@ class ParseToObjects(Transformer):
1405
1407
  def string_lit(self, args) -> str:
1406
1408
  if not args:
1407
1409
  return ""
1410
+
1408
1411
  return args[0]
1409
1412
 
1410
1413
  @v_args(meta=True)
@@ -1890,7 +1893,9 @@ class ParseToObjects(Transformer):
1890
1893
 
1891
1894
  def internal_fcast(self, meta, args) -> Function:
1892
1895
  args = process_function_args(args, meta=meta, environment=self.environment)
1896
+
1893
1897
  if isinstance(args[0], str):
1898
+
1894
1899
  processed: date | datetime | int | float | bool | str
1895
1900
  if args[1] == DataType.DATE:
1896
1901
  processed = date.fromisoformat(args[0])
@@ -1908,6 +1913,10 @@ class ParseToObjects(Transformer):
1908
1913
  processed = args[0]
1909
1914
  else:
1910
1915
  raise SyntaxError(f"Invalid cast type {args[1]}")
1916
+ if isinstance(args[1], TraitDataType):
1917
+ return self.function_factory.create_function(
1918
+ [processed, args[1]], FunctionType.TYPED_CONSTANT, meta
1919
+ )
1911
1920
  return self.function_factory.create_function(
1912
1921
  [processed], FunctionType.CONSTANT, meta
1913
1922
  )
trilogy/std/net.preql CHANGED
@@ -5,4 +5,5 @@ type domain string;
5
5
  type ip_net_mask string;
6
6
  type ipv6_address string;
7
7
  type ipv4_address string;
8
- type suffix string;
8
+ type suffix string;
9
+ type url_image string;