pytrilogy 0.0.1.104__tar.gz → 0.0.1.105__tar.gz

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 (99) hide show
  1. {pytrilogy-0.0.1.104/pytrilogy.egg-info → pytrilogy-0.0.1.105}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/__init__.py +1 -1
  4. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/models.py +99 -19
  5. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/concept_strategies_v3.py +1 -0
  6. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/common.py +19 -7
  7. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/filter_node.py +37 -10
  8. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/merge_node.py +11 -1
  9. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/base.py +12 -3
  10. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/parsing/common.py +30 -0
  11. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/parsing/parse_engine.py +42 -82
  12. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/parsing/render.py +0 -122
  13. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/LICENSE.md +0 -0
  14. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/README.md +0 -0
  15. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/pyproject.toml +0 -0
  16. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/SOURCES.txt +0 -0
  17. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/dependency_links.txt +0 -0
  18. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/entry_points.txt +0 -0
  19. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/requires.txt +0 -0
  20. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/pytrilogy.egg-info/top_level.txt +0 -0
  21. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/setup.cfg +0 -0
  22. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/setup.py +0 -0
  23. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_declarations.py +0 -0
  24. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_derived_concepts.py +0 -0
  25. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_discovery_nodes.py +0 -0
  26. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_environment.py +0 -0
  27. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_functions.py +0 -0
  28. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_imports.py +0 -0
  29. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_metadata.py +0 -0
  30. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_models.py +0 -0
  31. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_multi_join_assignments.py +0 -0
  32. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_parsing.py +0 -0
  33. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_partial_handling.py +0 -0
  34. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_query_processing.py +0 -0
  35. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_select.py +0 -0
  36. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_statements.py +0 -0
  37. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_undefined_concept.py +0 -0
  38. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/tests/test_where_clause.py +0 -0
  39. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/compiler.py +0 -0
  40. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/constants.py +0 -0
  41. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/__init__.py +0 -0
  42. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/constants.py +0 -0
  43. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/enums.py +0 -0
  44. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/env_processor.py +0 -0
  45. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/environment_helpers.py +0 -0
  46. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/ergonomics.py +0 -0
  47. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/exceptions.py +0 -0
  48. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/functions.py +0 -0
  49. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/graph_models.py +0 -0
  50. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/internal.py +0 -0
  51. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/__init__.py +0 -0
  52. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/graph_utils.py +0 -0
  53. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/__init__.py +0 -0
  54. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  55. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/concept_merge.py +0 -0
  56. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/group_node.py +0 -0
  57. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  58. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  59. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  60. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/select_node.py +0 -0
  61. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  62. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/node_generators/window_node.py +0 -0
  63. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/__init__.py +0 -0
  64. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/base_node.py +0 -0
  65. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/filter_node.py +0 -0
  66. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/group_node.py +0 -0
  67. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/merge_node.py +0 -0
  68. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  69. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  70. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/nodes/window_node.py +0 -0
  71. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/processing/utility.py +0 -0
  72. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/core/query_processor.py +0 -0
  73. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/__init__.py +0 -0
  74. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/bigquery.py +0 -0
  75. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/common.py +0 -0
  76. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/config.py +0 -0
  77. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/duckdb.py +0 -0
  78. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/enums.py +0 -0
  79. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/postgres.py +0 -0
  80. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/presto.py +0 -0
  81. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/snowflake.py +0 -0
  82. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/dialect/sql_server.py +0 -0
  83. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/docs/__init__.py +0 -0
  84. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/engine.py +0 -0
  85. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/executor.py +0 -0
  86. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/hooks/__init__.py +0 -0
  87. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/hooks/base_hook.py +0 -0
  88. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/hooks/graph_hook.py +0 -0
  89. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/hooks/query_debugger.py +0 -0
  90. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/metadata/__init__.py +0 -0
  91. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/parser.py +0 -0
  92. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/parsing/__init__.py +0 -0
  93. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/parsing/config.py +0 -0
  94. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/parsing/exceptions.py +0 -0
  95. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/parsing/helpers.py +0 -0
  96. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/py.typed +0 -0
  97. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/scripts/__init__.py +0 -0
  98. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/scripts/trilogy.py +0 -0
  99. {pytrilogy-0.0.1.104 → pytrilogy-0.0.1.105}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.104
3
+ Version: 0.0.1.105
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.104
3
+ Version: 0.0.1.105
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -3,6 +3,6 @@ from trilogy.dialect.enums import Dialects
3
3
  from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
 
6
- __version__ = "0.0.1.104"
6
+ __version__ = "0.0.1.105"
7
7
 
8
8
  __all__ = ["parse", "Executor", "Dialects", "Environment"]
