pytrilogy 0.0.2.9__py3-none-any.whl → 0.0.2.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.2.9.dist-info → pytrilogy-0.0.2.11.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.9.dist-info → pytrilogy-0.0.2.11.dist-info}/RECORD +20 -20
- trilogy/__init__.py +1 -1
- trilogy/constants.py +1 -1
- trilogy/core/models.py +43 -10
- trilogy/core/processing/concept_strategies_v3.py +2 -0
- trilogy/core/processing/node_generators/filter_node.py +27 -5
- trilogy/core/processing/node_generators/group_node.py +2 -0
- trilogy/core/processing/node_generators/node_merge_node.py +6 -1
- trilogy/core/processing/nodes/group_node.py +1 -5
- trilogy/core/processing/utility.py +18 -5
- trilogy/core/query_processor.py +12 -4
- trilogy/dialect/base.py +18 -15
- trilogy/dialect/duckdb.py +1 -1
- trilogy/parsing/parse_engine.py +60 -15
- trilogy/parsing/trilogy.lark +5 -1
- {pytrilogy-0.0.2.9.dist-info → pytrilogy-0.0.2.11.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.9.dist-info → pytrilogy-0.0.2.11.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.9.dist-info → pytrilogy-0.0.2.11.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.9.dist-info → pytrilogy-0.0.2.11.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=rSlG7hlvpZjtLdU_7j6ldvO5bmiafhgPB40sdcOu3j0,291
|
|
2
2
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
trilogy/constants.py,sha256=
|
|
3
|
+
trilogy/constants.py,sha256=HRQq4i3cpSEJCywt61QKEzRO1jd4tEPZNSBuxUA_7yg,922
|
|
4
4
|
trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
|
|
5
5
|
trilogy/executor.py,sha256=5cRbU4Rj7p1pNV76rfp1pz704Hx_0q8_O8HFURjgXxQ,11016
|
|
6
6
|
trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
|
|
@@ -16,26 +16,26 @@ trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,56
|
|
|
16
16
|
trilogy/core/functions.py,sha256=ARJAyBjeS415-54k3G_bx807rkPZonEulMaLRxSP7vU,10371
|
|
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=
|
|
19
|
+
trilogy/core/models.py,sha256=rIeYw173UjfTfc7194y-jqv7u51Uy6LufwbiwKI2Cgs,143834
|
|
20
20
|
trilogy/core/optimization.py,sha256=RJmDr2f9hyFePF-B6LpmHkj69-AzUm-nNtvO59899O8,6601
|
|
21
|
-
trilogy/core/query_processor.py,sha256=
|
|
21
|
+
trilogy/core/query_processor.py,sha256=50FHJ9Rw89ZNBflZK8R9jn7O_WdDHplS1hZSNXCozN8,19352
|
|
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=kHNyc2UoaPVdYfVAPAFwnWuk4sJ_IF5faRtVcDOrBtw,1110
|
|
25
25
|
trilogy/core/optimizations/inline_datasource.py,sha256=AATzQ6YrtW_1-aQFjQyTYqEYKBoMFhek7ADfBr4uUdQ,3634
|
|
26
26
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=4Y6zfJN3VVexkD6p9IYWN4BTL8RwW6hyNb0VQZ8ETdI,8020
|
|
27
27
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
28
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=5yZQRWlEyNR2sddq7kx12_PRUNhekEu6bWO2yIvjjUA,25511
|
|
29
29
|
trilogy/core/processing/graph_utils.py,sha256=aq-kqk4Iado2HywDxWEejWc-7PGO6Oa-ZQLAM6XWPHw,1199
|
|
30
|
-
trilogy/core/processing/utility.py,sha256=
|
|
30
|
+
trilogy/core/processing/utility.py,sha256=15Qi68ktmnQmMt5jxrpueQ34oG5fSSgst3kIEejwF8A,14519
|
|
31
31
|
trilogy/core/processing/node_generators/__init__.py,sha256=-mzYkRsaRNa_dfTckYkKVFSR8h8a3ihEiPJDU_tAmDo,672
|
|
32
32
|
trilogy/core/processing/node_generators/basic_node.py,sha256=4242PNGTCm2tklqMIkqVu5Iv4m_IeTnOYXxDveuCDZM,2856
|
|
33
33
|
trilogy/core/processing/node_generators/common.py,sha256=lDBRq9X6dQ_xSwXxLLNDq2pW8D-XwAY-ylTJLMugkLw,9525
|
|
34
|
-
trilogy/core/processing/node_generators/filter_node.py,sha256=
|
|
35
|
-
trilogy/core/processing/node_generators/group_node.py,sha256=
|
|
34
|
+
trilogy/core/processing/node_generators/filter_node.py,sha256=Ij2WqyOsu-TFxhAcL50PLMGpghsSWXJnWEJ8yTqOwrY,8228
|
|
35
|
+
trilogy/core/processing/node_generators/group_node.py,sha256=Du-9uFXD0M-aHq2MV7v5R3QCrAL0JZBFMW-YQwgb6Bw,3135
|
|
36
36
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=nzITnhaALIT7FMonyo16nNo-kSrLfefa9sZBYecrvkU,2887
|
|
37
37
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=vP84dnLQy6dtypi6mUbt9sMAcmmrTgQ1Oz4GI6X1IEo,6421
|
|
38
|
-
trilogy/core/processing/node_generators/node_merge_node.py,sha256=
|
|
38
|
+
trilogy/core/processing/node_generators/node_merge_node.py,sha256=qS8VKppi10NKfqNb7BXTKyLBBn4wJuMmFYhrtMUkYb8,12580
|
|
39
39
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=6KVnuk75mRzWJ-jIk7e8azN8BIPPuCn-VxPlxDqfPVE,4616
|
|
40
40
|
trilogy/core/processing/node_generators/select_node.py,sha256=E8bKOAUpwLwZy1iiaFVD5sM4XK-eFpHgijdyIWLMyH4,18904
|
|
41
41
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=gHjurhr86JFkbq6vxTHDQGDKt95EdotSFHVKgrR_Z3Q,1860
|
|
@@ -43,17 +43,17 @@ trilogy/core/processing/node_generators/window_node.py,sha256=lFfmEjX_mLB7MuOM6C
|
|
|
43
43
|
trilogy/core/processing/nodes/__init__.py,sha256=jyduHk96j5fpju72sc8swOiBjR3Md866kt8JZGkp3ZU,4866
|
|
44
44
|
trilogy/core/processing/nodes/base_node.py,sha256=KVLAPLlo5QQq9Cm6lTLsqLN7l8EyXGxQkKrZFHdyRp8,12871
|
|
45
45
|
trilogy/core/processing/nodes/filter_node.py,sha256=DBOSGFfkiILrZa1BlLv2uxUSkgWtSIKiZplqyKXPjg8,2132
|
|
46
|
-
trilogy/core/processing/nodes/group_node.py,sha256=
|
|
46
|
+
trilogy/core/processing/nodes/group_node.py,sha256=RKqODYxbY8wmClvI8-k3UgXWL7qQv5Hxhz5AB-RVUu0,6344
|
|
47
47
|
trilogy/core/processing/nodes/merge_node.py,sha256=ZUcFIm5v4hip_Ml_oJXqVMrevUkE62wvPrvCrO9FWAc,14272
|
|
48
48
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=QuXNcwgjTRYamOoIooGrp4ie6INcqA9whtC5LZWjD8s,7180
|
|
49
49
|
trilogy/core/processing/nodes/unnest_node.py,sha256=JFtm90IVM-46aCYkTNIaJah6v9ApAfonjVhcVM1HmDE,1903
|
|
50
50
|
trilogy/core/processing/nodes/window_node.py,sha256=X7qxLUKd3tekjUUsmH_4vz5b-U89gMnGd04VBxuu2Ns,1280
|
|
51
51
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
-
trilogy/dialect/base.py,sha256=
|
|
52
|
+
trilogy/dialect/base.py,sha256=xbZXcWvwdRadyb77J2qat67R_gRAmKeeeI4JRH4P928,30726
|
|
53
53
|
trilogy/dialect/bigquery.py,sha256=15KJ-cOpBlk9O7FPviPgmg8xIydJeKx7WfmL3SSsPE8,2953
|
|
54
54
|
trilogy/dialect/common.py,sha256=HVNPL8dGyQjT2REruV5C2YPpBbVR7KlD0akVM7GcuPI,3329
|
|
55
55
|
trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
|
|
56
|
-
trilogy/dialect/duckdb.py,sha256=
|
|
56
|
+
trilogy/dialect/duckdb.py,sha256=u_gpL35kouWxoBLas1h0ABYY2QzlVtEh22hm5h0lCOM,3182
|
|
57
57
|
trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
|
|
58
58
|
trilogy/dialect/postgres.py,sha256=ev1RJZsC8BB3vJSxJ4q-TTYqZ4Hk1NXUtuRkLrQEBX0,3254
|
|
59
59
|
trilogy/dialect/presto.py,sha256=2Rs53UfPxKU0rJTcEbiS-Lxm-CDiqUGojh7yRpQgyRE,3416
|
|
@@ -69,14 +69,14 @@ trilogy/parsing/common.py,sha256=zNd5buKxK4z9WSszOk4zOI0GexDFukzZtdNfQxg3kVw,621
|
|
|
69
69
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
70
70
|
trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
|
|
71
71
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
72
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
72
|
+
trilogy/parsing/parse_engine.py,sha256=riXOhMqYGvzyy1aeTZTjPIz3jOszue2VTEH7hn59zD4,63808
|
|
73
73
|
trilogy/parsing/render.py,sha256=Gy_6wVYPwYLf35Iota08sbqveuWILtUhI8MYStcvtJM,12174
|
|
74
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
74
|
+
trilogy/parsing/trilogy.lark,sha256=QNJnExOdvJyKTrQA4ffh-SGIz7rYd93kf2Ccs0m3cn4,11498
|
|
75
75
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
76
|
trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
|
|
77
|
-
pytrilogy-0.0.2.
|
|
78
|
-
pytrilogy-0.0.2.
|
|
79
|
-
pytrilogy-0.0.2.
|
|
80
|
-
pytrilogy-0.0.2.
|
|
81
|
-
pytrilogy-0.0.2.
|
|
82
|
-
pytrilogy-0.0.2.
|
|
77
|
+
pytrilogy-0.0.2.11.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
78
|
+
pytrilogy-0.0.2.11.dist-info/METADATA,sha256=mZ6V2gwihh9nv-bIGkimK2-bKNvsY_3JIUSNzP_m2Wg,7907
|
|
79
|
+
pytrilogy-0.0.2.11.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
80
|
+
pytrilogy-0.0.2.11.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
81
|
+
pytrilogy-0.0.2.11.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
82
|
+
pytrilogy-0.0.2.11.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/constants.py
CHANGED
trilogy/core/models.py
CHANGED
|
@@ -70,7 +70,7 @@ from trilogy.utility import unique
|
|
|
70
70
|
from collections import UserList, UserDict
|
|
71
71
|
from functools import cached_property
|
|
72
72
|
from abc import ABC
|
|
73
|
-
|
|
73
|
+
from collections import defaultdict
|
|
74
74
|
|
|
75
75
|
LOGGER_PREFIX = "[MODELS]"
|
|
76
76
|
|
|
@@ -801,15 +801,18 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
801
801
|
) -> "Concept":
|
|
802
802
|
from trilogy.utility import string_to_hash
|
|
803
803
|
|
|
804
|
-
|
|
804
|
+
if self.lineage and isinstance(self.lineage, FilterItem):
|
|
805
|
+
if self.lineage.where.conditional == condition:
|
|
806
|
+
return self
|
|
807
|
+
hash = string_to_hash(self.name + str(condition))
|
|
805
808
|
new = Concept(
|
|
806
|
-
name=f"{self.name}
|
|
809
|
+
name=f"{self.name}_filter_{hash}",
|
|
807
810
|
datatype=self.datatype,
|
|
808
811
|
purpose=self.purpose,
|
|
809
812
|
metadata=self.metadata,
|
|
810
813
|
lineage=FilterItem(content=self, where=WhereClause(conditional=condition)),
|
|
811
|
-
keys=None,
|
|
812
|
-
grain=
|
|
814
|
+
keys=(self.keys if self.purpose == Purpose.PROPERTY else None),
|
|
815
|
+
grain=self.grain if self.grain else Grain(components=[]),
|
|
813
816
|
namespace=self.namespace,
|
|
814
817
|
modifiers=self.modifiers,
|
|
815
818
|
pseudonyms=self.pseudonyms,
|
|
@@ -842,6 +845,16 @@ class Grain(Mergeable, BaseModel):
|
|
|
842
845
|
v2 = sorted(final, key=lambda x: x.name)
|
|
843
846
|
return v2
|
|
844
847
|
|
|
848
|
+
def with_filter(
|
|
849
|
+
self,
|
|
850
|
+
condition: "Conditional | Comparison | Parenthetical",
|
|
851
|
+
environment: Environment | None = None,
|
|
852
|
+
) -> "Grain":
|
|
853
|
+
return Grain(
|
|
854
|
+
components=[c.with_filter(condition, environment) for c in self.components],
|
|
855
|
+
nested=self.nested,
|
|
856
|
+
)
|
|
857
|
+
|
|
845
858
|
@property
|
|
846
859
|
def components_copy(self) -> List[Concept]:
|
|
847
860
|
return [*self.components]
|
|
@@ -1680,6 +1693,9 @@ class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
|
|
|
1680
1693
|
)
|
|
1681
1694
|
):
|
|
1682
1695
|
output.append(item)
|
|
1696
|
+
# TODO: explore implicit filtering more
|
|
1697
|
+
# if self.where_clause.conditional and self.where_clause_category == SelectFiltering.IMPLICIT:
|
|
1698
|
+
# output =[x.with_filter(self.where_clause.conditional) for x in output]
|
|
1683
1699
|
return Grain(
|
|
1684
1700
|
components=unique(output, "address"), where_clause=self.where_clause
|
|
1685
1701
|
)
|
|
@@ -2027,7 +2043,7 @@ class Datasource(Namespaced, BaseModel):
|
|
|
2027
2043
|
return self.__repr__()
|
|
2028
2044
|
|
|
2029
2045
|
def __hash__(self):
|
|
2030
|
-
return
|
|
2046
|
+
return self.full_name.__hash__()
|
|
2031
2047
|
|
|
2032
2048
|
def with_namespace(self, namespace: str):
|
|
2033
2049
|
new_namespace = (
|
|
@@ -2212,9 +2228,9 @@ class BaseJoin(BaseModel):
|
|
|
2212
2228
|
class QueryDatasource(BaseModel):
|
|
2213
2229
|
input_concepts: List[Concept]
|
|
2214
2230
|
output_concepts: List[Concept]
|
|
2231
|
+
datasources: List[Union[Datasource, "QueryDatasource"]]
|
|
2215
2232
|
source_map: Dict[str, Set[Union[Datasource, "QueryDatasource", "UnnestJoin"]]]
|
|
2216
2233
|
|
|
2217
|
-
datasources: List[Union[Datasource, "QueryDatasource"]]
|
|
2218
2234
|
grain: Grain
|
|
2219
2235
|
joins: List[BaseJoin | UnnestJoin]
|
|
2220
2236
|
limit: Optional[int] = None
|
|
@@ -2266,7 +2282,7 @@ class QueryDatasource(BaseModel):
|
|
|
2266
2282
|
|
|
2267
2283
|
@field_validator("source_map")
|
|
2268
2284
|
@classmethod
|
|
2269
|
-
def validate_source_map(cls, v, info: ValidationInfo):
|
|
2285
|
+
def validate_source_map(cls, v: dict, info: ValidationInfo):
|
|
2270
2286
|
values = info.data
|
|
2271
2287
|
for key in ("input_concepts", "output_concepts"):
|
|
2272
2288
|
if not values.get(key):
|
|
@@ -2344,11 +2360,23 @@ class QueryDatasource(BaseModel):
|
|
|
2344
2360
|
)
|
|
2345
2361
|
|
|
2346
2362
|
merged_datasources = {}
|
|
2363
|
+
|
|
2347
2364
|
for ds in [*self.datasources, *other.datasources]:
|
|
2348
2365
|
if ds.full_name in merged_datasources:
|
|
2349
2366
|
merged_datasources[ds.full_name] = merged_datasources[ds.full_name] + ds
|
|
2350
2367
|
else:
|
|
2351
2368
|
merged_datasources[ds.full_name] = ds
|
|
2369
|
+
|
|
2370
|
+
final_source_map = defaultdict(set)
|
|
2371
|
+
for key in self.source_map:
|
|
2372
|
+
final_source_map[key] = self.source_map[key].union(
|
|
2373
|
+
other.source_map.get(key, set())
|
|
2374
|
+
)
|
|
2375
|
+
for key in other.source_map:
|
|
2376
|
+
if key not in final_source_map:
|
|
2377
|
+
final_source_map[key] = other.source_map[key]
|
|
2378
|
+
for k, v in final_source_map.items():
|
|
2379
|
+
final_source_map[k] = set(merged_datasources[x.full_name] for x in list(v))
|
|
2352
2380
|
qds = QueryDatasource(
|
|
2353
2381
|
input_concepts=unique(
|
|
2354
2382
|
self.input_concepts + other.input_concepts, "address"
|
|
@@ -2356,7 +2384,7 @@ class QueryDatasource(BaseModel):
|
|
|
2356
2384
|
output_concepts=unique(
|
|
2357
2385
|
self.output_concepts + other.output_concepts, "address"
|
|
2358
2386
|
),
|
|
2359
|
-
source_map=
|
|
2387
|
+
source_map=final_source_map,
|
|
2360
2388
|
datasources=list(merged_datasources.values()),
|
|
2361
2389
|
grain=self.grain,
|
|
2362
2390
|
joins=unique(self.joins + other.joins, "unique_id"),
|
|
@@ -4062,8 +4090,13 @@ class RowsetDerivationStatement(Namespaced, BaseModel):
|
|
|
4062
4090
|
output: list[Concept] = []
|
|
4063
4091
|
orig: dict[str, Concept] = {}
|
|
4064
4092
|
for orig_concept in self.select.output_components:
|
|
4093
|
+
name = orig_concept.name
|
|
4094
|
+
if isinstance(orig_concept.lineage, FilterItem):
|
|
4095
|
+
if orig_concept.lineage.where == self.select.where_clause:
|
|
4096
|
+
name = orig_concept.lineage.content.name
|
|
4097
|
+
|
|
4065
4098
|
new_concept = Concept(
|
|
4066
|
-
name=
|
|
4099
|
+
name=name,
|
|
4067
4100
|
datatype=orig_concept.datatype,
|
|
4068
4101
|
purpose=orig_concept.purpose,
|
|
4069
4102
|
lineage=RowsetItem(
|
|
@@ -191,6 +191,7 @@ def generate_candidates_restrictive(
|
|
|
191
191
|
):
|
|
192
192
|
combos.append(local_candidates)
|
|
193
193
|
combos.append(grain_check)
|
|
194
|
+
# combos.append(local_candidates)
|
|
194
195
|
# append the empty set for sourcing concept by itself last
|
|
195
196
|
combos.append([])
|
|
196
197
|
return combos
|
|
@@ -645,6 +646,7 @@ def _search_concepts(
|
|
|
645
646
|
depth=depth,
|
|
646
647
|
source_concepts=search_concepts,
|
|
647
648
|
history=history,
|
|
649
|
+
search_conditions=conditions,
|
|
648
650
|
)
|
|
649
651
|
|
|
650
652
|
if expanded:
|
|
@@ -17,6 +17,7 @@ from trilogy.core.processing.node_generators.common import (
|
|
|
17
17
|
from trilogy.constants import logger
|
|
18
18
|
from trilogy.core.processing.utility import padding, unique
|
|
19
19
|
from trilogy.core.processing.node_generators.common import concept_to_relevant_joins
|
|
20
|
+
from trilogy.core.processing.utility import is_scalar_condition
|
|
20
21
|
|
|
21
22
|
LOGGER_PREFIX = "[GEN_FILTER_NODE]"
|
|
22
23
|
|
|
@@ -38,6 +39,15 @@ def gen_filter_node(
|
|
|
38
39
|
raise SyntaxError('Filter node must have a lineage of type "FilterItem"')
|
|
39
40
|
where = concept.lineage.where
|
|
40
41
|
|
|
42
|
+
optional_included: list[Concept] = []
|
|
43
|
+
for x in local_optional:
|
|
44
|
+
if isinstance(x.lineage, FilterItem):
|
|
45
|
+
if concept.lineage.where == where:
|
|
46
|
+
logger.info(
|
|
47
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching {x.lineage.content.address} as optional parent with same filter conditions "
|
|
48
|
+
)
|
|
49
|
+
parent_row_concepts.append(x.lineage.content)
|
|
50
|
+
optional_included.append(x)
|
|
41
51
|
logger.info(
|
|
42
52
|
f"{padding(depth)}{LOGGER_PREFIX} filter {concept.address} derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
|
|
43
53
|
)
|
|
@@ -48,6 +58,7 @@ def gen_filter_node(
|
|
|
48
58
|
g=g,
|
|
49
59
|
depth=depth + 1,
|
|
50
60
|
history=history,
|
|
61
|
+
conditions=conditions,
|
|
51
62
|
)
|
|
52
63
|
|
|
53
64
|
flattened_existence = [x for y in parent_existence_concepts for x in y]
|
|
@@ -78,16 +89,25 @@ def gen_filter_node(
|
|
|
78
89
|
return None
|
|
79
90
|
|
|
80
91
|
optimized_pushdown = False
|
|
81
|
-
if not
|
|
92
|
+
if not is_scalar_condition(where.conditional):
|
|
93
|
+
optimized_pushdown = False
|
|
94
|
+
elif not local_optional:
|
|
82
95
|
optimized_pushdown = True
|
|
83
96
|
elif conditions and conditions == where:
|
|
84
97
|
logger.info(
|
|
85
98
|
f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
|
|
86
99
|
)
|
|
87
100
|
optimized_pushdown = True
|
|
88
|
-
|
|
101
|
+
elif optional_included == local_optional:
|
|
102
|
+
logger.info(
|
|
103
|
+
f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
|
|
104
|
+
)
|
|
105
|
+
optimized_pushdown = True
|
|
89
106
|
if optimized_pushdown:
|
|
90
107
|
if isinstance(row_parent, SelectNode):
|
|
108
|
+
logger.info(
|
|
109
|
+
f"{padding(depth)}{LOGGER_PREFIX} nesting select node in strategy node"
|
|
110
|
+
)
|
|
91
111
|
parent = StrategyNode(
|
|
92
112
|
input_concepts=row_parent.output_concepts,
|
|
93
113
|
output_concepts=[concept] + row_parent.output_concepts,
|
|
@@ -111,6 +131,7 @@ def gen_filter_node(
|
|
|
111
131
|
x
|
|
112
132
|
for x in local_optional
|
|
113
133
|
if x.address in [y.address for y in parent.output_concepts]
|
|
134
|
+
or x.address in [y.address for y in optional_included]
|
|
114
135
|
]
|
|
115
136
|
parent.add_parents(core_parents)
|
|
116
137
|
parent.add_condition(where.conditional)
|
|
@@ -170,6 +191,7 @@ def gen_filter_node(
|
|
|
170
191
|
] + outputs
|
|
171
192
|
filter_node.rebuild_cache()
|
|
172
193
|
return filter_node
|
|
194
|
+
|
|
173
195
|
enrich_node = source_concepts( # this fetches the parent + join keys
|
|
174
196
|
# to then connect to the rest of the query
|
|
175
197
|
mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
|
|
@@ -177,10 +199,11 @@ def gen_filter_node(
|
|
|
177
199
|
g=g,
|
|
178
200
|
depth=depth + 1,
|
|
179
201
|
history=history,
|
|
202
|
+
conditions=conditions,
|
|
180
203
|
)
|
|
181
204
|
if not enrich_node:
|
|
182
205
|
return filter_node
|
|
183
|
-
|
|
206
|
+
return MergeNode(
|
|
184
207
|
input_concepts=[concept, immediate_parent] + local_optional,
|
|
185
208
|
output_concepts=[
|
|
186
209
|
concept,
|
|
@@ -201,8 +224,7 @@ def gen_filter_node(
|
|
|
201
224
|
[immediate_parent] + parent_row_concepts
|
|
202
225
|
),
|
|
203
226
|
join_type=JoinType.LEFT_OUTER,
|
|
204
|
-
filter_to_mutual=
|
|
227
|
+
filter_to_mutual=True,
|
|
205
228
|
)
|
|
206
229
|
],
|
|
207
230
|
)
|
|
208
|
-
return x
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import List, Optional
|
|
2
2
|
|
|
3
|
-
from trilogy.core.models import Concept, Environment, Conditional
|
|
3
|
+
from trilogy.core.models import Concept, Environment, Conditional, WhereClause
|
|
4
4
|
from trilogy.core.processing.nodes import MergeNode, History, StrategyNode
|
|
5
5
|
import networkx as nx
|
|
6
6
|
from trilogy.core.graph_models import concept_to_node
|
|
@@ -260,6 +260,7 @@ def subgraphs_to_merge_node(
|
|
|
260
260
|
source_concepts,
|
|
261
261
|
history,
|
|
262
262
|
conditions,
|
|
263
|
+
search_conditions: WhereClause | None = None,
|
|
263
264
|
enable_early_exit: bool = True,
|
|
264
265
|
):
|
|
265
266
|
parents: List[StrategyNode] = []
|
|
@@ -277,6 +278,7 @@ def subgraphs_to_merge_node(
|
|
|
277
278
|
g=g,
|
|
278
279
|
depth=depth + 1,
|
|
279
280
|
history=history,
|
|
281
|
+
conditions=search_conditions,
|
|
280
282
|
)
|
|
281
283
|
if not parent:
|
|
282
284
|
logger.info(
|
|
@@ -315,6 +317,7 @@ def gen_merge_node(
|
|
|
315
317
|
accept_partial: bool = False,
|
|
316
318
|
history: History | None = None,
|
|
317
319
|
conditions: Conditional | None = None,
|
|
320
|
+
search_conditions: WhereClause | None = None,
|
|
318
321
|
) -> Optional[MergeNode]:
|
|
319
322
|
|
|
320
323
|
for filter_downstream in [True, False]:
|
|
@@ -339,6 +342,7 @@ def gen_merge_node(
|
|
|
339
342
|
source_concepts=source_concepts,
|
|
340
343
|
history=history,
|
|
341
344
|
conditions=conditions,
|
|
345
|
+
search_conditions=search_conditions,
|
|
342
346
|
)
|
|
343
347
|
# one concept handling may need to be kicked to alias
|
|
344
348
|
if len(all_concepts) == 1:
|
|
@@ -354,6 +358,7 @@ def gen_merge_node(
|
|
|
354
358
|
history=history,
|
|
355
359
|
conditions=conditions,
|
|
356
360
|
enable_early_exit=False,
|
|
361
|
+
search_conditions=search_conditions,
|
|
357
362
|
)
|
|
358
363
|
if test:
|
|
359
364
|
return test
|
|
@@ -130,7 +130,7 @@ class GroupNode(StrategyNode):
|
|
|
130
130
|
if self.conditions
|
|
131
131
|
else self.output_concepts
|
|
132
132
|
),
|
|
133
|
-
inherited_inputs=self.input_concepts,
|
|
133
|
+
inherited_inputs=self.input_concepts + self.existence_concepts,
|
|
134
134
|
),
|
|
135
135
|
joins=[],
|
|
136
136
|
grain=grain,
|
|
@@ -139,10 +139,6 @@ class GroupNode(StrategyNode):
|
|
|
139
139
|
)
|
|
140
140
|
# if there is a condition on a group node and it's not scalar
|
|
141
141
|
# inject an additional CTE
|
|
142
|
-
if self.conditions:
|
|
143
|
-
logger.info("CONDITIONS")
|
|
144
|
-
logger.info(str(self.conditions))
|
|
145
|
-
logger.info(is_scalar_condition(self.conditions))
|
|
146
142
|
if self.conditions and not is_scalar_condition(self.conditions):
|
|
147
143
|
base.condition = None
|
|
148
144
|
base.output_concepts = self.output_concepts + self.conditions.row_arguments
|
|
@@ -355,27 +355,40 @@ def is_scalar_condition(
|
|
|
355
355
|
| MagicConstants
|
|
356
356
|
| DataType
|
|
357
357
|
),
|
|
358
|
+
materialized: set[str] | None = None,
|
|
358
359
|
) -> bool:
|
|
359
360
|
if isinstance(element, Parenthetical):
|
|
360
|
-
return is_scalar_condition(element.content)
|
|
361
|
+
return is_scalar_condition(element.content, materialized)
|
|
361
362
|
elif isinstance(element, SubselectComparison):
|
|
362
363
|
return True
|
|
363
364
|
elif isinstance(element, Comparison):
|
|
364
|
-
return is_scalar_condition(element.left) and is_scalar_condition(
|
|
365
|
+
return is_scalar_condition(element.left, materialized) and is_scalar_condition(
|
|
366
|
+
element.right, materialized
|
|
367
|
+
)
|
|
365
368
|
elif isinstance(element, Function):
|
|
366
369
|
if element.operator in FunctionClass.AGGREGATE_FUNCTIONS.value:
|
|
367
370
|
return False
|
|
371
|
+
elif isinstance(element, Concept):
|
|
372
|
+
if materialized and element.address in materialized:
|
|
373
|
+
return True
|
|
374
|
+
if element.lineage and isinstance(element.lineage, AggregateWrapper):
|
|
375
|
+
return is_scalar_condition(element.lineage, materialized)
|
|
376
|
+
return True
|
|
368
377
|
elif isinstance(element, AggregateWrapper):
|
|
369
|
-
return is_scalar_condition(element.function)
|
|
378
|
+
return is_scalar_condition(element.function, materialized)
|
|
370
379
|
elif isinstance(element, Conditional):
|
|
371
|
-
return is_scalar_condition(element.left) and is_scalar_condition(
|
|
380
|
+
return is_scalar_condition(element.left, materialized) and is_scalar_condition(
|
|
381
|
+
element.right, materialized
|
|
382
|
+
)
|
|
372
383
|
return True
|
|
373
384
|
|
|
374
385
|
|
|
375
386
|
def decompose_condition(
|
|
376
|
-
conditional: Conditional,
|
|
387
|
+
conditional: Conditional | Comparison | Parenthetical,
|
|
377
388
|
) -> list[SubselectComparison | Comparison | Conditional | Parenthetical]:
|
|
378
389
|
chunks: list[SubselectComparison | Comparison | Conditional | Parenthetical] = []
|
|
390
|
+
if not isinstance(conditional, Conditional):
|
|
391
|
+
return [conditional]
|
|
379
392
|
if conditional.operator == BooleanOperator.AND:
|
|
380
393
|
if not (
|
|
381
394
|
isinstance(
|
trilogy/core/query_processor.py
CHANGED
|
@@ -145,7 +145,9 @@ def generate_source_map(
|
|
|
145
145
|
names = set([x.name for x in ev])
|
|
146
146
|
ematches = [cte.name for cte in all_new_ctes if cte.source.name in names]
|
|
147
147
|
existence_source_map[ek] = ematches
|
|
148
|
-
return {
|
|
148
|
+
return {
|
|
149
|
+
k: [] if not v else list(set(v)) for k, v in source_map.items()
|
|
150
|
+
}, existence_source_map
|
|
149
151
|
|
|
150
152
|
|
|
151
153
|
def datasource_to_query_datasource(datasource: Datasource) -> QueryDatasource:
|
|
@@ -191,6 +193,8 @@ def resolve_cte_base_name_and_alias_v2(
|
|
|
191
193
|
raw_joins: List[Join | InstantiatedUnnestJoin],
|
|
192
194
|
) -> Tuple[str | None, str | None]:
|
|
193
195
|
joins: List[Join] = [join for join in raw_joins if isinstance(join, Join)]
|
|
196
|
+
# INFO trilogy:query_processor.py:263 Finished building source map for civet with 3 parents, have {'local.relevant_customers': ['fowl', 'fowl'],
|
|
197
|
+
# 'customer.demographics.gender': ['mandrill'], 'customer.id': ['mandrill'], 'customer.demographics.id': ['mandrill'], 'customer.id_9268029262289908': [], 'customer.demographics.gender_1513806568509111': []}, query_datasource had non-empty keys ['local.relevant_customers', 'customer.demographics.gender', 'customer.id', 'customer.demographics.id'] and existence had non-empty keys []
|
|
194
198
|
if (
|
|
195
199
|
len(source.datasources) == 1
|
|
196
200
|
and isinstance(source.datasources[0], Datasource)
|
|
@@ -212,12 +216,16 @@ def resolve_cte_base_name_and_alias_v2(
|
|
|
212
216
|
|
|
213
217
|
counts: dict[str, int] = defaultdict(lambda: 0)
|
|
214
218
|
output_addresses = [x.address for x in source.output_concepts]
|
|
219
|
+
input_address = [x.address for x in source.input_concepts]
|
|
215
220
|
for k, v in source_map.items():
|
|
216
221
|
for vx in v:
|
|
217
222
|
if k in output_addresses:
|
|
218
223
|
counts[vx] = counts[vx] + 1
|
|
219
|
-
|
|
220
|
-
|
|
224
|
+
|
|
225
|
+
if k in input_address:
|
|
226
|
+
counts[vx] = counts[vx] + 1
|
|
227
|
+
|
|
228
|
+
counts[vx] = counts[vx]
|
|
221
229
|
if counts:
|
|
222
230
|
return max(counts, key=counts.get), max(counts, key=counts.get) # type: ignore
|
|
223
231
|
return None, None
|
|
@@ -261,7 +269,7 @@ def datasource_to_ctes(
|
|
|
261
269
|
|
|
262
270
|
human_id = generate_cte_name(query_datasource.full_name, name_map)
|
|
263
271
|
logger.info(
|
|
264
|
-
f"Finished building source map for {human_id} with {len(parents)} parents, have {source_map}, query_datasource had non-empty keys {[k for k, v in query_datasource.source_map.items() if v]} "
|
|
272
|
+
f"Finished building source map for {human_id} with {len(parents)} parents, have {source_map}, query_datasource had non-empty keys {[k for k, v in query_datasource.source_map.items() if v]} and existence had non-empty keys {[k for k, v in query_datasource.existence_source_map.items() if v]} "
|
|
265
273
|
)
|
|
266
274
|
final_joins = [
|
|
267
275
|
x
|
trilogy/dialect/base.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import List, Union, Optional, Dict, Any, Sequence, Callable
|
|
|
2
2
|
|
|
3
3
|
from jinja2 import Template
|
|
4
4
|
|
|
5
|
-
from trilogy.core.processing.utility import is_scalar_condition
|
|
5
|
+
from trilogy.core.processing.utility import is_scalar_condition, decompose_condition
|
|
6
6
|
from trilogy.constants import CONFIG, logger, MagicConstants
|
|
7
7
|
from trilogy.core.internal import DEFAULT_CONCEPTS
|
|
8
8
|
from trilogy.core.enums import (
|
|
@@ -281,10 +281,7 @@ class BaseDialect:
|
|
|
281
281
|
rval = f"{self.WINDOW_FUNCTION_MAP[c.lineage.type](concept = self.render_concept_sql(c.lineage.content, cte=cte, alias=False), window=','.join(rendered_over_components), sort=','.join(rendered_order_components))}" # noqa: E501
|
|
282
282
|
elif isinstance(c.lineage, FilterItem):
|
|
283
283
|
# for cases when we've optimized this
|
|
284
|
-
if
|
|
285
|
-
len(cte.output_columns) == 1
|
|
286
|
-
and cte.condition == c.lineage.where.conditional
|
|
287
|
-
):
|
|
284
|
+
if cte.condition == c.lineage.where.conditional:
|
|
288
285
|
rval = self.render_expr(c.lineage.content, cte=cte)
|
|
289
286
|
else:
|
|
290
287
|
rval = f"CASE WHEN {self.render_expr(c.lineage.where.conditional, cte=cte)} THEN {self.render_concept_sql(c.lineage.content, cte=cte, alias=False)} ELSE NULL END"
|
|
@@ -538,6 +535,20 @@ class BaseDialect:
|
|
|
538
535
|
final_joins = []
|
|
539
536
|
else:
|
|
540
537
|
final_joins = cte.joins or []
|
|
538
|
+
where: Conditional | Parenthetical | Comparison | None = None
|
|
539
|
+
having: Conditional | Parenthetical | Comparison | None = None
|
|
540
|
+
materialized = {x for x, v in cte.source_map.items() if v}
|
|
541
|
+
if cte.condition:
|
|
542
|
+
if is_scalar_condition(cte.condition, materialized=materialized):
|
|
543
|
+
where = cte.condition
|
|
544
|
+
else:
|
|
545
|
+
components = decompose_condition(cte.condition)
|
|
546
|
+
for x in components:
|
|
547
|
+
if is_scalar_condition(x, materialized=materialized):
|
|
548
|
+
where = where + x if where else x
|
|
549
|
+
else:
|
|
550
|
+
having = having + x if having else x
|
|
551
|
+
|
|
541
552
|
return CompiledCTE(
|
|
542
553
|
name=cte.name,
|
|
543
554
|
statement=self.SQL_TEMPLATE.render(
|
|
@@ -561,16 +572,8 @@ class BaseDialect:
|
|
|
561
572
|
]
|
|
562
573
|
if j
|
|
563
574
|
],
|
|
564
|
-
where=(
|
|
565
|
-
|
|
566
|
-
if cte.condition and is_scalar_condition(cte.condition)
|
|
567
|
-
else None
|
|
568
|
-
),
|
|
569
|
-
having=(
|
|
570
|
-
self.render_expr(cte.condition, cte)
|
|
571
|
-
if cte.condition and not is_scalar_condition(cte.condition)
|
|
572
|
-
else None
|
|
573
|
-
),
|
|
575
|
+
where=(self.render_expr(where, cte) if where else None),
|
|
576
|
+
having=(self.render_expr(having, cte) if having else None),
|
|
574
577
|
order_by=(
|
|
575
578
|
[self.render_order_item(i, cte) for i in cte.order_by.items]
|
|
576
579
|
if cte.order_by
|
trilogy/dialect/duckdb.py
CHANGED
|
@@ -36,7 +36,7 @@ FUNCTION_MAP = {
|
|
|
36
36
|
# we may return a static value
|
|
37
37
|
FUNCTION_GRAIN_MATCH_MAP = {
|
|
38
38
|
**FUNCTION_MAP,
|
|
39
|
-
FunctionType.COUNT: lambda args: "
|
|
39
|
+
FunctionType.COUNT: lambda args: f"{args[0]}",
|
|
40
40
|
FunctionType.SUM: lambda args: f"{args[0]}",
|
|
41
41
|
FunctionType.AVG: lambda args: f"{args[0]}",
|
|
42
42
|
}
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -983,6 +983,7 @@ class ParseToObjects(Transformer):
|
|
|
983
983
|
order_by=order_by,
|
|
984
984
|
meta=Metadata(line_number=meta.line),
|
|
985
985
|
)
|
|
986
|
+
|
|
986
987
|
for item in select_items:
|
|
987
988
|
# we don't know the grain of an aggregate at assignment time
|
|
988
989
|
# so rebuild at this point in the tree
|
|
@@ -1000,21 +1001,43 @@ class ParseToObjects(Transformer):
|
|
|
1000
1001
|
)
|
|
1001
1002
|
self.environment.add_concept(new_concept, meta=meta)
|
|
1002
1003
|
item.content.output = new_concept
|
|
1004
|
+
# TODO: revisit if we can push down every filter
|
|
1005
|
+
# else:
|
|
1006
|
+
# item.content = (
|
|
1007
|
+
# item.content.with_filter(
|
|
1008
|
+
# output.where_clause.conditional, environment=self.environment
|
|
1009
|
+
# )
|
|
1010
|
+
# if output.where_clause
|
|
1011
|
+
# and output.where_clause_category == SelectFiltering.IMPLICIT
|
|
1012
|
+
# else item.content
|
|
1013
|
+
# )
|
|
1014
|
+
|
|
1003
1015
|
if order_by:
|
|
1004
1016
|
for orderitem in order_by.items:
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1017
|
+
if isinstance(orderitem.expr, Concept):
|
|
1018
|
+
if orderitem.expr.purpose == Purpose.METRIC:
|
|
1019
|
+
orderitem.expr = orderitem.expr.with_select_context(
|
|
1020
|
+
output.grain,
|
|
1021
|
+
conditional=(
|
|
1022
|
+
output.where_clause.conditional
|
|
1023
|
+
if output.where_clause
|
|
1024
|
+
and output.where_clause_category
|
|
1025
|
+
== SelectFiltering.IMPLICIT
|
|
1026
|
+
else None
|
|
1027
|
+
),
|
|
1028
|
+
environment=self.environment,
|
|
1029
|
+
)
|
|
1030
|
+
# TODO :push down every filter
|
|
1031
|
+
# else:
|
|
1032
|
+
# orderitem.expr = (
|
|
1033
|
+
# orderitem.expr.with_filter(
|
|
1034
|
+
# output.where_clause.conditional,
|
|
1035
|
+
# environment=self.environment,
|
|
1036
|
+
# )
|
|
1037
|
+
# if output.where_clause
|
|
1038
|
+
# and output.where_clause_category == SelectFiltering.IMPLICIT
|
|
1039
|
+
# else orderitem.expr
|
|
1040
|
+
# )
|
|
1018
1041
|
return output
|
|
1019
1042
|
|
|
1020
1043
|
@v_args(meta=True)
|
|
@@ -1116,7 +1139,25 @@ class ParseToObjects(Transformer):
|
|
|
1116
1139
|
def comparison(self, args) -> Comparison:
|
|
1117
1140
|
if args[1] == ComparisonOperator.IN:
|
|
1118
1141
|
raise SyntaxError
|
|
1119
|
-
|
|
1142
|
+
if isinstance(args[0], AggregateWrapper):
|
|
1143
|
+
left = arbitrary_to_concept(
|
|
1144
|
+
args[0],
|
|
1145
|
+
namespace=self.environment.namespace,
|
|
1146
|
+
name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(args[0]))}",
|
|
1147
|
+
)
|
|
1148
|
+
self.environment.add_concept(left)
|
|
1149
|
+
else:
|
|
1150
|
+
left = args[0]
|
|
1151
|
+
if isinstance(args[2], AggregateWrapper):
|
|
1152
|
+
right = arbitrary_to_concept(
|
|
1153
|
+
args[2],
|
|
1154
|
+
namespace=self.environment.namespace,
|
|
1155
|
+
name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(args[2]))}",
|
|
1156
|
+
)
|
|
1157
|
+
self.environment.add_concept(right)
|
|
1158
|
+
else:
|
|
1159
|
+
right = args[2]
|
|
1160
|
+
return Comparison(left=left, right=right, operator=args[1])
|
|
1120
1161
|
|
|
1121
1162
|
def between_comparison(self, args) -> Conditional:
|
|
1122
1163
|
left_bound = args[1]
|
|
@@ -1219,7 +1260,11 @@ class ParseToObjects(Transformer):
|
|
|
1219
1260
|
|
|
1220
1261
|
def filter_item(self, args) -> FilterItem:
|
|
1221
1262
|
where: WhereClause
|
|
1222
|
-
string_concept,
|
|
1263
|
+
string_concept, raw = args
|
|
1264
|
+
if isinstance(raw, WhereClause):
|
|
1265
|
+
where = raw
|
|
1266
|
+
else:
|
|
1267
|
+
where = WhereClause(conditional=raw)
|
|
1223
1268
|
concept = self.environment.concepts[string_concept]
|
|
1224
1269
|
return FilterItem(content=concept, where=where)
|
|
1225
1270
|
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -83,7 +83,11 @@
|
|
|
83
83
|
raw_function: "bind" "sql" IDENTIFIER "(" function_binding_list ")" "->" data_type "as"i MULTILINE_STRING
|
|
84
84
|
|
|
85
85
|
// user_id where state = Mexico
|
|
86
|
-
|
|
86
|
+
_filter_alt: IDENTIFIER "?" conditional
|
|
87
|
+
_filter_base: "filter"i IDENTIFIER where
|
|
88
|
+
filter_item: _filter_base | _filter_alt
|
|
89
|
+
|
|
90
|
+
|
|
87
91
|
|
|
88
92
|
// rank/lag/lead
|
|
89
93
|
WINDOW_TYPE: ("row_number"i|"rank"i|"lag"i|"lead"i | "sum"i) /[\s]+/
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|