pytrilogy 0.0.2.10__py3-none-any.whl → 0.0.2.12__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.10.dist-info → pytrilogy-0.0.2.12.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.10.dist-info → pytrilogy-0.0.2.12.dist-info}/RECORD +30 -30
- trilogy/__init__.py +1 -1
- trilogy/core/enums.py +0 -1
- trilogy/core/environment_helpers.py +44 -6
- trilogy/core/models.py +47 -26
- trilogy/core/optimization.py +31 -3
- trilogy/core/optimizations/__init__.py +2 -1
- trilogy/core/optimizations/predicate_pushdown.py +60 -42
- trilogy/core/processing/concept_strategies_v3.py +8 -4
- trilogy/core/processing/node_generators/basic_node.py +15 -9
- trilogy/core/processing/node_generators/filter_node.py +20 -3
- trilogy/core/processing/node_generators/group_node.py +2 -0
- trilogy/core/processing/node_generators/node_merge_node.py +28 -2
- trilogy/core/processing/node_generators/unnest_node.py +10 -3
- trilogy/core/processing/nodes/base_node.py +7 -2
- trilogy/core/processing/nodes/group_node.py +0 -1
- trilogy/core/processing/nodes/merge_node.py +11 -4
- trilogy/core/processing/nodes/unnest_node.py +13 -9
- trilogy/core/processing/utility.py +3 -1
- trilogy/core/query_processor.py +20 -5
- trilogy/dialect/base.py +96 -56
- trilogy/dialect/common.py +3 -3
- trilogy/parsing/common.py +58 -1
- trilogy/parsing/parse_engine.py +111 -136
- trilogy/parsing/trilogy.lark +5 -1
- {pytrilogy-0.0.2.10.dist-info → pytrilogy-0.0.2.12.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.10.dist-info → pytrilogy-0.0.2.12.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.10.dist-info → pytrilogy-0.0.2.12.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.10.dist-info → pytrilogy-0.0.2.12.dist-info}/top_level.txt +0 -0
trilogy/dialect/base.py
CHANGED
|
@@ -9,7 +9,6 @@ from trilogy.core.enums import (
|
|
|
9
9
|
FunctionType,
|
|
10
10
|
WindowType,
|
|
11
11
|
DatePart,
|
|
12
|
-
ComparisonOperator,
|
|
13
12
|
)
|
|
14
13
|
from trilogy.core.models import (
|
|
15
14
|
ListType,
|
|
@@ -227,13 +226,7 @@ def safe_get_cte_value(coalesce, cte: CTE, c: Concept, quote_char: str):
|
|
|
227
226
|
raw = cte.source_map.get(address, None)
|
|
228
227
|
|
|
229
228
|
if not raw:
|
|
230
|
-
|
|
231
|
-
if cte.source_map.get(k):
|
|
232
|
-
c = v
|
|
233
|
-
raw = cte.source_map[k]
|
|
234
|
-
break
|
|
235
|
-
if not raw:
|
|
236
|
-
return INVALID_REFERENCE_STRING("Missing source reference")
|
|
229
|
+
return None
|
|
237
230
|
if isinstance(raw, str):
|
|
238
231
|
rendered = cte.get_alias(c, raw)
|
|
239
232
|
return f"{raw}.{safe_quote(rendered, quote_char)}"
|
|
@@ -260,38 +253,64 @@ class BaseDialect:
|
|
|
260
253
|
|
|
261
254
|
return f"{self.render_concept_sql(order_item.expr, cte=cte, alias=False)} {order_item.order.value}"
|
|
262
255
|
|
|
263
|
-
def render_concept_sql(
|
|
256
|
+
def render_concept_sql(
|
|
257
|
+
self, c: Concept, cte: CTE, alias: bool = True, raise_invalid: bool = False
|
|
258
|
+
) -> str:
|
|
259
|
+
result = None
|
|
260
|
+
if c.pseudonyms:
|
|
261
|
+
for candidate in [c] + list(c.pseudonyms.values()):
|
|
262
|
+
try:
|
|
263
|
+
logger.debug(
|
|
264
|
+
f"{LOGGER_PREFIX} [{c.address}] Attempting rendering w/ candidate {candidate.address}"
|
|
265
|
+
)
|
|
266
|
+
result = self._render_concept_sql(
|
|
267
|
+
candidate, cte, raise_invalid=True
|
|
268
|
+
)
|
|
269
|
+
if result:
|
|
270
|
+
break
|
|
271
|
+
except ValueError:
|
|
272
|
+
continue
|
|
273
|
+
if not result:
|
|
274
|
+
result = self._render_concept_sql(c, cte, raise_invalid=raise_invalid)
|
|
275
|
+
if alias:
|
|
276
|
+
return f"{result} as {self.QUOTE_CHARACTER}{c.safe_address}{self.QUOTE_CHARACTER}"
|
|
277
|
+
return result
|
|
278
|
+
|
|
279
|
+
def _render_concept_sql(
|
|
280
|
+
self, c: Concept, cte: CTE, raise_invalid: bool = False
|
|
281
|
+
) -> str:
|
|
264
282
|
# only recurse while it's in sources of the current cte
|
|
265
283
|
logger.debug(
|
|
266
284
|
f"{LOGGER_PREFIX} [{c.address}] Starting rendering loop on cte: {cte.name}"
|
|
267
285
|
)
|
|
268
286
|
|
|
287
|
+
# check if it's not inherited AND no pseudonyms are inherited
|
|
269
288
|
if c.lineage and cte.source_map.get(c.address, []) == []:
|
|
270
289
|
logger.debug(
|
|
271
|
-
f"{LOGGER_PREFIX} [{c.address}] rendering concept with lineage that is not already existing"
|
|
290
|
+
f"{LOGGER_PREFIX} [{c.address}] rendering concept with lineage that is not already existing, have {cte.source_map}"
|
|
272
291
|
)
|
|
273
292
|
if isinstance(c.lineage, WindowItem):
|
|
274
293
|
rendered_order_components = [
|
|
275
|
-
f"{self.render_concept_sql(x.expr, cte, alias=False)} {x.order.value}"
|
|
294
|
+
f"{self.render_concept_sql(x.expr, cte, alias=False, raise_invalid=raise_invalid)} {x.order.value}"
|
|
276
295
|
for x in c.lineage.order_by
|
|
277
296
|
]
|
|
278
297
|
rendered_over_components = [
|
|
279
|
-
self.render_concept_sql(
|
|
298
|
+
self.render_concept_sql(
|
|
299
|
+
x, cte, alias=False, raise_invalid=raise_invalid
|
|
300
|
+
)
|
|
301
|
+
for x in c.lineage.over
|
|
280
302
|
]
|
|
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
|
|
303
|
+
rval = f"{self.WINDOW_FUNCTION_MAP[c.lineage.type](concept = self.render_concept_sql(c.lineage.content, cte=cte, alias=False, raise_invalid=raise_invalid), window=','.join(rendered_over_components), sort=','.join(rendered_order_components))}" # noqa: E501
|
|
282
304
|
elif isinstance(c.lineage, FilterItem):
|
|
283
305
|
# for cases when we've optimized this
|
|
284
|
-
if
|
|
285
|
-
len(cte.output_columns) == 1
|
|
286
|
-
and cte.condition == c.lineage.where.conditional
|
|
287
|
-
):
|
|
306
|
+
if cte.condition == c.lineage.where.conditional:
|
|
288
307
|
rval = self.render_expr(c.lineage.content, cte=cte)
|
|
289
308
|
else:
|
|
290
|
-
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"
|
|
309
|
+
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, raise_invalid=raise_invalid)} ELSE NULL END"
|
|
291
310
|
elif isinstance(c.lineage, RowsetItem):
|
|
292
|
-
rval = f"{self.render_concept_sql(c.lineage.content, cte=cte, alias=False)}"
|
|
311
|
+
rval = f"{self.render_concept_sql(c.lineage.content, cte=cte, alias=False, raise_invalid=raise_invalid)}"
|
|
293
312
|
elif isinstance(c.lineage, MultiSelectStatement):
|
|
294
|
-
rval = f"{self.render_concept_sql(c.lineage.find_source(c, cte), cte=cte, alias=False)}"
|
|
313
|
+
rval = f"{self.render_concept_sql(c.lineage.find_source(c, cte), cte=cte, alias=False, raise_invalid=raise_invalid)}"
|
|
295
314
|
elif isinstance(c.lineage, AggregateWrapper):
|
|
296
315
|
args = [
|
|
297
316
|
self.render_expr(v, cte) # , alias=False)
|
|
@@ -307,16 +326,15 @@ class BaseDialect:
|
|
|
307
326
|
rval = f"{self.FUNCTION_GRAIN_MATCH_MAP[c.lineage.function.operator](args)}"
|
|
308
327
|
else:
|
|
309
328
|
args = [
|
|
310
|
-
self.render_expr(
|
|
329
|
+
self.render_expr(
|
|
330
|
+
v, cte=cte, raise_invalid=raise_invalid
|
|
331
|
+
) # , alias=False)
|
|
311
332
|
for v in c.lineage.arguments
|
|
312
333
|
]
|
|
313
334
|
|
|
314
335
|
if cte.group_to_grain:
|
|
315
336
|
rval = f"{self.FUNCTION_MAP[c.lineage.operator](args)}"
|
|
316
337
|
else:
|
|
317
|
-
logger.debug(
|
|
318
|
-
f"{LOGGER_PREFIX} [{c.address}] ignoring optimazable aggregate function, at grain so optimizing"
|
|
319
|
-
)
|
|
320
338
|
rval = f"{self.FUNCTION_GRAIN_MATCH_MAP[c.lineage.operator](args)}"
|
|
321
339
|
else:
|
|
322
340
|
logger.debug(
|
|
@@ -327,14 +345,24 @@ class BaseDialect:
|
|
|
327
345
|
if isinstance(raw_content, RawColumnExpr):
|
|
328
346
|
rval = raw_content.text
|
|
329
347
|
elif isinstance(raw_content, Function):
|
|
330
|
-
rval = self.render_expr(
|
|
348
|
+
rval = self.render_expr(
|
|
349
|
+
raw_content, cte=cte, raise_invalid=raise_invalid
|
|
350
|
+
)
|
|
331
351
|
else:
|
|
332
|
-
rval =
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
352
|
+
rval = safe_get_cte_value(
|
|
353
|
+
self.FUNCTION_MAP[FunctionType.COALESCE],
|
|
354
|
+
cte,
|
|
355
|
+
c,
|
|
356
|
+
self.QUOTE_CHARACTER,
|
|
357
|
+
)
|
|
358
|
+
if not rval:
|
|
359
|
+
if raise_invalid:
|
|
360
|
+
raise ValueError(
|
|
361
|
+
f"Invalid reference string found in query: {rval}, this should never occur. Please report this issue."
|
|
362
|
+
)
|
|
363
|
+
rval = INVALID_REFERENCE_STRING(
|
|
364
|
+
f"Missing source reference to {c.address}"
|
|
365
|
+
)
|
|
338
366
|
return rval
|
|
339
367
|
|
|
340
368
|
def render_expr(
|
|
@@ -370,6 +398,7 @@ class BaseDialect:
|
|
|
370
398
|
],
|
|
371
399
|
cte: Optional[CTE] = None,
|
|
372
400
|
cte_map: Optional[Dict[str, CTE]] = None,
|
|
401
|
+
raise_invalid: bool = False,
|
|
373
402
|
) -> str:
|
|
374
403
|
|
|
375
404
|
if isinstance(e, SubselectComparison):
|
|
@@ -398,9 +427,9 @@ class BaseDialect:
|
|
|
398
427
|
target = INVALID_REFERENCE_STRING(
|
|
399
428
|
f"Missing source CTE for {e.right.address}"
|
|
400
429
|
)
|
|
401
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} (select {target}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} from {target} where {target}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} is not null)"
|
|
430
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} (select {target}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} from {target} where {target}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} is not null)"
|
|
402
431
|
elif isinstance(e.right, (ListWrapper, Parenthetical, list)):
|
|
403
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
432
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}"
|
|
404
433
|
|
|
405
434
|
elif isinstance(
|
|
406
435
|
e.right,
|
|
@@ -411,53 +440,64 @@ class BaseDialect:
|
|
|
411
440
|
float,
|
|
412
441
|
),
|
|
413
442
|
):
|
|
414
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} ({self.render_expr(e.right, cte=cte, cte_map=cte_map)})"
|
|
443
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} ({self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)})"
|
|
415
444
|
else:
|
|
416
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
445
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}"
|
|
417
446
|
elif isinstance(e, Comparison):
|
|
418
|
-
|
|
419
|
-
right_comp = e.right
|
|
420
|
-
assert isinstance(right_comp, Conditional)
|
|
421
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(right_comp.left, cte=cte, cte_map=cte_map) and self.render_expr(right_comp.right, cte=cte, cte_map=cte_map)}"
|
|
422
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
447
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}"
|
|
423
448
|
elif isinstance(e, Conditional):
|
|
424
449
|
# conditions need to be nested in parentheses
|
|
425
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
450
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}"
|
|
426
451
|
elif isinstance(e, WindowItem):
|
|
427
452
|
rendered_order_components = [
|
|
428
|
-
f"{self.render_expr(x.expr, cte, cte_map=cte_map)} {x.order.value}"
|
|
453
|
+
f"{self.render_expr(x.expr, cte, cte_map=cte_map, raise_invalid=raise_invalid)} {x.order.value}"
|
|
429
454
|
for x in e.order_by
|
|
430
455
|
]
|
|
431
456
|
rendered_over_components = [
|
|
432
|
-
self.render_expr(x, cte, cte_map=cte_map)
|
|
457
|
+
self.render_expr(x, cte, cte_map=cte_map, raise_invalid=raise_invalid)
|
|
458
|
+
for x in e.over
|
|
433
459
|
]
|
|
434
|
-
return f"{self.WINDOW_FUNCTION_MAP[e.type](concept = self.render_expr(e.content, cte=cte, cte_map=cte_map), window=','.join(rendered_over_components), sort=','.join(rendered_order_components))}" # noqa: E501
|
|
460
|
+
return f"{self.WINDOW_FUNCTION_MAP[e.type](concept = self.render_expr(e.content, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid), window=','.join(rendered_over_components), sort=','.join(rendered_order_components))}" # noqa: E501
|
|
435
461
|
elif isinstance(e, Parenthetical):
|
|
436
462
|
# conditions need to be nested in parentheses
|
|
437
463
|
if isinstance(e.content, list):
|
|
438
|
-
return f"( {','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e.content])} )"
|
|
439
|
-
return f"( {self.render_expr(e.content, cte=cte, cte_map=cte_map)} )"
|
|
464
|
+
return f"( {','.join([self.render_expr(x, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid) for x in e.content])} )"
|
|
465
|
+
return f"( {self.render_expr(e.content, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} )"
|
|
440
466
|
elif isinstance(e, CaseWhen):
|
|
441
|
-
return f"WHEN {self.render_expr(e.comparison, cte=cte, cte_map=cte_map) } THEN {self.render_expr(e.expr, cte=cte, cte_map=cte_map) }"
|
|
467
|
+
return f"WHEN {self.render_expr(e.comparison, cte=cte, cte_map=cte_map) } THEN {self.render_expr(e.expr, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid) }"
|
|
442
468
|
elif isinstance(e, CaseElse):
|
|
443
|
-
return f"ELSE {self.render_expr(e.expr, cte=cte, cte_map=cte_map) }"
|
|
469
|
+
return f"ELSE {self.render_expr(e.expr, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid) }"
|
|
444
470
|
elif isinstance(e, Function):
|
|
445
471
|
|
|
446
472
|
if cte and cte.group_to_grain:
|
|
447
473
|
return self.FUNCTION_MAP[e.operator](
|
|
448
|
-
[
|
|
474
|
+
[
|
|
475
|
+
self.render_expr(
|
|
476
|
+
z, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid
|
|
477
|
+
)
|
|
478
|
+
for z in e.arguments
|
|
479
|
+
]
|
|
449
480
|
)
|
|
450
481
|
|
|
451
482
|
return self.FUNCTION_GRAIN_MATCH_MAP[e.operator](
|
|
452
|
-
[
|
|
483
|
+
[
|
|
484
|
+
self.render_expr(
|
|
485
|
+
z, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid
|
|
486
|
+
)
|
|
487
|
+
for z in e.arguments
|
|
488
|
+
]
|
|
453
489
|
)
|
|
454
490
|
elif isinstance(e, AggregateWrapper):
|
|
455
|
-
return self.render_expr(
|
|
491
|
+
return self.render_expr(
|
|
492
|
+
e.function, cte, cte_map=cte_map, raise_invalid=raise_invalid
|
|
493
|
+
)
|
|
456
494
|
elif isinstance(e, FilterItem):
|
|
457
|
-
return f"CASE WHEN {self.render_expr(e.where.conditional,cte=cte, cte_map=cte_map)} THEN {self.render_expr(e.content, cte, cte_map=cte_map)} ELSE NULL END"
|
|
495
|
+
return f"CASE WHEN {self.render_expr(e.where.conditional,cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} THEN {self.render_expr(e.content, cte, cte_map=cte_map, raise_invalid=raise_invalid)} ELSE NULL END"
|
|
458
496
|
elif isinstance(e, Concept):
|
|
459
497
|
if cte:
|
|
460
|
-
return self.render_concept_sql(
|
|
498
|
+
return self.render_concept_sql(
|
|
499
|
+
e, cte, alias=False, raise_invalid=raise_invalid
|
|
500
|
+
)
|
|
461
501
|
elif cte_map:
|
|
462
502
|
return f"{cte_map[e.address].name}.{self.QUOTE_CHARACTER}{e.safe_address}{self.QUOTE_CHARACTER}"
|
|
463
503
|
return f"{self.QUOTE_CHARACTER}{e.safe_address}{self.QUOTE_CHARACTER}"
|
|
@@ -468,11 +508,11 @@ class BaseDialect:
|
|
|
468
508
|
elif isinstance(e, (int, float)):
|
|
469
509
|
return str(e)
|
|
470
510
|
elif isinstance(e, ListWrapper):
|
|
471
|
-
return f"[{','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}]"
|
|
511
|
+
return f"[{','.join([self.render_expr(x, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid) for x in e])}]"
|
|
472
512
|
elif isinstance(e, MapWrapper):
|
|
473
|
-
return f"MAP {{{','.join([f'{self.render_expr(k, cte=cte, cte_map=cte_map)}:{self.render_expr(v, cte=cte, cte_map=cte_map)}' for k, v in e.items()])}}}"
|
|
513
|
+
return f"MAP {{{','.join([f'{self.render_expr(k, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}:{self.render_expr(v, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}' for k, v in e.items()])}}}"
|
|
474
514
|
elif isinstance(e, list):
|
|
475
|
-
return f"{self.FUNCTION_MAP[FunctionType.ARRAY]([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}"
|
|
515
|
+
return f"{self.FUNCTION_MAP[FunctionType.ARRAY]([self.render_expr(x, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid) for x in e])}"
|
|
476
516
|
elif isinstance(e, DataType):
|
|
477
517
|
return str(e.value)
|
|
478
518
|
elif isinstance(e, DatePart):
|
trilogy/dialect/common.py
CHANGED
|
@@ -37,10 +37,10 @@ def render_join(
|
|
|
37
37
|
if not cte:
|
|
38
38
|
raise ValueError("must provide a cte to build an unnest joins")
|
|
39
39
|
if unnest_mode == UnnestMode.CROSS_JOIN:
|
|
40
|
-
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.
|
|
40
|
+
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
|
|
41
41
|
if unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
|
|
42
|
-
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.
|
|
43
|
-
return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.
|
|
42
|
+
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
|
|
43
|
+
return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
|
|
44
44
|
left_name = join.left_name
|
|
45
45
|
right_name = join.right_name
|
|
46
46
|
right_base = join.right_ref
|
trilogy/parsing/common.py
CHANGED
|
@@ -9,6 +9,10 @@ from trilogy.core.models import (
|
|
|
9
9
|
ListWrapper,
|
|
10
10
|
MapWrapper,
|
|
11
11
|
WindowItem,
|
|
12
|
+
Meta,
|
|
13
|
+
Parenthetical,
|
|
14
|
+
FunctionClass,
|
|
15
|
+
Environment,
|
|
12
16
|
)
|
|
13
17
|
from typing import List, Tuple
|
|
14
18
|
from trilogy.core.functions import (
|
|
@@ -16,8 +20,61 @@ from trilogy.core.functions import (
|
|
|
16
20
|
FunctionType,
|
|
17
21
|
arg_to_datatype,
|
|
18
22
|
)
|
|
19
|
-
from trilogy.utility import unique
|
|
23
|
+
from trilogy.utility import unique, string_to_hash
|
|
20
24
|
from trilogy.core.enums import PurposeLineage
|
|
25
|
+
from trilogy.constants import (
|
|
26
|
+
VIRTUAL_CONCEPT_PREFIX,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def process_function_args(
|
|
31
|
+
args,
|
|
32
|
+
meta: Meta | None,
|
|
33
|
+
environment: Environment,
|
|
34
|
+
):
|
|
35
|
+
final: List[Concept | Function] = []
|
|
36
|
+
for arg in args:
|
|
37
|
+
# if a function has an anonymous function argument
|
|
38
|
+
# create an implicit concept
|
|
39
|
+
while isinstance(arg, Parenthetical):
|
|
40
|
+
arg = arg.content
|
|
41
|
+
if isinstance(arg, Function):
|
|
42
|
+
# if it's not an aggregate function, we can skip the virtual concepts
|
|
43
|
+
# to simplify anonymous function handling
|
|
44
|
+
if (
|
|
45
|
+
arg.operator not in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
46
|
+
and arg.operator != FunctionType.UNNEST
|
|
47
|
+
):
|
|
48
|
+
final.append(arg)
|
|
49
|
+
continue
|
|
50
|
+
id_hash = string_to_hash(str(arg))
|
|
51
|
+
concept = function_to_concept(
|
|
52
|
+
arg,
|
|
53
|
+
name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
|
|
54
|
+
namespace=environment.namespace,
|
|
55
|
+
)
|
|
56
|
+
# to satisfy mypy, concept will always have metadata
|
|
57
|
+
if concept.metadata and meta:
|
|
58
|
+
concept.metadata.line_number = meta.line
|
|
59
|
+
environment.add_concept(concept, meta=meta)
|
|
60
|
+
final.append(concept)
|
|
61
|
+
elif isinstance(
|
|
62
|
+
arg, (FilterItem, WindowItem, AggregateWrapper, ListWrapper, MapWrapper)
|
|
63
|
+
):
|
|
64
|
+
id_hash = string_to_hash(str(arg))
|
|
65
|
+
concept = arbitrary_to_concept(
|
|
66
|
+
arg,
|
|
67
|
+
name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
|
|
68
|
+
namespace=environment.namespace,
|
|
69
|
+
)
|
|
70
|
+
if concept.metadata and meta:
|
|
71
|
+
concept.metadata.line_number = meta.line
|
|
72
|
+
environment.add_concept(concept, meta=meta)
|
|
73
|
+
final.append(concept)
|
|
74
|
+
|
|
75
|
+
else:
|
|
76
|
+
final.append(arg)
|
|
77
|
+
return final
|
|
21
78
|
|
|
22
79
|
|
|
23
80
|
def get_purpose_and_keys(
|