pytrilogy 0.0.2.11__py3-none-any.whl → 0.0.2.13__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 (31) hide show
  1. {pytrilogy-0.0.2.11.dist-info → pytrilogy-0.0.2.13.dist-info}/METADATA +1 -1
  2. {pytrilogy-0.0.2.11.dist-info → pytrilogy-0.0.2.13.dist-info}/RECORD +31 -31
  3. {pytrilogy-0.0.2.11.dist-info → pytrilogy-0.0.2.13.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +1 -1
  5. trilogy/constants.py +5 -0
  6. trilogy/core/enums.py +3 -1
  7. trilogy/core/environment_helpers.py +44 -6
  8. trilogy/core/models.py +51 -27
  9. trilogy/core/optimization.py +31 -3
  10. trilogy/core/optimizations/__init__.py +2 -1
  11. trilogy/core/optimizations/predicate_pushdown.py +60 -42
  12. trilogy/core/processing/concept_strategies_v3.py +6 -4
  13. trilogy/core/processing/node_generators/basic_node.py +22 -9
  14. trilogy/core/processing/node_generators/common.py +13 -23
  15. trilogy/core/processing/node_generators/node_merge_node.py +22 -1
  16. trilogy/core/processing/node_generators/unnest_node.py +10 -3
  17. trilogy/core/processing/nodes/base_node.py +18 -11
  18. trilogy/core/processing/nodes/group_node.py +0 -1
  19. trilogy/core/processing/nodes/merge_node.py +12 -5
  20. trilogy/core/processing/nodes/unnest_node.py +13 -9
  21. trilogy/core/processing/utility.py +3 -1
  22. trilogy/core/query_processor.py +14 -12
  23. trilogy/dialect/base.py +95 -52
  24. trilogy/dialect/common.py +3 -3
  25. trilogy/executor.py +8 -2
  26. trilogy/parsing/common.py +73 -2
  27. trilogy/parsing/parse_engine.py +88 -132
  28. trilogy/parsing/trilogy.lark +3 -3
  29. {pytrilogy-0.0.2.11.dist-info → pytrilogy-0.0.2.13.dist-info}/LICENSE.md +0 -0
  30. {pytrilogy-0.0.2.11.dist-info → pytrilogy-0.0.2.13.dist-info}/entry_points.txt +0 -0
  31. {pytrilogy-0.0.2.11.dist-info → pytrilogy-0.0.2.13.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
- for k, v in c.pseudonyms.items():
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,35 +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(self, c: Concept, cte: CTE, alias: bool = True) -> str:
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(x, cte, alias=False) for x in c.lineage.over
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
306
  if cte.condition == c.lineage.where.conditional:
285
307
  rval = self.render_expr(c.lineage.content, cte=cte)
286
308
  else:
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"
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"
288
310
  elif isinstance(c.lineage, RowsetItem):
289
- 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)}"
290
312
  elif isinstance(c.lineage, MultiSelectStatement):
291
- 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)}"
292
314
  elif isinstance(c.lineage, AggregateWrapper):