@@ -113,15 +113,27 @@ NAMESPACED_TYPES = Union[
113
113
 
114
114
 
115
115
  class Namespaced(ABC):
116
- pass
117
116
 
118
117
  def with_namespace(self, namespace: str):
119
118
  raise NotImplementedError
120
119
 
121
120
 
122
- class SelectGrain(ABC):
123
- pass
121
+ class ConceptArgs(ABC):
122
+
123
+ @property
124
+ def concept_arguments(self) -> List["Concept"]:
125
+ raise NotImplementedError
126
+
127
+ @property
128
+ def existence_arguments(self) -> List["Concept"]:
129
+ return []
130
+
131
+ @property
132
+ def row_arguments(self) -> List["Concept"]:
133
+ return self.concept_arguments
124
134
 
135
+
136
+ class SelectGrain(ABC):
125
137
  def with_select_grain(self, grain: Grain):
126
138
  raise NotImplementedError
127
139
 
@@ -1395,16 +1407,11 @@ class MultiSelectStatement(Namespaced, BaseModel):
1395
1407
  return output
1396
1408
 
1397
1409
  def find_source(self, concept: Concept, cte: CTE) -> Concept:
1398
- all = []
1399
1410
  for x in self.align.items:
1400
1411
  if concept.name == x.alias:
1401
1412
  for c in x.concepts:
1402
1413
  if c.address in cte.output_lcl:
1403
- all.append(c)
1404
-
1405
- if len(all) == 1:
1406
- return all[0]
1407
-
1414
+ return c
1408
1415
  raise SyntaxError(
1409
1416
  f"Could not find upstream map for multiselect {str(concept)} on cte ({cte})"
1410
1417
  )
@@ -2546,7 +2553,7 @@ class LazyEnvironment(Environment):
2546
2553
  return super().__getattribute__(name)
2547
2554
 
2548
2555
 
2549
- class Comparison(Namespaced, SelectGrain, BaseModel):
2556
+ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2550
2557
  left: Union[
2551
2558
  int,
2552
2559
  str,
@@ -2598,7 +2605,7 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
2598
2605
  return f"{str(self.left)} {self.operator.value} {str(self.right)}"
2599
2606
 
2600
2607
  def with_namespace(self, namespace: str):
2601
- return Comparison(
2608
+ return self.__class__(
2602
2609
  left=(
2603
2610
  self.left.with_namespace(namespace)
2604
2611
  if isinstance(self.left, Namespaced)
@@ -2613,7 +2620,7 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
2613
2620
  )
2614
2621
 
2615
2622
  def with_select_grain(self, grain: Grain):
2616
- return Comparison(
2623
+ return self.__class__(
2617
2624
  left=(
2618
2625
  self.left.with_select_grain(grain)
2619
2626
  if isinstance(self.left, SelectGrain)
@@ -2632,7 +2639,9 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
2632
2639
  output: List[Concept] = []
2633
2640
  if isinstance(self.left, (Concept,)):
2634
2641
  output += [self.left]
2635
- if isinstance(self.left, (Conditional, Parenthetical)):
2642
+ if isinstance(
2643
+ self.left, (Comparison, SubselectComparison, Conditional, Parenthetical)
2644
+ ):
2636
2645
  output += self.left.input
2637
2646
  if isinstance(self.left, FilterItem):
2638
2647
  output += self.left.concept_arguments
@@ -2641,7 +2650,9 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
2641
2650
 
2642
2651
  if isinstance(self.right, (Concept,)):
2643
2652
  output += [self.right]
2644
- if isinstance(self.right, (Conditional, Parenthetical)):
2653
+ if isinstance(
2654
+ self.right, (Comparison, SubselectComparison, Conditional, Parenthetical)
2655
+ ):
2645
2656
  output += self.right.input
2646
2657
  if isinstance(self.right, FilterItem):
2647
2658
  output += self.right.concept_arguments
@@ -2658,8 +2669,31 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
2658
2669
  return output
2659
2670
 
2660
2671
 
2672
+ class SubselectComparison(Comparison):
2673
+
2674
+ @property
2675
+ def row_arguments(self) -> List[Concept]:
2676
+ return get_concept_arguments(self.left)
2677
+
2678
+ @property
2679
+ def existence_arguments(self) -> List[Concept]:
2680
+ return get_concept_arguments(self.right)
2681
+
2682
+ def with_select_grain(self, grain: Grain):
2683
+ # there's no need to pass the select grain through to a subselect comparison
2684
+ return self.__class__(
2685
+ left=(
2686
+ self.left.with_select_grain(grain)
2687
+ if isinstance(self.left, SelectGrain)
2688
+ else self.left
2689
+ ),
2690
+ right=self.right,
2691
+ operator=self.operator,
2692
+ )
2693
+
2694
+
2661
2695
  class CaseWhen(Namespaced, SelectGrain, BaseModel):
2662
- comparison: Conditional | Comparison
2696
+ comparison: Conditional | SubselectComparison | Comparison
2663
2697
  expr: "Expr"
2664
2698
 
2665
2699
  @property
@@ -2726,7 +2760,7 @@ class CaseElse(Namespaced, SelectGrain, BaseModel):
2726
2760
  )
2727
2761
 
2728
2762
 
2729
- class Conditional(Namespaced, SelectGrain, BaseModel):
2763
+ class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2730
2764
  left: Union[
2731
2765
  int,
2732
2766
  str,
@@ -2821,6 +2855,32 @@ class Conditional(Namespaced, SelectGrain, BaseModel):
2821
2855
  output += get_concept_arguments(self.right)
2822
2856
  return output
2823
2857
 
2858
+ @property
2859
+ def row_arguments(self) -> List[Concept]:
2860
+ output = []
2861
+ if isinstance(self.left, ConceptArgs):
2862
+ output += self.left.row_arguments
2863
+ else:
2864
+ output += get_concept_arguments(self.left)
2865
+ if isinstance(self.right, ConceptArgs):
2866
+ output += self.right.row_arguments
2867
+ else:
2868
+ output += get_concept_arguments(self.right)
2869
+ return output
2870
+
2871
+ @property
2872
+ def existence_arguments(self) -> List[Concept]:
2873
+ output = []
2874
+ if isinstance(self.left, ConceptArgs):
2875
+ output += self.left.existence_arguments
2876
+ else:
2877
+ output += get_concept_arguments(self.left)
2878
+ if isinstance(self.right, ConceptArgs):
2879
+ output += self.right.existence_arguments
2880
+ else:
2881
+ output += get_concept_arguments(self.right)
2882
+ return output
2883
+
2824
2884
 
2825
2885
  class AggregateWrapper(Namespaced, SelectGrain, BaseModel):
2826
2886
  function: Function
@@ -2864,8 +2924,8 @@ class AggregateWrapper(Namespaced, SelectGrain, BaseModel):
2864
2924
  return AggregateWrapper(function=self.function.with_select_grain(grain), by=by)
2865
2925
 
2866
2926
 
2867
- class WhereClause(Namespaced, SelectGrain, BaseModel):
2868
- conditional: Union[Comparison, Conditional, "Parenthetical"]
2927
+ class WhereClause(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2928
+ conditional: Union[SubselectComparison, Comparison, Conditional, "Parenthetical"]
2869
2929
 
2870
2930
  @property
2871
2931
  def input(self) -> List[Concept]:
@@ -2875,6 +2935,14 @@ class WhereClause(Namespaced, SelectGrain, BaseModel):
2875
2935
  def concept_arguments(self) -> List[Concept]:
2876
2936
  return self.conditional.concept_arguments
2877
2937
 
2938
+ @property
2939
+ def row_arguments(self) -> List[Concept]:
2940
+ return self.conditional.row_arguments
2941
+
2942
+ @property
2943
+ def existence_arguments(self) -> List[Concept]:
2944
+ return self.conditional.existence_arguments
2945
+
2878
2946
  def with_namespace(self, namespace: str) -> WhereClause:
2879
2947
  return WhereClause(conditional=self.conditional.with_namespace(namespace))
2880
2948
 
@@ -3062,7 +3130,7 @@ class RowsetItem(Namespaced, BaseModel):
3062
3130
  return [self.content]
3063
3131
 
3064
3132
 
3065
- class Parenthetical(Namespaced, SelectGrain, BaseModel):
3133
+ class Parenthetical(ConceptArgs, Namespaced, SelectGrain, BaseModel):
3066
3134
  content: "Expr"
3067
3135
 
3068
3136
  def __str__(self):
@@ -3106,6 +3174,18 @@ class Parenthetical(Namespaced, SelectGrain, BaseModel):
3106
3174
  base.append(x)
3107
3175
  return base
3108
3176
 
3177
+ @property
3178
+ def row_arguments(self) -> List[Concept]:
3179
+ if isinstance(self.content, ConceptArgs):
3180
+ return self.content.row_arguments
3181
+ return self.concept_arguments
3182
+
3183
+ @property
3184
+ def existence_arguments(self) -> List[Concept]:
3185
+ if isinstance(self.content, ConceptArgs):
3186
+ return self.content.existence_arguments
3187
+ return self.concept_arguments
3188
+
3109
3189
  @property
3110
3190
  def input(self):
3111
3191
  base = []
@@ -317,6 +317,7 @@ def generate_node(
317
317
  return gen_basic_node(
318
318
  concept, local_optional, environment, g, depth + 1, source_concepts, history
319
319
  )
320
+
320
321
  elif concept.derivation == PurposeLineage.ROOT:
321
322
  logger.info(
322
323
  f"{depth_to_prefix(depth)}{LOGGER_PREFIX} for {concept.address}, generating select node with optional {[x.address for x in local_optional]}"
@@ -45,21 +45,33 @@ def resolve_function_parent_concepts(concept: Concept) -> List[Concept]:
45
45
  return unique(concept.lineage.concept_arguments, "address")
46
46
 
47
47
 
48
- def resolve_filter_parent_concepts(concept: Concept) -> Tuple[Concept, List[Concept]]:
48
+ def resolve_filter_parent_concepts(
49
+ concept: Concept,
50
+ ) -> Tuple[Concept, List[Concept], List[Concept]]:
49
51
  if not isinstance(concept.lineage, FilterItem):
50
- raise ValueError
52
+ raise ValueError(
53
+ f"Concept {concept} lineage is not filter item, is {type(concept.lineage)}"
54
+ )
51
55
  direct_parent = concept.lineage.content
52
- base = [direct_parent]
53
- base += concept.lineage.where.concept_arguments
56
+ base_existence = []
57
+ base_rows = [direct_parent]
58
+ base_rows += concept.lineage.where.row_arguments
59
+ base_existence += concept.lineage.where.existence_arguments
54
60
  if direct_parent.grain:
55
- base += direct_parent.grain.components_copy
61
+ base_rows += direct_parent.grain.components_copy
56
62
  if (
57
63
  isinstance(direct_parent, Concept)
58
64
  and direct_parent.purpose == Purpose.PROPERTY
59
65
  and direct_parent.keys
60
66
  ):
61
- base += direct_parent.keys
62
- return concept.lineage.content, unique(base, "address")
67
+ base_rows += direct_parent.keys
68
+ if concept.lineage.where.existence_arguments:
69
+ return (
70
+ concept.lineage.content,
71
+ unique(base_rows, "address"),
72
+ unique(base_existence, "address"),
73
+ )
74
+ return concept.lineage.content, unique(base_rows, "address"), []
63
75
 
64
76
 
65
77
  def gen_property_enrichment_node(
@@ -11,7 +11,7 @@ from trilogy.core.processing.node_generators.common import (
11
11
  resolve_filter_parent_concepts,
12
12
  )
13
13
  from trilogy.constants import logger
14
- from trilogy.core.processing.utility import padding
14
+ from trilogy.core.processing.utility import padding, unique
15
15
  from trilogy.core.processing.node_generators.common import concept_to_relevant_joins
16
16
 
17
17
  LOGGER_PREFIX = "[GEN_FILTER_NODE]"
@@ -26,30 +26,57 @@ def gen_filter_node(
26
26
  source_concepts,
27
27
  history: History | None = None,
28
28
  ) -> MergeNode | FilterNode | None:
29
- immediate_parent, parent_concepts = resolve_filter_parent_concepts(concept)
29
+ immediate_parent, parent_row_concepts, parent_existence_concepts = (
30
+ resolve_filter_parent_concepts(concept)
31
+ )
30
32
 
31
- logger.info(f"{padding(depth)}{LOGGER_PREFIX} fetching filter node parents")
33
+ logger.info(
34
+ f"{padding(depth)}{LOGGER_PREFIX} fetching filter node row parents {[x.address for x in parent_row_concepts]}"
35
+ )
36
+ core_parents = []
32
37
  parent = source_concepts(
33
- mandatory_list=parent_concepts,
38
+ mandatory_list=parent_row_concepts,
34
39
  environment=environment,
35
40
  g=g,
36
41
  depth=depth + 1,
37
42
  history=history,
38
43
  )
44
+
39
45
  if not parent:
40
46
  return None
47
+ core_parents.append(parent)
48
+ if parent_existence_concepts:
49
+ logger.info(
50
+ f"{padding(depth)}{LOGGER_PREFIX} fetching filter node existence parents {[x.address for x in parent_existence_concepts]}"
51
+ )
52
+ parent_existence = source_concepts(
53
+ mandatory_list=parent_existence_concepts,
54
+ environment=environment,
55
+ g=g,
56
+ depth=depth + 1,
57
+ history=history,
58
+ )
59
+ if not parent_existence:
60
+ return None
61
+ core_parents.append(parent_existence)
62
+
41
63
  filter_node = FilterNode(
42
- input_concepts=[immediate_parent] + parent_concepts,
43
- output_concepts=[concept, immediate_parent] + parent_concepts,
64
+ input_concepts=unique(
65
+ [immediate_parent] + parent_row_concepts + parent_existence_concepts,
66
+ "address",
67
+ ),
68
+ output_concepts=[concept, immediate_parent] + parent_row_concepts,
44
69
  environment=environment,
45
70
  g=g,
46
- parents=[parent],
71
+ parents=core_parents,
47
72
  )
48
- if not local_optional:
73
+ if not local_optional or all(
74
+ [x.address in [y.address for y in parent_row_concepts] for x in local_optional]
75
+ ):
49
76
  return filter_node
50
77
  enrich_node = source_concepts( # this fetches the parent + join keys
51
78
  # to then connect to the rest of the query
52
- mandatory_list=[immediate_parent] + parent_concepts + local_optional,
79
+ mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
53
80
  environment=environment,
54
81
  g=g,
55
82
  depth=depth + 1,
@@ -75,7 +102,7 @@ def gen_filter_node(
75
102
  left_node=enrich_node,
76
103
  right_node=filter_node,
77
104
  concepts=concept_to_relevant_joins(
78
- [immediate_parent] + parent_concepts
105
+ [immediate_parent] + parent_row_concepts
79
106
  ),
80
107
  join_type=JoinType.LEFT_OUTER,
81
108
  filter_to_mutual=False,
@@ -87,8 +87,18 @@ def gen_merge_node(
87
87
  ) -> Optional[MergeNode]:
88
88
  join_candidates: List[PathInfo] = []
89
89
  # anchor on datasources
90
+ final_all_concepts = []
91
+ # implicit_upstream = {}
92
+ for x in all_concepts:
93
+ # if x.derivation in (PurposeLineage.AGGREGATE, PurposeLineage.BASIC):
94
+ # final_all_concepts +=resolve_function_parent_concepts(x)
95
+ # elif x.derivation == PurposeLineage.FILTER:
96
+ # final_all_concepts +=resolve_filter_parent_concepts(x)
97
+ # else:
98
+ # final_all_concepts.append(x)
99
+ final_all_concepts.append(x)
90
100
  for datasource in environment.datasources.values():
91
- path = identify_ds_join_paths(all_concepts, g, datasource, accept_partial)
101
+ path = identify_ds_join_paths(final_all_concepts, g, datasource, accept_partial)
92
102
  if path and path.reduced_concepts:
93
103
  join_candidates.append(path)
94
104
  join_candidates.sort(key=lambda x: sum([len(v) for v in x.paths.values()]))
@@ -22,6 +22,7 @@ from trilogy.core.models import (
22
22
  CompiledCTE,
23
23
  Conditional,
24
24
  Comparison,
25
+ SubselectComparison,
25
26
  OrderItem,
26
27
  WindowItem,
27
28
  FilterItem,
@@ -273,14 +274,13 @@ class BaseDialect:
273
274
  ]
274
275
  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
275
276
  elif isinstance(c.lineage, FilterItem):
276
- rval = f"CASE WHEN {self.render_expr(c.lineage.where.conditional)} THEN {self.render_concept_sql(c.lineage.content, cte=cte, alias=False)} ELSE NULL END"
277
+ 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"
277
278
  elif isinstance(c.lineage, RowsetItem):
278
279
  rval = f"{self.render_concept_sql(c.lineage.content, cte=cte, alias=False)}"
279
280
  elif isinstance(c.lineage, MultiSelectStatement):
280
281
  rval = f"{self.render_concept_sql(c.lineage.find_source(c, cte), cte=cte, alias=False)}"
281
282
  elif isinstance(c.lineage, MergeStatement):
282
283
  rval = f"{self.render_concept_sql(c.lineage.find_source(c, cte), cte=cte, alias=False)}"
283
- # rval = f"{self.FUNCTION_MAP[FunctionType.COALESCE](*[self.render_concept_sql(parent, cte=cte, alias=False) for parent in c.lineage.find_sources(c, cte)])}"
284
284
  elif isinstance(c.lineage, AggregateWrapper):
285
285
  args = [
286
286
  self.render_expr(v, cte) # , alias=False)
@@ -330,6 +330,7 @@ class BaseDialect:
330
330
  Function,
331
331
  Conditional,
332
332
  Comparison,
333
+ SubselectComparison,
333
334
  Concept,
334
335
  str,
335
336
  int,
@@ -358,7 +359,15 @@ class BaseDialect:
358
359
  # if isinstance(e, Concept):
359
360
  # cte = cte or cte_map.get(e.address, None)
360
361
 
361
- if isinstance(e, Comparison):
362
+ if isinstance(e, SubselectComparison):
363
+ assert cte, "Subselects must be rendered with a CTE in context"
364
+ if isinstance(e.right, Concept):
365
+ return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} (select {self.render_expr(e.right, cte=cte, cte_map=cte_map)} from {cte.source_map[e.right.address][0]})"
366
+ else:
367
+ raise NotImplementedError(
368
+ f"Subselects must be a concept, got {e.right}"
369
+ )
370
+ elif isinstance(e, Comparison):
362
371
  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)}"
363
372
  elif isinstance(e, Conditional):
364
373
  # conditions need to be nested in parentheses
@@ -174,3 +174,33 @@ def agg_wrapper_to_concept(
174
174
  keys=tuple(parent.by) if parent.by else keys,
175
175
  )
176
176
  return out
177
+
178
+
179
+ def arbitrary_to_concept(
180
+ parent: (
181
+ AggregateWrapper
182
+ | WindowItem
183
+ | FilterItem
184
+ | Function
185
+ | ListWrapper
186
+ | int
187
+ | float
188
+ | str
189
+ ),
190
+ namespace: str,
191
+ name: str,
192
+ metadata: Metadata | None = None,
193
+ purpose: Purpose | None = None,
194
+ ) -> Concept:
195
+ if isinstance(parent, AggregateWrapper):
196
+ return agg_wrapper_to_concept(parent, namespace, name, metadata, purpose)
197
+ elif isinstance(parent, WindowItem):
198
+ return window_item_to_concept(parent, name, namespace, purpose, metadata)
199
+ elif isinstance(parent, FilterItem):
200
+ return filter_item_to_concept(parent, name, namespace, purpose, metadata)
201
+ elif isinstance(parent, Function):
202
+ return function_to_concept(parent, name, namespace)
203
+ elif isinstance(parent, ListWrapper):
204
+ return constant_to_concept(parent, name, namespace, purpose, metadata)
205
+ else:
206
+ return constant_to_concept(parent, name, namespace, purpose, metadata)
@@ -17,6 +17,7 @@ from trilogy.constants import (
17
17
  DEFAULT_NAMESPACE,
18
18
  NULL_VALUE,
19
19
  VIRTUAL_CONCEPT_PREFIX,
20
+ MagicConstants,
20
21
  )
21
22
  from trilogy.core.enums import (
22
23
  BooleanOperator,
@@ -62,6 +63,7 @@ from trilogy.core.models import (
62
63
  ColumnAssignment,
63
64
  Comment,
64
65
  Comparison,
66
+ SubselectComparison,
65
67
  Concept,
66
68
  ConceptTransform,
67
69
  Conditional,
@@ -108,6 +110,7 @@ from trilogy.parsing.common import (
108
110
  function_to_concept,
109
111
  filter_item_to_concept,
110
112
  constant_to_concept,
113
+ arbitrary_to_concept,
111
114
  )
112
115
 
113
116
  CONSTANT_TYPES = (int, float, str, bool, ListWrapper)
@@ -187,9 +190,9 @@ grammar = r"""
187
190
 
188
191
  // FUNCTION blocks
189
192
  function: raw_function
190
- function_binding_item: IDENTIFIER data_type
193
+ function_binding_item: IDENTIFIER ":" data_type
191
194
  function_binding_list: (function_binding_item ",")* function_binding_item ","?
192
- raw_function: "def" "rawsql" IDENTIFIER "(" function_binding_list ")" "-" ">" data_type "as"i MULTILINE_STRING
195
+ raw_function: "bind" "sql" IDENTIFIER "(" function_binding_list ")" "-" ">" data_type "as"i MULTILINE_STRING
193
196
 
194
197
 
195
198
  // user_id where state = Mexico
@@ -246,7 +249,9 @@ grammar = r"""
246
249
 
247
250
  COMPARISON_OPERATOR: (/is[\s]+not/ | "is" |"=" | ">" | "<" | ">=" | "<=" | "!=" )
248
251
 
249
- comparison: (expr COMPARISON_OPERATOR expr) | (expr array_comparison expr_tuple)
252
+ comparison: (expr COMPARISON_OPERATOR expr) | (expr array_comparison expr_tuple)
253
+
254
+ subselect_comparison: expr array_comparison expr
250
255
 
251
256
  expr_tuple: "(" (expr ",")* expr ","? ")"
252
257
 
@@ -258,7 +263,7 @@ grammar = r"""
258
263
 
259
264
  parenthetical: "(" (conditional | expr) ")"
260
265
 
261
- expr: window_item | filter_item | comparison | fgroup | aggregate_functions | unnest | _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions | literal | expr_reference | index_access | attr_access | parenthetical
266
+ expr: window_item | filter_item | comparison | subselect_comparison | fgroup | aggregate_functions | unnest | _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions | literal | expr_reference | index_access | attr_access | parenthetical
262
267
 
263
268
  // functions
264
269
 
@@ -525,43 +530,11 @@ class ParseToObjects(Transformer):
525
530
  concept.metadata.line_number = meta.line
526
531
  self.environment.add_concept(concept, meta=meta)
527
532
  final.append(concept)
528
- elif isinstance(arg, FilterItem):
533
+ elif isinstance(
534
+ arg, (FilterItem, WindowItem, AggregateWrapper, ListWrapper)
535
+ ):
529
536
  id_hash = string_to_hash(str(arg))
530
- concept = filter_item_to_concept(
531
- arg,
532
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
533
- namespace=self.environment.namespace,
534
- )
535
- if concept.metadata:
536
- concept.metadata.line_number = meta.line
537
- self.environment.add_concept(concept, meta=meta)
538
- final.append(concept)
539
- elif isinstance(arg, WindowItem):
540
- id_hash = string_to_hash(str(arg))
541
- concept = window_item_to_concept(
542
- arg,
543
- namespace=self.environment.namespace,
544
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
545
- )
546
- if concept.metadata:
547
- concept.metadata.line_number = meta.line
548
- self.environment.add_concept(concept, meta=meta)
549
- final.append(concept)
550
- elif isinstance(arg, AggregateWrapper):
551
- id_hash = string_to_hash(str(arg))
552
- concept = agg_wrapper_to_concept(
553
- arg,
554
- namespace=self.environment.namespace,
555
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
556
- )
557
- if concept.metadata:
558
- concept.metadata.line_number = meta.line
559
- self.environment.add_concept(concept, meta=meta)
560
- final.append(concept)
561
- # we don't need virtual types for most constants
562
- elif isinstance(arg, (ListWrapper)):
563
- id_hash = string_to_hash(str(arg))
564
- concept = constant_to_concept(
537
+ concept = arbitrary_to_concept(
565
538
  arg,
566
539
  name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
567
540
  namespace=self.environment.namespace,
@@ -570,6 +543,7 @@ class ParseToObjects(Transformer):
570
543
  concept.metadata.line_number = meta.line
571
544
  self.environment.add_concept(concept, meta=meta)
572
545
  final.append(concept)
546
+
573
547
  else:
574
548
  final.append(arg)
575
549
  return final
@@ -773,8 +747,10 @@ class ParseToObjects(Transformer):
773
747
  while isinstance(source_value, Parenthetical):
774
748
  source_value = source_value.content
775
749
 
776
- if isinstance(source_value, FilterItem):
777
- concept = filter_item_to_concept(
750
+ if isinstance(
751
+ source_value, (FilterItem, WindowItem, AggregateWrapper, Function)
752
+ ):
753
+ concept = arbitrary_to_concept(
778
754
  source_value,
779
755
  name=name,
780
756
  namespace=namespace,
@@ -782,31 +758,6 @@ class ParseToObjects(Transformer):
782
758
  metadata=metadata,
783
759
  )
784
760
 
785
- if concept.metadata:
786
- concept.metadata.line_number = meta.line
787
- self.environment.add_concept(concept, meta=meta)
788
- return ConceptDerivation(concept=concept)
789
- elif isinstance(source_value, WindowItem):
790
-
791
- concept = window_item_to_concept(
792
- source_value,
793
- name=name,
794
- namespace=namespace,
795
- purpose=purpose,
796
- metadata=metadata,
797
- )
798
- if concept.metadata:
799
- concept.metadata.line_number = meta.line
800
- self.environment.add_concept(concept, meta=meta)
801
- return ConceptDerivation(concept=concept)
802
- elif isinstance(source_value, AggregateWrapper):
803
- concept = agg_wrapper_to_concept(
804
- source_value,
805
- namespace=namespace,
806
- name=name,
807
- metadata=metadata,
808
- purpose=purpose,
809
- )
810
761
  if concept.metadata:
811
762
  concept.metadata.line_number = meta.line
812
763
  self.environment.add_concept(concept, meta=meta)
@@ -824,19 +775,6 @@ class ParseToObjects(Transformer):
824
775
  self.environment.add_concept(concept, meta=meta)
825
776
  return ConceptDerivation(concept=concept)
826
777
 
827
- elif isinstance(source_value, Function):
828
- function: Function = source_value
829
-
830
- concept = function_to_concept(
831
- function,
832
- name=name,
833
- namespace=namespace,
834
- )
835
- if concept.metadata:
836
- concept.metadata.line_number = meta.line
837
- self.environment.add_concept(concept, meta=meta)
838
- return ConceptDerivation(concept=concept)
839
-
840
778
  raise SyntaxError(
841
779
  f"Received invalid type {type(args[2])} {args[2]} as input to select"
842
780
  " transform"
@@ -1219,7 +1157,14 @@ class ParseToObjects(Transformer):
1219
1157
  def where(self, args):
1220
1158
  root = args[0]
1221
1159
  if not isinstance(root, (Comparison, Conditional, Parenthetical)):
1222
- root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
1160
+ if arg_to_datatype(root) == DataType.BOOL:
1161
+ root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
1162
+ else:
1163
+ root = Comparison(
1164
+ left=root,
1165
+ right=MagicConstants.NULL,
1166
+ operator=ComparisonOperator.IS_NOT,
1167
+ )
1223
1168
  return WhereClause(conditional=root)
1224
1169
 
1225
1170
  @v_args(meta=True)
@@ -1232,7 +1177,6 @@ class ParseToObjects(Transformer):
1232
1177
 
1233
1178
  @v_args(meta=True)
1234
1179
  def raw_function(self, meta: Meta, args) -> Function:
1235
- print(args)
1236
1180
  identity = args[0]
1237
1181
  fargs = args[1]
1238
1182
  output = args[2]
@@ -1273,6 +1217,22 @@ class ParseToObjects(Transformer):
1273
1217
  def comparison(self, args) -> Comparison:
1274
1218
  return Comparison(left=args[0], right=args[2], operator=args[1])
1275
1219
 
1220
+ @v_args(meta=True)
1221
+ def subselect_comparison(self, meta: Meta, args) -> SubselectComparison:
1222
+ right = args[2]
1223
+ if not isinstance(right, Concept):
1224
+ right = arbitrary_to_concept(
1225
+ right,
1226
+ namespace=self.environment.namespace,
1227
+ name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(right))}",
1228
+ )
1229
+ self.environment.add_concept(right)
1230
+ return SubselectComparison(
1231
+ left=args[0],
1232
+ right=right,
1233
+ operator=args[1],
1234
+ )
1235
+
1276
1236
  def expr_tuple(self, args):
1277
1237
  return Parenthetical(content=args)
1278
1238
 
@@ -298,128 +298,6 @@ class Renderer:
298
298
  def _(self, arg: "FilterItem"):
299
299
  return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
300
300
 
301
- @to_string.register
302
- def _(self, arg: "WindowItem"):
303
- over = ",".join(self.to_string(c) for c in arg.over)
304
- order = ",".join(self.to_string(c) for c in arg.order_by)
305
- if over:
306
- return (
307
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
308
- )
309
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
310
-
311
- @to_string.register
312
- def _(self, arg: "FilterItem"):
313
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
314
-
315
- @to_string.register
316
- def _(self, arg: "ImportStatement"):
317
- return f"import {arg.path} as {arg.alias};"
318
-
319
- @to_string.register
320
- def _(self, arg: "WindowItem"):
321
- over = ",".join(self.to_string(c) for c in arg.over)
322
- order = ",".join(self.to_string(c) for c in arg.order_by)
323
- if over:
324
- return (
325
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
326
- )
327
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
328
-
329
- @to_string.register
330
- def _(self, arg: "FilterItem"):
331
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
332
-
333
- @to_string.register
334
- def _(self, arg: "ImportStatement"):
335
- return f"import {arg.path} as {arg.alias};"
336
-
337
- @to_string.register
338
- def _(self, arg: "WindowItem"):
339
- over = ",".join(self.to_string(c) for c in arg.over)
340
- order = ",".join(self.to_string(c) for c in arg.order_by)
341
- if over:
342
- return (
343
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
344
- )
345
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
346
-
347
- @to_string.register
348
- def _(self, arg: "FilterItem"):
349
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
350
-
351
- @to_string.register
352
- def _(self, arg: "ImportStatement"):
353
- return f"import {arg.path} as {arg.alias};"
354
-
355
- @to_string.register
356
- def _(self, arg: "WindowItem"):
357
- over = ",".join(self.to_string(c) for c in arg.over)
358
- order = ",".join(self.to_string(c) for c in arg.order_by)
359
- if over:
360
- return (
361
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
362
- )
363
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
364
-
365
- @to_string.register
366
- def _(self, arg: "FilterItem"):
367
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
368
-
369
- @to_string.register
370
- def _(self, arg: "ImportStatement"):
371
- return f"import {arg.path} as {arg.alias};"
372
-
373
- @to_string.register
374
- def _(self, arg: "WindowItem"):
375
- over = ",".join(self.to_string(c) for c in arg.over)
376
- order = ",".join(self.to_string(c) for c in arg.order_by)
377
- if over:
378
- return (
379
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
380
- )
381
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
382
-
383
- @to_string.register
384
- def _(self, arg: "FilterItem"):
385
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
386
-
387
- @to_string.register
388
- def _(self, arg: "ImportStatement"):
389
- return f"import {arg.path} as {arg.alias};"
390
-
391
- @to_string.register
392
- def _(self, arg: "WindowItem"):
393
- over = ",".join(self.to_string(c) for c in arg.over)
394
- order = ",".join(self.to_string(c) for c in arg.order_by)
395
- if over:
396
- return (
397
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
398
- )
399
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
400
-
401
- @to_string.register
402
- def _(self, arg: "FilterItem"):
403
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
404
-
405
- @to_string.register
406
- def _(self, arg: "ImportStatement"):
407
- return f"import {arg.path} as {arg.alias};"
408
-
409
- @to_string.register
410
- def _(self, arg: "WindowItem"):
411
- over = ",".join(self.to_string(c) for c in arg.over)
412
- order = ",".join(self.to_string(c) for c in arg.order_by)
413
- if over:
414
- return (
415
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
416
- )
417
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
418
-
419
- @to_string.register
420
- def _(self, arg: "FilterItem"):
421
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
422
-
423
301
  @to_string.register
424
302
  def _(self, arg: "ImportStatement"):
425
303
  return f"import {arg.path} as {arg.alias};"
File without changes
File without changes
File without changes
File without changes