pytrilogy 0.0.1.109__py3-none-any.whl → 0.0.1.111__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.

Files changed (34) hide show
  1. {pytrilogy-0.0.1.109.dist-info → pytrilogy-0.0.1.111.dist-info}/METADATA +1 -1
  2. {pytrilogy-0.0.1.109.dist-info → pytrilogy-0.0.1.111.dist-info}/RECORD +34 -34
  3. {pytrilogy-0.0.1.109.dist-info → pytrilogy-0.0.1.111.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +1 -1
  5. trilogy/constants.py +11 -3
  6. trilogy/core/enums.py +1 -0
  7. trilogy/core/models.py +94 -67
  8. trilogy/core/optimization.py +134 -12
  9. trilogy/core/processing/concept_strategies_v3.py +44 -19
  10. trilogy/core/processing/node_generators/basic_node.py +2 -0
  11. trilogy/core/processing/node_generators/common.py +3 -1
  12. trilogy/core/processing/node_generators/concept_merge_node.py +24 -8
  13. trilogy/core/processing/node_generators/filter_node.py +36 -6
  14. trilogy/core/processing/node_generators/node_merge_node.py +34 -23
  15. trilogy/core/processing/node_generators/rowset_node.py +37 -8
  16. trilogy/core/processing/node_generators/select_node.py +23 -9
  17. trilogy/core/processing/node_generators/unnest_node.py +24 -3
  18. trilogy/core/processing/node_generators/window_node.py +4 -2
  19. trilogy/core/processing/nodes/__init__.py +7 -6
  20. trilogy/core/processing/nodes/base_node.py +40 -6
  21. trilogy/core/processing/nodes/filter_node.py +15 -1
  22. trilogy/core/processing/nodes/group_node.py +20 -1
  23. trilogy/core/processing/nodes/merge_node.py +37 -10
  24. trilogy/core/processing/nodes/select_node_v2.py +34 -39
  25. trilogy/core/processing/nodes/unnest_node.py +12 -0
  26. trilogy/core/processing/nodes/window_node.py +11 -0
  27. trilogy/core/processing/utility.py +0 -14
  28. trilogy/core/query_processor.py +125 -29
  29. trilogy/dialect/base.py +45 -40
  30. trilogy/executor.py +31 -3
  31. trilogy/parsing/parse_engine.py +49 -17
  32. {pytrilogy-0.0.1.109.dist-info → pytrilogy-0.0.1.111.dist-info}/LICENSE.md +0 -0
  33. {pytrilogy-0.0.1.109.dist-info → pytrilogy-0.0.1.111.dist-info}/entry_points.txt +0 -0
  34. {pytrilogy-0.0.1.109.dist-info → pytrilogy-0.0.1.111.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.109
3
+ Version: 0.0.1.111
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,14 +1,14 @@
1
- trilogy/__init__.py,sha256=e2cU9lfgy43E_xTR0vpKBnQaJvz7-8qF7hjum9oQk-k,292
1
+ trilogy/__init__.py,sha256=PNtNelxhMDftdgkjjOKNn49l5DhtOeAgkI93YI77r64,292
2
2
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- trilogy/constants.py,sha256=LxiK2TiVQPEa6tXkxWk9DJHOR3zsGNSqgQuqtOf66cw,518
3
+ trilogy/constants.py,sha256=DJi3ESttmvqgy6fPRXiaQzqJVye6jYwf6XM89NHv0_M,735
4
4
  trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
5
- trilogy/executor.py,sha256=xF6wzbhP6a3wz4nrxsRCKeKF7qytUQEL75oI3BGJ2hQ,8744
5
+ trilogy/executor.py,sha256=_ZbjrKsUdWL52tWgpxqZnmccAuPXcIPEPN_dDSLNeAQ,9696
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
9
9
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  trilogy/core/constants.py,sha256=LL8NLvxb3HRnAjvofyLRXqQJijLcYiXAQYQzGarVD-g,128
11
- trilogy/core/enums.py,sha256=KEZQTzJ8tlGIukuUwQUIG1FTHOP1B4i0EeCgFjfsbDw,5394
11
+ trilogy/core/enums.py,sha256=XSvq2yPxn9oJ18nhn7UERgIV1IXZDRiSWaGpvtU34eE,5416
12
12
  trilogy/core/env_processor.py,sha256=SU-jpaGfoWLe9sGTeQYG1qjVnwGQ7TwctmnJRlfzluc,1459
13
13
  trilogy/core/environment_helpers.py,sha256=mzBDHhdF9ssZ_-LY8CcaM_ddfJavkpRYrFImUd3cjXI,5972
14
14
  trilogy/core/ergonomics.py,sha256=w3gwXdgrxNHCuaRdyKg73t6F36tj-wIjQf47WZkHmJk,1465
@@ -16,36 +16,36 @@ trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,56
16
16
  trilogy/core/functions.py,sha256=zkRReytiotOBAW-a3Ri5eoejZDYTt2-7Op80ZxZxUmw,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=AwVZNiDN1hM0BeEquEyrfTnuVBPDR8UuTnoFUhAaqUo,109648
20
- trilogy/core/optimization.py,sha256=chfzpLVJo9eg8H4e2hdnpRqWMDTQ3tJWPdDfGESa-EU,4510
21
- trilogy/core/query_processor.py,sha256=6BqLYPwyFkRtueTIRFZi3IcVFTpbpGRNowayhSn3_AY,11805
19
+ trilogy/core/models.py,sha256=EDtmcDKNBUBc--jIwWtk2vkQM2Q7heuZ0VH7JF_M32s,109985
20
+ trilogy/core/optimization.py,sha256=B_EuAqHmJbuJiGyBfrC66FB_YPsGg-nbfnV8FjqfP6Q,9097
21
+ trilogy/core/query_processor.py,sha256=clIRJ6IcsqIVBPKFsxt8bqCLsLyajvAu02MUIcKQhTo,15713
22
22
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- trilogy/core/processing/concept_strategies_v3.py,sha256=27lZXFLgDEF3sh2MUR7HX_atVz7TC1fJB7z3oxa1TcY,22610
23
+ trilogy/core/processing/concept_strategies_v3.py,sha256=MYrpNMidqvPOg123RekOcqVTjcj03i_538gBo0MzoWE,23432
24
24
  trilogy/core/processing/graph_utils.py,sha256=ulCJ4hYAISbUxLD6VM2fah9RBPGIXSEHEPeRBSFl0Rs,1197
25
- trilogy/core/processing/utility.py,sha256=Gk35HgyIG2SSUyI5OHZcB0bw1PZUVC_aNc9Sre6xPQU,10535
25
+ trilogy/core/processing/utility.py,sha256=acxH5448-j8JXqxMRibyAxjz1Wqu7QudbR0PfMuucww,9902
26
26
  trilogy/core/processing/node_generators/__init__.py,sha256=LIs6uBEum8LDc-26zjyAwjxa-ay2ok9tKtPjDNvbVkE,757
27
- trilogy/core/processing/node_generators/basic_node.py,sha256=tVPmg0r0kDdABkmn6z4sxsk1hKy9yTT_Xvl1eVN2Zck,2162
28
- trilogy/core/processing/node_generators/common.py,sha256=A0zB4xr1etbEexaiSH6mVTecXY_wd7pSwWUGUt-u0eg,8882
29
- trilogy/core/processing/node_generators/concept_merge_node.py,sha256=TRbOIjLWfLB0Nl6YmMV1ao0qhPP6OQDd9M3UViWkCBU,6621
30
- trilogy/core/processing/node_generators/filter_node.py,sha256=CGALiTzKhPAvXPFAguIQfjf6I3pjlafY0uaaM9MTkIE,3414
27
+ trilogy/core/processing/node_generators/basic_node.py,sha256=HJnIhZLgkUdorKYcofe-QnKSM3Lf_3QO91cbSJhsqf4,2242
28
+ trilogy/core/processing/node_generators/common.py,sha256=liZDth7mvhkF_sUFXK7JitJsiaKD132w3ySLbF7l-nE,8956
29
+ trilogy/core/processing/node_generators/concept_merge_node.py,sha256=x4M8VVZZmBcqHDY1uq7M9KGKCBwjU6mcE_x2BOEk2Mg,7328
30
+ trilogy/core/processing/node_generators/filter_node.py,sha256=y_tqYe2So18vWHASMwVPLzDO-PnyQCO-MAlI4B-rY3Y,4526
31
31
  trilogy/core/processing/node_generators/group_node.py,sha256=xWI1xNIXEOj6jlRGD9hcv2_vVNvY6lpzJl6pQ8HuFBE,2988
32
32
  trilogy/core/processing/node_generators/group_to_node.py,sha256=BzPdYwzoo8gRMH7BDffTTXq4z-mjfCEzvfB5I-P0_nw,2941
33
33
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=vP84dnLQy6dtypi6mUbt9sMAcmmrTgQ1Oz4GI6X1IEo,6421
34
- trilogy/core/processing/node_generators/node_merge_node.py,sha256=sQQ9jhw1oAJh649DBAJX6U7r_E_piFS95mxKvm7pxqQ,5818
35
- trilogy/core/processing/node_generators/rowset_node.py,sha256=zlSRd58V4fDqz1Km65cWblOrEFpXAT3jlSvv6NKC3pc,4909
36
- trilogy/core/processing/node_generators/select_node.py,sha256=xeCqIUEubrf3u_QQfbGdf1BG4fO0HYQ64hiFur8NUqY,20080
37
- trilogy/core/processing/node_generators/unnest_node.py,sha256=s1VXQZSf1LnX3ISeQ5JzmzmCKUw30-5OK_f0YTB9_48,1031
38
- trilogy/core/processing/node_generators/window_node.py,sha256=ekazi5eXxnShpcp-qukXNG4DHFdULoXrX-YWUWLNEpM,2527
39
- trilogy/core/processing/nodes/__init__.py,sha256=gzKxGSduIQ5QwpMWrmwSYiE8sg2mWejwVn0VvjYc6s0,3879
40
- trilogy/core/processing/nodes/base_node.py,sha256=Du7hRjVVOAiGb0okytzKIa_TQqhwTNYGU8PGNnrE1xs,9142
41
- trilogy/core/processing/nodes/filter_node.py,sha256=DqSRv8voEajPZqzeeiIsxuv4ubvsmeQcCW6x_v2CmOk,1359
42
- trilogy/core/processing/nodes/group_node.py,sha256=Y_NWB_AwFrE-YithjZ7lYYDN4e0el4su3ICq2EIr3HA,3837
43
- trilogy/core/processing/nodes/merge_node.py,sha256=baLDHCJiX5tk1dsVTm1KebJKPyy1w3WGMfN5wdm0BRw,12759
44
- trilogy/core/processing/nodes/select_node_v2.py,sha256=tAADeVruch-flFiedbY1zi7ukMG2RpWecvxxZ5aL3ZU,6354
45
- trilogy/core/processing/nodes/unnest_node.py,sha256=t4kY3a_dR3iXistPemStfdw0uJfnxwTcoQg1HiDa3xo,1501
46
- trilogy/core/processing/nodes/window_node.py,sha256=QjAWgqBZqFSRCPwc7JBmgQJobWW50rsHI0pjJe0Zzg0,926
34
+ trilogy/core/processing/node_generators/node_merge_node.py,sha256=wNDHAbRrKSjsns-EROM_G12mRyOMjbcWpYav2uefXOE,6045
35
+ trilogy/core/processing/node_generators/rowset_node.py,sha256=eNG6rfLifUKraoRGxE8pesQMy5cKT6R5XNIaa3Wuiwk,6081
36
+ trilogy/core/processing/node_generators/select_node.py,sha256=Qb00Kizsv-877UMkGfusl5jXKXMZtZTtLks5pxU07SU,20698
37
+ trilogy/core/processing/node_generators/unnest_node.py,sha256=6CH66eGwpadNX7TzUhWZ8aqIisOtQeHINbLV6X3QBUk,1779
38
+ trilogy/core/processing/node_generators/window_node.py,sha256=9nXUXUgQrNczU1gaOqhOZPNzCUxw-lkxt0R7HORI6ss,2582
39
+ trilogy/core/processing/nodes/__init__.py,sha256=baODkJfvUoWEEbu843GEd7snubwLeOG5FQ8l-CwIaC8,3928
40
+ trilogy/core/processing/nodes/base_node.py,sha256=yhjmsAUmhHDqgbQjz_9YdfP-M5pj4xbrPRDF6Y4XVuw,10498
41
+ trilogy/core/processing/nodes/filter_node.py,sha256=rDw4vfE6tqWxuKT0arihVmIOoOWDDCyzRA-2yONX_Ek,1860
42
+ trilogy/core/processing/nodes/group_node.py,sha256=vzeU9J4xMhRrPj4-KPJTgNbH-KFu2ZS8b57SOynsdw0,4448
43
+ trilogy/core/processing/nodes/merge_node.py,sha256=FvSiTWKOzaUsXBkf6wJD8QQqQxp_aphS_I5VzNRw8Yo,13600
44
+ trilogy/core/processing/nodes/select_node_v2.py,sha256=ERCflBFzKpD5SzweMevnJLyQnxmF_-IQ6VRu5yVeiBg,6552
45
+ trilogy/core/processing/nodes/unnest_node.py,sha256=JFtm90IVM-46aCYkTNIaJah6v9ApAfonjVhcVM1HmDE,1903
46
+ trilogy/core/processing/nodes/window_node.py,sha256=X7qxLUKd3tekjUUsmH_4vz5b-U89gMnGd04VBxuu2Ns,1280
47
47
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- trilogy/dialect/base.py,sha256=xPB5mh6471VJLHxNdXrYvi7q7vJC_tioVR1LrLcoZc0,29394
48
+ trilogy/dialect/base.py,sha256=ii9P_OO8BhKsQVAr9A13rhx_dzRZd4wxnkL-Ul5OS74,30398
49
49
  trilogy/dialect/bigquery.py,sha256=9vxQn2BMv_oTGQSWQpoN5ho_OgqMWaHH9e-5vQVf44c,2906
50
50
  trilogy/dialect/common.py,sha256=zWrYmvevlXznocw9uGHmY5Ws1rp_kICm9zA_ulTe4eg,2165
51
51
  trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
@@ -65,13 +65,13 @@ trilogy/parsing/common.py,sha256=lz0IyVA8v-u-DGFgzkmdb4_00I--Kegmo9HNF7CrajI,579
65
65
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
66
66
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
67
67
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
68
- trilogy/parsing/parse_engine.py,sha256=iOqKUCyLeHyFVwwAt-XTSJGHia4zzLUN6bYDuIfJ1Pg,63938
68
+ trilogy/parsing/parse_engine.py,sha256=Xxqyx0MLRWIcjU55jRao1XHEZ5SunhbZIPhJD9-urlE,65008
69
69
  trilogy/parsing/render.py,sha256=fxjpq2FZLgllw_d4cru-t_IXNPAz2DmYkT7v9ED0XRI,11540
70
70
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
72
- pytrilogy-0.0.1.109.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
73
- pytrilogy-0.0.1.109.dist-info/METADATA,sha256=-m-LMyjvq4whCY7LpRM9zFqpn3hqH4-DuuP0JYOFfwQ,7882
74
- pytrilogy-0.0.1.109.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
75
- pytrilogy-0.0.1.109.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
76
- pytrilogy-0.0.1.109.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
77
- pytrilogy-0.0.1.109.dist-info/RECORD,,
72
+ pytrilogy-0.0.1.111.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
73
+ pytrilogy-0.0.1.111.dist-info/METADATA,sha256=OoRPUSENsnE0Qd1-nOtrbHz2T-izBPb_hJV4jMvlIDw,7882
74
+ pytrilogy-0.0.1.111.dist-info/WHEEL,sha256=rWxmBtp7hEUqVLOnTaDOPpR-cZpCDkzhhcBce-Zyd5k,91
75
+ pytrilogy-0.0.1.111.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
76
+ pytrilogy-0.0.1.111.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
77
+ pytrilogy-0.0.1.111.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (71.0.4)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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.109"
7
+ __version__ = "0.0.1.111"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/constants.py CHANGED
@@ -1,8 +1,8 @@
1
1
  from logging import getLogger
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
3
  from enum import Enum
4
4
 
5
- logger = getLogger("preql")
5
+ logger = getLogger("trilogy")
6
6
 
7
7
  DEFAULT_NAMESPACE = "local"
8
8
 
@@ -18,12 +18,20 @@ class MagicConstants(Enum):
18
18
  NULL_VALUE = MagicConstants.NULL
19
19
 
20
20
 
21
+ @dataclass
22
+ class Optimizations:
23
+ predicate_pushdown: bool = True
24
+ datasource_inlining: bool = True
25
+ direct_return: bool = True
26
+
27
+
21
28
  # TODO: support loading from environments
22
29
  @dataclass
23
30
  class Config:
24
31
  strict_mode: bool = True
25
32
  human_identifiers: bool = True
26
- inline_datasources: bool = True
33
+ validate_missing: bool = True
34
+ optimizations: Optimizations = field(default_factory=Optimizations)
27
35
 
28
36
 
29
37
  CONFIG = Config()
trilogy/core/enums.py CHANGED
@@ -263,6 +263,7 @@ class SourceType(Enum):
263
263
  WINDOW = "window"
264
264
  UNNEST = "unnest"
265
265
  CONSTANT = "constant"
266
+ ROWSET = "rowset"
266
267
 
267
268
 
268
269
  class ShowCategory(Enum):
trilogy/core/models.py CHANGED
@@ -33,7 +33,13 @@ from pydantic import (
33
33
  )
34
34
  from lark.tree import Meta
35
35
  from pathlib import Path
36
- from trilogy.constants import logger, DEFAULT_NAMESPACE, ENV_CACHE_NAME, MagicConstants
36
+ from trilogy.constants import (
37
+ logger,
38
+ DEFAULT_NAMESPACE,
39
+ ENV_CACHE_NAME,
40
+ MagicConstants,
41
+ CONFIG,
42
+ )
37
43
  from trilogy.core.constants import (
38
44
  ALL_ROWS_CONCEPT,
39
45
  INTERNAL_NAMESPACE,
@@ -61,7 +67,6 @@ from trilogy.core.enums import (
61
67
  from trilogy.core.exceptions import UndefinedConceptException, InvalidSyntaxException
62
68
  from trilogy.utility import unique
63
69
  from collections import UserList
64
- from trilogy.utility import string_to_hash
65
70
  from functools import cached_property
66
71
  from abc import ABC
67
72
 
@@ -129,7 +134,7 @@ class ConceptArgs(ABC):
129
134
  raise NotImplementedError
130
135
 
131
136
  @property
132
- def existence_arguments(self) -> List["Concept"]:
137
+ def existence_arguments(self) -> list[tuple["Concept", ...]]:
133
138
  return []
134
139
 
135
140
  @property
@@ -281,9 +286,6 @@ class Concept(Namespaced, SelectGrain, BaseModel):
281
286
  MultiSelectStatement | MergeStatement,
282
287
  ]
283
288
  ] = None
284
- # lineage: Annotated[Optional[
285
- # Union[Function, WindowItem, FilterItem, AggregateWrapper]
286
- # ], WrapValidator(lineage_validator)] = None
287
289
  namespace: Optional[str] = Field(default=DEFAULT_NAMESPACE, validate_default=True)
288
290
  keys: Optional[Tuple["Concept", ...]] = None
289
291
  grain: "Grain" = Field(default=None, validate_default=True)
@@ -621,6 +623,12 @@ class Grain(BaseModel):
621
623
  if sub.purpose in (Purpose.PROPERTY, Purpose.METRIC) and sub.keys:
622
624
  if all([c in v2 for c in sub.keys]):
623
625
  continue
626
+ elif sub.derivation == PurposeLineage.MERGE and isinstance(
627
+ sub.lineage, MergeStatement
628
+ ):
629
+ parents = sub.lineage.concepts
630
+ if any([p in v2 for p in parents]):
631
+ continue
624
632
  final.append(sub)
625
633
  v2 = sorted(final, key=lambda x: x.name)
626
634
  return v2
@@ -966,23 +974,6 @@ class ConceptTransform(Namespaced, BaseModel):
966
974
  modifiers=self.modifiers,
967
975
  )
968
976
 
969
- def with_filter(self, where: "WhereClause") -> "ConceptTransform":
970
- id_hash = string_to_hash(str(where))
971
- new_parent_concept = Concept(
972
- name=f"_anon_concept_transform_filter_input_{id_hash}",
973
- datatype=self.output.datatype,
974
- purpose=self.output.purpose,
975
- lineage=self.output.lineage,
976
- namespace=DEFAULT_NAMESPACE,
977
- grain=self.output.grain,
978
- keys=self.output.keys,
979
- )
980
- new_parent = FilterItem(content=new_parent_concept, where=where)
981
- self.output.lineage = new_parent
982
- return ConceptTransform(
983
- function=new_parent, output=self.output, modifiers=self.modifiers
984
- )
985
-
986
977
 
987
978
  class Window(BaseModel):
988
979
  count: int
@@ -1611,13 +1602,15 @@ class Datasource(Namespaced, BaseModel):
1611
1602
  def __add__(self, other):
1612
1603
  if not other == self:
1613
1604
  raise ValueError(
1614
- "Attempted to add two datasources that are not identical, this should"
1615
- " never happen"
1605
+ "Attempted to add two datasources that are not identical, this is not a valid operation"
1616
1606
  )
1617
1607
  return self
1618
1608
 
1609
+ def __repr__(self):
1610
+ return f"Datasource<{self.namespace}.{self.identifier}@<{self.grain}>"
1611
+
1619
1612
  def __str__(self):
1620
- return f"{self.namespace}.{self.identifier}@<{self.grain}>"
1613
+ return self.__repr__()
1621
1614
 
1622
1615
  def __hash__(self):
1623
1616
  return (self.namespace + self.identifier).__hash__()
@@ -1786,6 +1779,7 @@ class QueryDatasource(BaseModel):
1786
1779
  input_concepts: List[Concept]
1787
1780
  output_concepts: List[Concept]
1788
1781
  source_map: Dict[str, Set[Union[Datasource, "QueryDatasource", "UnnestJoin"]]]
1782
+
1789
1783
  datasources: List[Union[Datasource, "QueryDatasource"]]
1790
1784
  grain: Grain
1791
1785
  joins: List[BaseJoin | UnnestJoin]
@@ -1799,6 +1793,12 @@ class QueryDatasource(BaseModel):
1799
1793
  join_derived_concepts: List[Concept] = Field(default_factory=list)
1800
1794
  hidden_concepts: List[Concept] = Field(default_factory=list)
1801
1795
  force_group: bool | None = None
1796
+ existence_source_map: Dict[str, Set[Union[Datasource, "QueryDatasource"]]] = Field(
1797
+ default_factory=dict
1798
+ )
1799
+
1800
+ def __repr__(self):
1801
+ return f"{self.identifier}@<{self.grain}>"
1802
1802
 
1803
1803
  @property
1804
1804
  def non_partial_concept_addresses(self) -> List[str]:
@@ -1841,14 +1841,14 @@ class QueryDatasource(BaseModel):
1841
1841
  for k, _ in v.items():
1842
1842
  seen.add(k)
1843
1843
  for x in expected:
1844
- if x not in seen:
1844
+ if x not in seen and CONFIG.validate_missing:
1845
1845
  raise SyntaxError(
1846
1846
  f"source map missing {x} on (expected {expected}, have {seen})"
1847
1847
  )
1848
1848
  return v
1849
1849
 
1850
1850
  def __str__(self):
1851
- return f"{self.identifier}@<{self.grain}>"
1851
+ return self.__repr__()
1852
1852
 
1853
1853
  def __hash__(self):
1854
1854
  return (self.identifier).__hash__()
@@ -1941,6 +1941,9 @@ class QueryDatasource(BaseModel):
1941
1941
  ),