293
315
  args = [
294
316
  self.render_expr(v, cte) # , alias=False)
@@ -304,16 +326,15 @@ class BaseDialect:
304
326
  rval = f"{self.FUNCTION_GRAIN_MATCH_MAP[c.lineage.function.operator](args)}"
305
327
  else:
306
328
  args = [
307
- self.render_expr(v, cte) # , alias=False)
329
+ self.render_expr(
330
+ v, cte=cte, raise_invalid=raise_invalid
331
+ ) # , alias=False)
308
332
  for v in c.lineage.arguments
309
333
  ]
310
334
 
311
335
  if cte.group_to_grain:
312
336
  rval = f"{self.FUNCTION_MAP[c.lineage.operator](args)}"
313
337
  else:
314
- logger.debug(
315
- f"{LOGGER_PREFIX} [{c.address}] ignoring optimazable aggregate function, at grain so optimizing"
316
- )
317
338
  rval = f"{self.FUNCTION_GRAIN_MATCH_MAP[c.lineage.operator](args)}"
318
339
  else:
319
340
  logger.debug(
@@ -324,14 +345,24 @@ class BaseDialect:
324
345
  if isinstance(raw_content, RawColumnExpr):
325
346
  rval = raw_content.text
326
347
  elif isinstance(raw_content, Function):
327
- rval = self.render_expr(raw_content, cte=cte)
348
+ rval = self.render_expr(
349
+ raw_content, cte=cte, raise_invalid=raise_invalid
350
+ )
328
351
  else:
329
- rval = f"{safe_get_cte_value(self.FUNCTION_MAP[FunctionType.COALESCE], cte, c, self.QUOTE_CHARACTER)}"
330
- if alias:
331
- return (
332
- f"{rval} as"
333
- f" {self.QUOTE_CHARACTER}{c.safe_address}{self.QUOTE_CHARACTER}"
334
- )
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
+ )
335
366
  return rval
336
367
 
337
368
  def render_expr(
@@ -367,6 +398,7 @@ class BaseDialect:
367
398
  ],
368
399
  cte: Optional[CTE] = None,
369
400
  cte_map: Optional[Dict[str, CTE]] = None,
401
+ raise_invalid: bool = False,
370
402
  ) -> str:
371
403
 
372
404
  if isinstance(e, SubselectComparison):
@@ -395,9 +427,9 @@ class BaseDialect:
395
427
  target = INVALID_REFERENCE_STRING(
396
428
  f"Missing source CTE for {e.right.address}"
397
429
  )
398
- 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)"
399
431
  elif isinstance(e.right, (ListWrapper, Parenthetical, list)):
400
- 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)}"
401
433
 
402
434
  elif isinstance(
403
435
  e.right,
@@ -408,53 +440,64 @@ class BaseDialect:
408
440
  float,
409
441
  ),
410
442
  ):
411
- 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)})"
412
444
  else:
413
- 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)}"
414
446
  elif isinstance(e, Comparison):
415
- if e.operator == ComparisonOperator.BETWEEN:
416
- right_comp = e.right
417
- assert isinstance(right_comp, Conditional)
418
- 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)}"
419
- 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)}"
420
448
  elif isinstance(e, Conditional):
421
449
  # conditions need to be nested in parentheses
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)}"
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)}"
423
451
  elif isinstance(e, WindowItem):
424
452
  rendered_order_components = [
425
- 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}"
426
454
  for x in e.order_by
427
455
  ]
428
456
  rendered_over_components = [
429
- self.render_expr(x, cte, cte_map=cte_map) for x in e.over
457
+ self.render_expr(x, cte, cte_map=cte_map, raise_invalid=raise_invalid)
458
+ for x in e.over
430
459
  ]
431
- 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
432
461
  elif isinstance(e, Parenthetical):
433
462
  # conditions need to be nested in parentheses
434
463
  if isinstance(e.content, list):
435
- return f"( {','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e.content])} )"
436
- 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)} )"
437
466
  elif isinstance(e, CaseWhen):
438
- 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) }"
439
468
  elif isinstance(e, CaseElse):
440
- 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) }"
441
470
  elif isinstance(e, Function):
442
471
 
443
472
  if cte and cte.group_to_grain:
444
473
  return self.FUNCTION_MAP[e.operator](
445
- [self.render_expr(z, cte=cte, cte_map=cte_map) for z in e.arguments]
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
+ ]
446
480
  )
447
481
 
448
482
  return self.FUNCTION_GRAIN_MATCH_MAP[e.operator](
449
- [self.render_expr(z, cte=cte, cte_map=cte_map) for z in e.arguments]
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
+ ]
450
489
  )
451
490
  elif isinstance(e, AggregateWrapper):