1942
1942
  join_derived_concepts=self.join_derived_concepts,
1943
1943
  force_group=self.force_group,
1944
+ hidden_concepts=unique(
1945
+ self.hidden_concepts + other.hidden_concepts, "address"
1946
+ ),
1944
1947
  )
1945
1948
 
1946
1949
  return qds
@@ -2007,10 +2010,11 @@ class CTE(BaseModel):
2007
2010
  name: str
2008
2011
  source: "QueryDatasource"
2009
2012
  output_columns: List[Concept]
2010
- source_map: Dict[str, str | list[str]]
2013
+ source_map: Dict[str, list[str]]
2011
2014
  grain: Grain
2012
2015
  base: bool = False
2013
2016
  group_to_grain: bool = False
2017
+ existence_source_map: Dict[str, list[str]] = Field(default_factory=dict)
2014
2018
  parent_ctes: List["CTE"] = Field(default_factory=list)
2015
2019
  joins: List[Union["Join", "InstantiatedUnnestJoin"]] = Field(default_factory=list)
2016
2020
  condition: Optional[Union["Conditional", "Comparison", "Parenthetical"]] = None
@@ -2021,6 +2025,7 @@ class CTE(BaseModel):
2021
2025
  limit: Optional[int] = None
2022
2026
  requires_nesting: bool = True
2023
2027
  base_name_override: Optional[str] = None
2028
+ base_alias_override: Optional[str] = None
2024
2029
 
2025
2030
  @computed_field # type: ignore
2026
2031
  @property
@@ -2031,7 +2036,7 @@ class CTE(BaseModel):
2031
2036
  def validate_output_columns(cls, v):
2032
2037
  return unique(v, "address")
2033
2038
 
2034
- def inline_parent_datasource(self, parent: CTE) -> bool:
2039
+ def inline_parent_datasource(self, parent: CTE, force_group: bool = False) -> bool:
2035
2040
  qds_being_inlined = parent.source
2036
2041
  ds_being_inlined = qds_being_inlined.datasources[0]
2037
2042
  if not isinstance(ds_being_inlined, Datasource):
@@ -2047,6 +2052,7 @@ class CTE(BaseModel):
2047
2052
  # need to identify this before updating joins
2048
2053
  if self.base_name == parent.name:
2049
2054
  self.base_name_override = ds_being_inlined.safe_location
2055
+ self.base_alias_override = ds_being_inlined.identifier
2050
2056
 
2051
2057
  for join in self.joins:
2052
2058
  if isinstance(join, InstantiatedUnnestJoin):
@@ -2063,6 +2069,8 @@ class CTE(BaseModel):
2063
2069
  elif v == parent.name:
2064
2070
  self.source_map[k] = ds_being_inlined.name
2065
2071
  self.parent_ctes = [x for x in self.parent_ctes if x.name != parent.name]
2072
+ if force_group:
2073
+ self.group_to_grain = True
2066
2074
  return True
2067
2075
 
2068
2076
  def __add__(self, other: "CTE"):
@@ -2101,6 +2109,9 @@ class CTE(BaseModel):
2101
2109
  self.source.output_concepts = unique(
2102
2110
  self.source.output_concepts + other.source.output_concepts, "address"
2103
2111
  )
2112
+ self.hidden_concepts = unique(
2113
+ self.hidden_concepts + other.hidden_concepts, "address"
2114
+ )
2104
2115
  return self
2105
2116
 
2106
2117
  @property
@@ -2120,9 +2131,6 @@ class CTE(BaseModel):
2120
2131
  if self.base_name_override:
2121
2132
  return self.base_name_override
2122
2133
  # if this cte selects from a single datasource, select right from it
2123
- valid_joins: List[Join] = [
2124
- join for join in self.joins if isinstance(join, Join)
2125
- ]
2126
2134
  if self.is_root_datasource:
2127
2135
  return self.source.datasources[0].safe_location
2128
2136
 
@@ -2130,33 +2138,16 @@ class CTE(BaseModel):
2130
2138
  # as the root