452
- return self.render_expr(e.function, cte, cte_map=cte_map)
491
+ return self.render_expr(
492
+ e.function, cte, cte_map=cte_map, raise_invalid=raise_invalid
493
+ )
453
494
  elif isinstance(e, FilterItem):
454
- 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"
455
496
  elif isinstance(e, Concept):
456
497
  if cte:
457
- return self.render_concept_sql(e, cte, alias=False)
498
+ return self.render_concept_sql(
499
+ e, cte, alias=False, raise_invalid=raise_invalid
500
+ )
458
501
  elif cte_map:
459
502
  return f"{cte_map[e.address].name}.{self.QUOTE_CHARACTER}{e.safe_address}{self.QUOTE_CHARACTER}"
460
503
  return f"{self.QUOTE_CHARACTER}{e.safe_address}{self.QUOTE_CHARACTER}"
@@ -465,11 +508,11 @@ class BaseDialect:
465
508
  elif isinstance(e, (int, float)):
466
509
  return str(e)
467
510
  elif isinstance(e, ListWrapper):
468
- 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])}]"
469
512
  elif isinstance(e, MapWrapper):
470
- 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()])}}}"
471
514
  elif isinstance(e, list):
472
- 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])}"
473
516
  elif isinstance(e, DataType):
474
517
  return str(e.value)
475
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.concept, render_func, cte)}"
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.concept, render_func, cte)}"
43
- return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.concept, render_func, cte)}"
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/executor.py CHANGED
@@ -300,10 +300,16 @@ class Executor(object):
300
300
  self.environment.add_datasource(x.datasource)
301
301
  yield x
302
302
 
303
- def execute_raw_sql(self, command: str) -> CursorResult:
303
+ def execute_raw_sql(
304
+ self, command: str, variables: dict | None = None
305
+ ) -> CursorResult:
304
306
  """Run a command against the raw underlying
305
307
  execution engine"""
306
- return self.connection.execute(text(command))
308
+ if variables:
309
+ return self.connection.execute(text(command), variables)
310
+ return self.connection.execute(
311
+ text(command),
312
+ )
307
313
 
308
314
  def execute_text(self, command: str) -> List[CursorResult]:
309
315
  """Run a preql text command"""
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}_{arg.operator.value}_{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(
@@ -198,20 +255,34 @@ def arbitrary_to_concept(
198
255
  | str
199
256
  ),
200
257
  namespace: str,
201
- name: str,
258
+ name: str | None = None,
202
259
  metadata: Metadata | None = None,
203
260
  purpose: Purpose | None = None,
204
261
  ) -> Concept:
205
262
 
206
263
  if isinstance(parent, AggregateWrapper):
264
+ if not name:
265
+ name = (
266
+ f"_agg_{parent.function.operator.value}_{string_to_hash(str(parent))}"
267
+ )
207
268
  return agg_wrapper_to_concept(parent, namespace, name, metadata, purpose)
208
269
  elif isinstance(parent, WindowItem):
270
+ if not name:
271
+ name = f"_window_{parent.type.value}_{string_to_hash(str(parent))}"
209
272
  return window_item_to_concept(parent, name, namespace, purpose, metadata)
210
273
  elif isinstance(parent, FilterItem):
274
+ if not name:
275
+ name = f"_filter_{parent.content.name}_{string_to_hash(str(parent))}"
211
276
  return filter_item_to_concept(parent, name, namespace, purpose, metadata)
212
277
  elif isinstance(parent, Function):
278
+ if not name:
279
+ name = f"_func_{parent.operator.value}_{string_to_hash(str(parent))}"
213
280
  return function_to_concept(parent, name, namespace)
214
281
  elif isinstance(parent, ListWrapper):
282
+ if not name:
283
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(parent))}"
215
284
  return constant_to_concept(parent, name, namespace, purpose, metadata)
216
285
  else:
286
+ if not name:
287
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(parent))}"
217
288
  return constant_to_concept(parent, name, namespace, purpose, metadata)