2131
2139
  elif len(self.source.datasources) == 1 and len(self.parent_ctes) == 1:
2132
2140
  return self.parent_ctes[0].name
2133
- elif valid_joins and len(valid_joins) > 0:
2134
- candidates = [x.left_cte.name for x in valid_joins]
2135
- disallowed = [x.right_cte.name for x in valid_joins]
2136
- try:
2137
- return [y for y in candidates if y not in disallowed][0]
2138
- except IndexError:
2139
- raise SyntaxError(
2140
- f"Invalid join configuration {candidates} {disallowed} with all parents {[x.base_name for x in self.parent_ctes]}"
2141
- )
2142
2141
  elif self.relevant_base_ctes:
2143
2142
  return self.relevant_base_ctes[0].name
2144
- elif self.parent_ctes:
2145
- raise SyntaxError(
2146
- f"{self.name} has no relevant base CTEs, {self.source_map},"
2147
- f" {[x.name for x in self.parent_ctes]}, outputs"
2148
- f" {[x.address for x in self.output_columns]}"
2149
- )
2150
2143
  return self.source.name
2151
2144
 
2152
2145
  @property
2153
2146
  def base_alias(self) -> str:
2154
-
2147
+ if self.base_alias_override:
2148
+ return self.base_alias_override
2155
2149
  if self.is_root_datasource:
2156
2150
  return self.source.datasources[0].identifier
2157
- relevant_joins = [j for j in self.joins if isinstance(j, Join)]
2158
- if relevant_joins:
2159
- return relevant_joins[0].left_cte.name
2160
2151
  elif self.relevant_base_ctes:
2161
2152
  return self.relevant_base_ctes[0].name
2162
2153
  elif self.parent_ctes:
@@ -2486,9 +2477,17 @@ class Environment(BaseModel):
2486
2477
  for datasource in self.datasources.values():
2487
2478
  for concept in datasource.output_concepts:
2488
2479
  concrete_addresses.add(concept.address)
2480
+ current_mat = [x.address for x in self.materialized_concepts]
2489
2481
  self.materialized_concepts = [
2490
2482
  c for c in self.concepts.values() if c.address in concrete_addresses
2491
2483
  ]
2484
+ new = [
2485
+ x.address
2486
+ for x in self.materialized_concepts
2487
+ if x.address not in current_mat
2488
+ ]
2489
+ if new:
2490
+ logger.info(f"Environment added new materialized concepts {new}")
2492
2491
  for concept in self.concepts.values():
2493
2492
  if concept.derivation == PurposeLineage.MERGE:
2494
2493
  ms = concept.lineage
@@ -2647,6 +2646,17 @@ class Environment(BaseModel):
2647
2646
  self.gen_concept_list_caches()
2648
2647
  return datasource
2649
2648
 
2649
+ def delete_datasource(
2650
+ self,
2651
+ address: str,
2652
+ meta: Meta | None = None,
2653
+ ) -> bool:
2654
+ if address in self.datasources:
2655
+ del self.datasources[address]
2656
+ self.gen_concept_list_caches()
2657
+ return True
2658
+ return False
2659
+
2650
2660
 
2651
2661
  class LazyEnvironment(Environment):
2652
2662
  """Variant of environment to defer parsing of a path"""
@@ -2728,6 +2738,9 @@ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2728
2738
  def __repr__(self):
2729
2739
  return f"{str(self.left)} {self.operator.value} {str(self.right)}"
2730
2740
 
2741
+ def __str__(self):
2742
+ return self.__repr__()
2743
+
2731
2744
  def with_namespace(self, namespace: str):
2732
2745
  return self.__class__(
2733
2746
  left=(
@@ -2750,11 +2763,8 @@ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2750
2763
  if isinstance(self.left, SelectGrain)
2751
2764
  else self.left
2752
2765
  ),
2753
- right=(
2754
- self.right.with_select_grain(grain)
2755
- if isinstance(self.right, SelectGrain)
2756
- else self.right
2757
- ),
2766
+ # the right side does NOT need to inherit select grain
2767
+ right=self.right,
2758
2768
  operator=self.operator,
2759
2769
  )
2760
2770
 
@@ -2800,8 +2810,8 @@ class SubselectComparison(Comparison):
2800
2810
  return get_concept_arguments(self.left)
2801
2811
 
2802
2812
  @property
2803
- def existence_arguments(self) -> List[Concept]:
2804
- return get_concept_arguments(self.right)
2813
+ def existence_arguments(self) -> list[tuple["Concept", ...]]:
2814
+ return [tuple(get_concept_arguments(self.right))]
2805
2815
 
2806
2816
  def with_select_grain(self, grain: Grain):
2807
2817
  # there's no need to pass the select grain through to a subselect comparison
@@ -2993,18 +3003,26 @@ class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2993
3003
  return output
2994
3004
 
2995
3005
  @property
2996
- def existence_arguments(self) -> List[Concept]:
3006
+ def existence_arguments(self) -> list[tuple["Concept", ...]]:
2997
3007
  output = []
2998
3008
  if isinstance(self.left, ConceptArgs):
2999
3009
  output += self.left.existence_arguments
3000
- else:
3001
- output += get_concept_arguments(self.left)
3002
3010
  if isinstance(self.right, ConceptArgs):
3003
3011
  output += self.right.existence_arguments
3004
- else:
3005
- output += get_concept_arguments(self.right)
3006
3012
  return output
3007
3013
 
3014
+ def decompose(self):
3015
+ chunks = []
3016
+ if self.operator == BooleanOperator.AND:
3017
+ for val in [self.left, self.right]:
3018
+ if isinstance(val, Conditional):
3019
+ chunks.extend(val.decompose())
3020
+ else:
3021
+ chunks.append(val)
3022
+ else:
3023
+ chunks.append(self)
3024
+ return chunks
3025
+
3008
3026
 
3009
3027
  class AggregateWrapper(Namespaced, SelectGrain, BaseModel):
3010
3028
  function: Function
@@ -3064,7 +3082,7 @@ class WhereClause(ConceptArgs, Namespaced, SelectGrain, BaseModel):
3064
3082
  return self.conditional.row_arguments
3065
3083
 
3066
3084
  @property
3067
- def existence_arguments(self) -> List[Concept]:
3085
+ def existence_arguments(self) -> list[tuple["Concept", ...]]:
3068
3086
  return self.conditional.existence_arguments
3069
3087
 
3070
3088
  def with_namespace(self, namespace: str) -> WhereClause:
@@ -3305,10 +3323,10 @@ class Parenthetical(ConceptArgs, Namespaced, SelectGrain, BaseModel):
3305
3323
  return self.concept_arguments
3306
3324
 
3307
3325
  @property
3308
- def existence_arguments(self) -> List[Concept]:
3326
+ def existence_arguments(self) -> list[tuple["Concept", ...]]:
3309
3327
  if isinstance(self.content, ConceptArgs):
3310
3328
  return self.content.existence_arguments
3311
- return self.concept_arguments
3329
+ return []
3312
3330
 
3313
3331
  @property
3314
3332
  def input(self):
@@ -3377,6 +3395,12 @@ Function.model_rebuild()
3377
3395
  Grain.model_rebuild()
3378
3396
 
3379
3397
 
3398
+ def list_to_wrapper(args):
3399
+ types = [arg_to_datatype(arg) for arg in args]
3400
+ assert len(set(types)) == 1
3401
+ return ListWrapper(args, type=types[0])
3402
+
3403
+
3380
3404
  def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType:
3381
3405
  if isinstance(arg, Function):
3382
3406
  return arg.output_datatype
@@ -3400,5 +3424,8 @@ def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType:
3400
3424
  if arg.type in (WindowType.RANK, WindowType.ROW_NUMBER):
3401
3425
  return DataType.INTEGER
3402
3426
  return arg_to_datatype(arg.content)
3427
+ elif isinstance(arg, list):
3428
+ wrapper = list_to_wrapper(arg)
3429
+ return ListType(type=wrapper.type)
3403
3430
  else:
3404
3431
  raise ValueError(f"Cannot parse arg datatype for arg of raw type {type(arg)}")