pytrilogy 0.0.1.112__tar.gz → 0.0.1.113__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.112/pytrilogy.egg-info → pytrilogy-0.0.1.113}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_declarations.py +3 -1
  4. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_parsing.py +24 -1
  5. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_select.py +1 -0
  6. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_where_clause.py +1 -1
  7. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/__init__.py +1 -1
  8. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/enums.py +7 -0
  9. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/models.py +36 -0
  10. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/base.py +37 -17
  11. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/parsing/common.py +1 -1
  12. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/parsing/parse_engine.py +59 -311
  13. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/LICENSE.md +0 -0
  14. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/README.md +0 -0
  15. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/pyproject.toml +0 -0
  16. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/SOURCES.txt +0 -0
  17. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/dependency_links.txt +0 -0
  18. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/entry_points.txt +0 -0
  19. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/requires.txt +0 -0
  20. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/pytrilogy.egg-info/top_level.txt +0 -0
  21. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/setup.cfg +0 -0
  22. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/setup.py +0 -0
  23. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_derived_concepts.py +0 -0
  24. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_discovery_nodes.py +0 -0
  25. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_environment.py +0 -0
  26. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_functions.py +0 -0
  27. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_imports.py +0 -0
  28. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_metadata.py +0 -0
  29. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_models.py +0 -0
  30. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_multi_join_assignments.py +0 -0
  31. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_partial_handling.py +0 -0
  32. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_query_processing.py +0 -0
  33. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_statements.py +0 -0
  34. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/tests/test_undefined_concept.py +0 -0
  35. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/compiler.py +0 -0
  36. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/constants.py +0 -0
  37. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/__init__.py +0 -0
  38. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/constants.py +0 -0
  39. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/env_processor.py +0 -0
  40. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/environment_helpers.py +0 -0
  41. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/ergonomics.py +0 -0
  42. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/exceptions.py +0 -0
  43. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/functions.py +0 -0
  44. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/graph_models.py +0 -0
  45. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/internal.py +0 -0
  46. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/optimization.py +0 -0
  47. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/__init__.py +0 -0
  48. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  49. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/graph_utils.py +0 -0
  50. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/__init__.py +0 -0
  51. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  52. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/common.py +0 -0
  53. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/concept_merge_node.py +0 -0
  54. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  55. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/group_node.py +0 -0
  56. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  57. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  58. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  59. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  60. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/select_node.py +0 -0
  61. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  62. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/node_generators/window_node.py +0 -0
  63. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/__init__.py +0 -0
  64. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/base_node.py +0 -0
  65. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/filter_node.py +0 -0
  66. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/group_node.py +0 -0
  67. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/merge_node.py +0 -0
  68. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  69. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  70. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/nodes/window_node.py +0 -0
  71. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/processing/utility.py +0 -0
  72. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/core/query_processor.py +0 -0
  73. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/__init__.py +0 -0
  74. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/bigquery.py +0 -0
  75. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/common.py +0 -0
  76. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/config.py +0 -0
  77. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/duckdb.py +0 -0
  78. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/enums.py +0 -0
  79. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/postgres.py +0 -0
  80. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/presto.py +0 -0
  81. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/snowflake.py +0 -0
  82. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/dialect/sql_server.py +0 -0
  83. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/engine.py +0 -0
  84. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/executor.py +0 -0
  85. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/hooks/__init__.py +0 -0
  86. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/hooks/base_hook.py +0 -0
  87. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/hooks/graph_hook.py +0 -0
  88. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/hooks/query_debugger.py +0 -0
  89. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/metadata/__init__.py +0 -0
  90. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/parser.py +0 -0
  91. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/parsing/__init__.py +0 -0
  92. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/parsing/config.py +0 -0
  93. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/parsing/exceptions.py +0 -0
  94. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/parsing/helpers.py +0 -0
  95. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/parsing/render.py +0 -0
  96. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/py.typed +0 -0
  97. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/scripts/__init__.py +0 -0
  98. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/scripts/trilogy.py +0 -0
  99. {pytrilogy-0.0.1.112 → pytrilogy-0.0.1.113}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.112
3
+ Version: 0.0.1.113
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.112
3
+ Version: 0.0.1.113
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -4,7 +4,7 @@ from trilogy.parser import parse
4
4
  def test_declarations():
5
5
  declarations = """key namespace.user_id int metadata(description="the description");
6
6
  metric namespace.count <- count(namespace.user_id);
7
- metric namespace.distinct_count <- count_distinct(namespace.user_id); # the distinct count of user ids
7
+ metric namespace.distinct_count <- count_distinct(namespace.user_id); #the distinct count of user ids
8
8
  """
9
9
  env, _ = parse(declarations)
10
10
 
@@ -14,3 +14,5 @@ def test_declarations():
14
14
  env.concepts["namespace.distinct_count"].metadata.description
15
15
  == "the distinct count of user ids"
16
16
  )
17
+
18
+ assert env.concepts["namespace.user_id"].metadata.description == "the description"
@@ -211,7 +211,7 @@ rowset test<- select
211
211
  ;
212
212
 
213
213
  select
214
- count(test.name) -> test_name_count;
214
+ count( test.name ) -> test_name_count;
215
215
  """
216
216
  )
217
217
  # assert output_purpose == Purpose.METRIC
@@ -240,3 +240,26 @@ def test_between():
240
240
  ), type(right)
241
241
  assert right.operator == ComparisonOperator.LTE
242
242
  assert right.right == 5
243
+
244
+
245
+ def test_the_comments():
246
+ _, parsed = parse_text(
247
+ """const
248
+ # comment here?
249
+ order_id <- 4; SELECT
250
+ # TOOD - add in more columns?
251
+ order_id # this is the order id
252
+ WHERE
253
+ # order_id should not be null
254
+ order_id
255
+ # in this comp
256
+ is not
257
+ null; # nulls are the worst
258
+
259
+ """
260
+ )
261
+ query = parsed[-1]
262
+ right = query.where_clause.conditional.right
263
+ assert isinstance(right, MagicConstants), type(right)
264
+ rendered = BaseDialect().render_expr(right)
265
+ assert rendered == "null"
@@ -37,6 +37,7 @@ datasource users (
37
37
  env, parsed = parse(declarations)
38
38
 
39
39
  q1 = """select
40
+ # a comment
40
41
  user_id,
41
42
  about_me,
42
43
  count(post_id)->post_count
@@ -150,7 +150,7 @@ select
150
150
  order_id,
151
151
  total_revenue,
152
152
  where
153
- (order_id =1 or order_id = 2) and total_revenue>30
153
+ (order_id = 1 or order_id = 2) and total_revenue>30
154
154
  ;
155
155
 
156
156
 
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.1.112"
7
+ __version__ = "0.0.1.113"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -76,6 +76,12 @@ class JoinType(Enum):
76
76
  class Ordering(Enum):
77
77
  ASCENDING = "asc"
78
78
  DESCENDING = "desc"
79
+ ASC_NULLS_AUTO = "asc nulls auto"
80
+ ASC_NULLS_FIRST = "asc nulls first"
81
+ ASC_NULLS_LAST = "asc nulls last"
82
+ DESC_NULLS_FIRST = "desc nulls first"
83
+ DESC_NULLS_LAST = "desc nulls last"
84
+ DESC_NULLS_AUTO = "desc nulls auto"
79
85
 
80
86
 
81
87
  class WindowType(Enum):
@@ -220,6 +226,7 @@ class ComparisonOperator(Enum):
220
226
  ILIKE = "ilike"
221
227
  CONTAINS = "contains"
222
228
  ELSE = "else"
229
+ BETWEEN = "between"
223
230
 
224
231
  @classmethod
225
232
  def _missing_(cls, value):
@@ -812,6 +812,7 @@ class Function(Namespaced, SelectGrain, BaseModel):
812
812
  "Parenthetical",
813
813
  CaseWhen,
814
814
  "CaseElse",
815
+ list,
815
816
  ListWrapper[int],
816
817
  ListWrapper[str],
817
818
  ListWrapper[float],
@@ -996,6 +997,7 @@ class WindowItem(Namespaced, SelectGrain, BaseModel):
996
997
  content: Concept
997
998
  order_by: List["OrderItem"]
998
999
  over: List["Concept"] = Field(default_factory=list)
1000
+ index: Optional[int] = None
999
1001
 
1000
1002
  def with_namespace(self, namespace: str) -> "WindowItem":
1001
1003
  return WindowItem(
@@ -2735,6 +2737,14 @@ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2735
2737
  raise SyntaxError(
2736
2738
  f"Cannot compare {self.left} and {self.right} of different types"
2737
2739
  )
2740
+ if self.operator == ComparisonOperator.BETWEEN:
2741
+ if (
2742
+ not isinstance(self.right, ComparisonOperator)
2743
+ and self.right.operator == BooleanOperator.AND
2744
+ ):
2745
+ raise SyntaxError(
2746
+ f"Between operator must have two operands with and, not {self.right}"
2747
+ )
2738
2748
 
2739
2749
  def __add__(self, other):
2740
2750
  if not isinstance(other, (Comparison, Conditional, Parenthetical)):
@@ -2810,6 +2820,29 @@ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2810
2820
  output += get_concept_arguments(self.right)
2811
2821
  return output
2812
2822
 
2823
+ @property
2824
+ def row_arguments(self) -> List[Concept]:
2825
+ output = []
2826
+ if isinstance(self.left, ConceptArgs):
2827
+ output += self.left.row_arguments
2828
+ else:
2829
+ output += get_concept_arguments(self.left)
2830
+ if isinstance(self.right, ConceptArgs):
2831
+ output += self.right.row_arguments
2832
+ else:
2833
+ output += get_concept_arguments(self.right)
2834
+ return output
2835
+
2836
+ @property
2837
+ def existence_arguments(self) -> List[Tuple[Concept, ...]]:
2838
+ """Return concepts directly referenced in where clause"""
2839
+ output: List[Tuple[Concept, ...]] = []
2840
+ if isinstance(self.left, ConceptArgs):
2841
+ output += self.left.existence_arguments
2842
+ if isinstance(self.right, ConceptArgs):
2843
+ output += self.right.existence_arguments
2844
+ return output
2845
+
2813
2846
 
2814
2847
  class SubselectComparison(Comparison):
2815
2848
 
@@ -2909,6 +2942,7 @@ class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2909
2942
  float,
2910
2943
  list,
2911
2944
  bool,
2945
+ MagicConstants,
2912
2946
  Concept,
2913
2947
  Comparison,
2914
2948
  "Conditional",
@@ -2922,11 +2956,13 @@ class Conditional(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2922
2956
  float,
2923
2957
  list,
2924
2958
  bool,
2959
+ MagicConstants,
2925
2960
  Concept,
2926
2961
  Comparison,
2927
2962
  "Conditional",
2928
2963
  "Parenthetical",
2929
2964
  Function,
2965
+ FilterItem,
2930
2966
  ]
2931
2967
  operator: BooleanOperator
2932
2968
 
@@ -10,6 +10,7 @@ from trilogy.core.enums import (
10
10
  WindowType,
11
11
  DatePart,
12
12
  PurposeLineage,
13
+ ComparisonOperator,
13
14
  )
14
15
  from trilogy.core.models import (
15
16
  ListType,
@@ -58,17 +59,23 @@ def INVALID_REFERENCE_STRING(x: Any, callsite: str = ""):
58
59
 
59
60
 
60
61
  def window_factory(string: str, include_concept: bool = False) -> Callable:
61
- def render_window(concept: str, window: str, sort: str) -> str:
62
+ def render_window(
63
+ concept: str, window: str, sort: str, offset: int | None = None
64
+ ) -> str:
62
65
  if not include_concept:
63
66
  concept = ""
67
+ if offset:
68
+ base = f"{string}({concept}, {offset})"
69
+ else:
70
+ base = f"{string}({concept})"
64
71
  if window and sort:
65
- return f"{string}({concept}) over (partition by {window} order by {sort} )"
72
+ return f"{base} over (partition by {window} order by {sort} )"
66
73
  elif window:
67
- return f"{string}({concept}) over (partition by {window})"
74
+ return f"{base} over (partition by {window})"
68
75
  elif sort:
69
- return f"{string}({concept}) over (order by {sort} )"
76
+ return f"{base} over (order by {sort} )"
70
77
  else:
71
- return f"{string}({concept}) over ()"
78
+ return f"{base} over ()"
72
79
 
73
80
  return render_window
74
81
 
@@ -109,10 +116,10 @@ FUNCTION_MAP = {
109
116
  FunctionType.INDEX_ACCESS: lambda x: f"{x[0]}[{x[1]}]",
110
117
  FunctionType.UNNEST: lambda x: f"unnest({x[0]})",
111
118
  # math
112
- FunctionType.ADD: lambda x: f"({x[0]} + {x[1]})",
113
- FunctionType.SUBTRACT: lambda x: f"({x[0]} - {x[1]})",
114
- FunctionType.DIVIDE: lambda x: f"({x[0]} / {x[1]})",
115
- FunctionType.MULTIPLY: lambda x: f"({x[0]} * {x[1]})",
119
+ FunctionType.ADD: lambda x: f"{x[0]} + {x[1]}",
120
+ FunctionType.SUBTRACT: lambda x: f"{x[0]} - {x[1]}",
121
+ FunctionType.DIVIDE: lambda x: f"{x[0]} / {x[1]}",
122
+ FunctionType.MULTIPLY: lambda x: f"{x[0]} * {x[1]}",
116
123
  FunctionType.ROUND: lambda x: f"round({x[0]},{x[1]})",
117
124
  FunctionType.MOD: lambda x: f"({x[0]} % {x[1]})",
118
125
  # aggregate types
@@ -355,17 +362,30 @@ class BaseDialect:
355
362
  lookup = lookup_cte.existence_source_map[e.right.address]
356
363
 
357
364
  return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} (select {lookup[0]}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} from {lookup[0]})"
358
- elif isinstance(e.right, (ListWrapper, Parenthetical)):
365
+ elif isinstance(e.right, (ListWrapper, Parenthetical, list)):
359
366
  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)}"
360
- elif isinstance(e.right, (str, int, bool, float, list)):
367
+
368
+ elif isinstance(
369
+ e.right,
370
+ (
371
+ str,
372
+ int,
373
+ bool,
374
+ float,
375
+ ),
376
+ ):
361
377
  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)})"
362
378
  else:
363
- 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)})"
379
+ 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)}"
364
380
  elif isinstance(e, Comparison):
381
+ if e.operator == ComparisonOperator.BETWEEN:
382
+ right_comp = e.right
383
+ assert isinstance(right_comp, Conditional)
384
+ 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)}"
365
385
  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)}"
366
386
  elif isinstance(e, Conditional):
367
387
  # conditions need to be nested in parentheses
368
- 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)} ) "
388
+ 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)}"
369
389
  elif isinstance(e, WindowItem):
370
390
  rendered_order_components = [
371
391
  f"{self.render_expr(x.expr, cte, cte_map=cte_map)} {x.order.value}"
@@ -375,11 +395,11 @@ class BaseDialect:
375
395
  self.render_expr(x, cte, cte_map=cte_map) for x in e.over
376
396
  ]
377
397
  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
378
- elif isinstance(e, FilterItem):
379
- return f"CASE WHEN {self.render_expr(e.where.conditional, cte=cte, cte_map=cte_map)} THEN {self.render_expr(e.content, cte=cte, cte_map=cte_map)} ELSE 0 END"
380
398
  elif isinstance(e, Parenthetical):
381
399
  # conditions need to be nested in parentheses
382
- return f"( {self.render_expr(e.content, cte=cte, cte_map=cte_map)} ) "
400
+ if isinstance(e.content, list):
401
+ return f"( {','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e.content])} )"
402
+ return f"( {self.render_expr(e.content, cte=cte, cte_map=cte_map)} )"
383
403
  elif isinstance(e, CaseWhen):
384
404
  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) }"
385
405
  elif isinstance(e, CaseElse):
@@ -412,7 +432,7 @@ class BaseDialect:
412
432
  elif isinstance(e, ListWrapper):
413
433
  return f"[{','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}]"
414
434
  elif isinstance(e, list):
415
- return f"{','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}"
435
+ return f"[{','.join([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}]"
416
436
  elif isinstance(e, DataType):
417
437
  return str(e.value)
418
438
  elif isinstance(e, DatePart):
@@ -41,7 +41,7 @@ def concept_list_to_keys(concepts: Tuple[Concept, ...]) -> Tuple[Concept, ...]:
41
41
 
42
42
 
43
43
  def constant_to_concept(
44
- parent: ListWrapper | int | float | str,
44
+ parent: ListWrapper | list | int | float | str,
45
45
  name: str,
46
46
  namespace: str,
47
47
  purpose: Purpose | None = None,
@@ -114,298 +114,17 @@ from trilogy.parsing.common import (
114
114
  arbitrary_to_concept,
115
115
  )
116
116
 
117
+ CONSTANT_TYPES = (int, float, str, bool, list, ListWrapper)
117
118
 
118
- CONSTANT_TYPES = (int, float, str, bool, ListWrapper)
119
-
120
- grammar = r"""
121
- !start: ( block | show_statement | comment )*
122
- block: statement _TERMINATOR comment?
123
- ?statement: concept
124
- | datasource
125
- | function
126
- | multi_select_statement
127
- | select_statement
128
- | persist_statement
129
- | rowset_derivation_statement
130
- | import_statement
131
- | merge_statement
132
-
133
- _TERMINATOR: ";"i /\s*/
134
-
135
- comment: /#.*(\n|$)/ | /\/\/.*\n/
136
-
137
- // property display_name string
138
- concept_declaration: PURPOSE IDENTIFIER data_type concept_nullable_modifier? metadata?
139
- //customer_id.property first_name STRING;
140
- //<customer_id,country>.property local_alias STRING
141
- concept_property_declaration: PROPERTY (prop_ident | IDENTIFIER) data_type concept_nullable_modifier? metadata?
142
- //metric post_length <- len(post_text);
143
- concept_derivation: (PURPOSE | AUTO | PROPERTY ) (prop_ident | IDENTIFIER) "<" "-" expr
144
-
145
- rowset_derivation_statement: ("rowset"i IDENTIFIER "<" "-" (multi_select_statement | select_statement)) | ("with"i IDENTIFIER "as"i (multi_select_statement | select_statement))
146
-
147
- constant_derivation: CONST IDENTIFIER "<" "-" literal
148
- concept_nullable_modifier: "?"
149
- concept: (concept_declaration | concept_derivation | concept_property_declaration | constant_derivation)
150
-
151
- //concept property
152
- prop_ident: "<" (IDENTIFIER ",")* IDENTIFIER ","? ">" "." IDENTIFIER
153
-
154
- // datasource concepts
155
- datasource: "datasource" IDENTIFIER "(" column_assignment_list ")" grain_clause? (address | query)
156
-
157
- grain_clause: "grain" "(" column_list ")"
158
-
159
- address: "address" ADDRESS
160
-
161
- query: "query" MULTILINE_STRING
162
-
163
- concept_assignment: IDENTIFIER | (MODIFIER "[" concept_assignment "]" ) | (SHORTHAND_MODIFIER concept_assignment )
164
-
165
- column_assignment: ((IDENTIFIER | raw_column_assignment | _static_functions ) ":" concept_assignment)
166
-
167
- raw_column_assignment: "raw" "(" MULTILINE_STRING ")"
168
-
169
- column_assignment_list : (column_assignment "," )* column_assignment ","?
170
-
171
- column_list : (IDENTIFIER "," )* IDENTIFIER ","?
172
-
173
- import_statement: "import" (IDENTIFIER ".") * IDENTIFIER "as" IDENTIFIER
174
-
175
- // persist_statement
176
- persist_statement: "persist"i IDENTIFIER "into"i IDENTIFIER "from"i select_statement grain_clause?
177
-
178
- // select statement
179
- select_statement: "select"i select_list where? comment* order_by? comment* limit? comment*
180
-
181
- // multiple_selects
182
- multi_select_statement: select_statement ("merge" select_statement)+ "align"i align_clause where? comment* order_by? comment* limit? comment*
183
-
184
- align_item: IDENTIFIER ":" IDENTIFIER ("," IDENTIFIER)* ","?
185
-
186
- align_clause: align_item ("," align_item)* ","?
187
-
188
- // merge statemment
189
- merge_statement: "merge" IDENTIFIER ("," IDENTIFIER)* ","? comment*
190
-
191
- // FUNCTION blocks
192
- function: raw_function
193
- function_binding_item: IDENTIFIER ":" data_type
194
- function_binding_list: (function_binding_item ",")* function_binding_item ","?
195
- raw_function: "bind" "sql" IDENTIFIER "(" function_binding_list ")" "-" ">" data_type "as"i MULTILINE_STRING
196
-
197
- // user_id where state = Mexico
198
- filter_item: "filter"i IDENTIFIER where
199
-
200
- // rank/lag/lead
201
- WINDOW_TYPE: ("row_number"i|"rank"i|"lag"i|"lead"i | "sum"i) /[\s]+/
202
-
203
- window_item: WINDOW_TYPE (IDENTIFIER | select_transform | comment+ ) window_item_over? window_item_order?
204
-
205
- window_item_over: ("OVER"i over_list)
206
-
207
- window_item_order: ("ORDER"i? "BY"i order_list)
208
-
209
- select_hide_modifier: "--"
210
- select_partial_modifier: "~"
211
- select_item: (select_hide_modifier | select_partial_modifier)? (IDENTIFIER | select_transform | comment+ )
212
-
213
- select_list: ( select_item "," )* select_item ","?
214
-
215
- // count(post_id) -> post_count
216
- _assignment: ("-" ">") | "as"
217
- select_transform : expr _assignment IDENTIFIER metadata?
218
-
219
- metadata: "metadata" "(" IDENTIFIER "=" _string_lit ")"
220
-
221
- limit: "LIMIT"i /[0-9]+/
222
-
223
- !window_order: ("TOP"i | "BOTTOM"i)
224
-
225
- window: window_order /[0-9]+/
226
-
227
- window_order_by: "BY"i column_list
228
-
229
- order_list: (expr ORDERING "," )* expr ORDERING ","?
230
-
231
- over_list: (IDENTIFIER "," )* IDENTIFIER ","?
232
-
233
- ORDERING: ("ASC"i | "DESC"i)
234
-
235
- order_by: "ORDER"i "BY"i order_list
236
-
237
- //WHERE STATEMENT
238
-
239
- LOGICAL_OPERATOR: "AND"i | "OR"i
240
-
241
- conditional: expr LOGICAL_OPERATOR (conditional | expr)
242
-
243
- where: "WHERE"i (expr | conditional)
244
-
245
- expr_reference: IDENTIFIER
246
-
247
- !array_comparison: ( ("NOT"i "IN"i) | "IN"i)
248
-
249
- COMPARISON_OPERATOR: (/is[\s]+not/ | "is" |"=" | ">" | "<" | ">=" | "<=" | "!=" )
250
-
251
- comparison: (expr COMPARISON_OPERATOR expr)
252
-
253
- between_comparison: expr "BETWEEN"i expr "AND"i expr
254
-
255
- subselect_comparison: expr array_comparison expr | (expr array_comparison expr_tuple)
256
-
257
- expr_tuple: "(" (expr ",")* expr ","? ")"
258
-
259
- //unnesting is a function
260
- unnest: "UNNEST"i "(" expr ")"
261
- //indexing into an expression is a function
262
- index_access: expr "[" int_lit "]"
263
- attr_access: expr "[" _string_lit "]"
264
-
265
- parenthetical: "(" (conditional | expr) ")"
266
-
267
- expr: window_item | filter_item | between_comparison | 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
268
-
269
- // functions
270
-
271
- fadd: ("add"i "(" expr "," expr ")" ) | ( expr "+" expr )
272
- fsub: ("subtract"i "(" expr "," expr ")" ) | ( expr "-" expr )
273
- fmul: ("multiply"i "(" expr "," expr ")" ) | ( expr "*" expr )
274
- fdiv: ( "divide"i "(" expr "," expr ")") | ( expr "/" expr )
275
- fmod: ( "mod"i "(" expr "," expr ")") | ( expr "%" expr )
276
- fround: "round"i "(" expr "," expr ")"
277
- fabs: "abs"i "(" expr ")"
278
-
279
- _math_functions: fadd | fsub | fmul | fdiv | fround | fmod | fabs
280
-
281
- //generic
282
- fcast: "cast"i "(" expr "AS"i data_type ")"
283
- concat: ("concat"i "(" (expr ",")* expr ")") | (expr "||" expr)
284
- fcoalesce: "coalesce"i "(" (expr ",")* expr ")"
285
- fcase_when: "WHEN"i (expr | conditional) "THEN"i expr
286
- fcase_else: "ELSE"i expr
287
- fcase: "CASE"i (fcase_when)* (fcase_else)? "END"i
288
- len: "len"i "(" expr ")"
289
- fnot: "NOT"i expr
290
-
291
- _generic_functions: fcast | concat | fcoalesce | fcase | len | fnot
292
-
293
- //constant
294
- fcurrent_date: "current_date"i "(" ")"
295
- fcurrent_datetime: "current_datetime"i "(" ")"
296
-
297
- _constant_functions: fcurrent_date | fcurrent_datetime
298
-
299
- //string
300
- like: "like"i "(" expr "," _string_lit ")"
301
- ilike: "ilike"i "(" expr "," _string_lit ")"
302
- alt_like: expr "like"i expr
303
- upper: "upper"i "(" expr ")"
304
- lower: "lower"i "(" expr ")"
305
- fsplit: "split"i "(" expr "," _string_lit ")"
306
- fstrpos: "strpos"i "(" expr "," expr ")"
307
- fsubstring: "substring"i "(" expr "," expr "," expr ")"
308
-
309
- _string_functions: like | ilike | upper | lower | fsplit | fstrpos | fsubstring | alt_like
310
-
311
- // special aggregate
312
- fgroup: "group"i "(" expr ")" aggregate_over?
313
- //aggregates
314
- count: "count"i "(" expr ")"
315
- count_distinct: "count_distinct"i "(" expr ")"
316
- sum: "sum"i "(" expr ")"
317
- avg: "avg"i "(" expr ")"
318
- max: "max"i "(" expr ")"
319
- min: "min"i "(" expr ")"
320
-
321
- //aggregates can force a grain
322
- aggregate_all: "*"
323
- aggregate_over: ("BY"i (aggregate_all | over_list))
324
- aggregate_functions: (count | count_distinct | sum | avg | max | min) aggregate_over?
325
-
326
- // date functions
327
- fdate: "date"i "(" expr ")"
328
- fdatetime: "datetime"i "(" expr ")"
329
- ftimestamp: "timestamp"i "(" expr ")"
330
-
331
- fsecond: "second"i "(" expr ")"
332
- fminute: "minute"i "(" expr ")"
333
- fhour: "hour"i "(" expr ")"
334
- fday: "day"i "(" expr ")"
335
- fday_of_week: "day_of_week"i "(" expr ")"
336
- fweek: "week"i "(" expr ")"
337
- fmonth: "month"i "(" expr ")"
338
- fquarter: "quarter"i "(" expr ")"
339
- fyear: "year"i "(" expr ")"
340
-
341
- DATE_PART: "DAY"i | "WEEK"i | "MONTH"i | "QUARTER"i | "YEAR"i | "MINUTE"i | "HOUR"i | "SECOND"i
342
- fdate_trunc: "date_trunc"i "(" expr "," DATE_PART ")"
343
- fdate_part: "date_part"i "(" expr "," DATE_PART ")"
344
- fdate_add: "date_add"i "(" expr "," DATE_PART "," int_lit ")"
345
- fdate_diff: "date_diff"i "(" expr "," expr "," DATE_PART ")"
346
-
347
- _date_functions: fdate | fdate_add | fdate_diff | fdatetime | ftimestamp | fsecond | fminute | fhour | fday | fday_of_week | fweek | fmonth | fquarter | fyear | fdate_part | fdate_trunc
348
-
349
- _static_functions: _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions
350
-
351
- // base language constructs
352
- IDENTIFIER: /[a-zA-Z_][a-zA-Z0-9_\\-\\.\-]*/
353
- ADDRESS: /[a-zA-Z_][a-zA-Z0-9_\\-\\.\-\*]*/ | /`[a-zA-Z_][a-zA-Z0-9_\\-\\.\-\*]*`/
354
-
355
- MULTILINE_STRING: /\'{3}(.*?)\'{3}/s
356
-
357
- DOUBLE_STRING_CHARS: /(?:(?!\${)([^"\\]|\\.))+/+ // any character except "
358
- SINGLE_STRING_CHARS: /(?:(?!\${)([^'\\]|\\.))+/+ // any character except '
359
- _single_quote: "'" ( SINGLE_STRING_CHARS )* "'"
360
- _double_quote: "\"" ( DOUBLE_STRING_CHARS )* "\""
361
- _string_lit: _single_quote | _double_quote
362
-
363
- MINUS: "-"
364
-
365
- int_lit: MINUS? /[0-9]+/
366
-
367
- float_lit: /[0-9]*\.[0-9]+/
368
-
369
- array_lit: "[" (literal ",")* literal ","? "]"()
370
-
371
- !bool_lit: "True"i | "False"i
372
-
373
- !null_lit: "null"i
374
-
375
- literal: _string_lit | int_lit | float_lit | bool_lit | null_lit | array_lit
376
-
377
- MODIFIER: "Optional"i | "Partial"i | "Nullable"i
378
-
379
- SHORTHAND_MODIFIER: "~"
380
-
381
- struct_type: "struct" "<" ((data_type | IDENTIFIER) ",")* (data_type | IDENTIFIER) ","? ">"
382
-
383
- list_type: "list" "<" data_type ">"
384
-
385
-
386
- !data_type: "string"i | "number"i | "numeric"i | "map"i | "list"i | "array"i | "any"i | "int"i | "bigint" | "date"i | "datetime"i | "timestamp"i | "float"i | "bool"i | struct_type | list_type
387
-
388
- PURPOSE: "key"i | "metric"i | "const"i | "constant"i
389
- PROPERTY: "property"i
390
- CONST: "const"i | "constant"i
391
- AUTO: "AUTO"i
392
-
393
- // meta functions
394
- CONCEPTS: "CONCEPTS"i
395
- DATASOURCES: "DATASOURCES"i
396
-
397
- show_category: CONCEPTS | DATASOURCES
398
-
399
- show_statement: "show"i ( show_category | select_statement | persist_statement) _TERMINATOR
400
-
401
- %import common.WS_INLINE -> _WHITESPACE
402
- %import common.WS
403
- %ignore WS
404
- """ # noqa: E501
405
-
406
- PARSER = Lark(
407
- grammar, start="start", propagate_positions=True, g_regex_flags=IGNORECASE
408
- )
119
+ with open(join(dirname(__file__), "trilogy.lark"), "r") as f:
120
+ PARSER = Lark(
121
+ f.read(),
122
+ start="start",
123
+ propagate_positions=True,
124
+ g_regex_flags=IGNORECASE,
125
+ parser="lalr",
126
+ cache=True,
127
+ )
409
128
 
410
129
 
411
130
  def parse_concept_reference(
@@ -569,6 +288,9 @@ class ParseToObjects(Transformer):
569
288
  def IDENTIFIER(self, args) -> str:
570
289
  return args.value
571
290
 
291
+ def concept_lit(self, args) -> Concept:
292
+ return self.environment.concepts.__getitem__(args[0])
293
+
572
294
  def ADDRESS(self, args) -> str:
573
295
  return args.value
574
296
 
@@ -624,19 +346,23 @@ class ParseToObjects(Transformer):
624
346
  def column_assignment(self, meta: Meta, args):
625
347
  # TODO -> deal with conceptual modifiers
626
348
  modifiers = []
627
- concept = args[1]
349
+ alias = args[0]
350
+ concept_list = args[1]
628
351
  # recursively collect modifiers
629
- while len(concept) > 1:
630
- modifiers.append(concept[0])
631
- concept = concept[1]
352
+ if len(concept_list) > 1:
353
+ modifiers += concept_list[:-1]
354
+ concept = concept_list[-1]
632
355
  resolved = self.environment.concepts.__getitem__( # type: ignore
633
- key=concept[0], line_no=meta.line
356
+ key=concept, line_no=meta.line
634
357
  )
635
- return ColumnAssignment(alias=args[0], modifiers=modifiers, concept=resolved)
358
+ return ColumnAssignment(alias=alias, modifiers=modifiers, concept=resolved)
636
359
 
637
360
  def _TERMINATOR(self, args):
638
361
  return None
639
362
 
363
+ def _static_functions(self, args):
364
+ return args[0]
365
+
640
366
  def MODIFIER(self, args) -> Modifier:
641
367
  return Modifier(args.value)
642
368
 
@@ -895,6 +621,9 @@ class ParseToObjects(Transformer):
895
621
  assert len(args) == 1
896
622
  return Comment(text=args[0].value)
897
623
 
624
+ def PARSE_COMMENT(self, args):
625
+ return Comment(text=args.value)
626
+
898
627
  @v_args(meta=True)
899
628
  def select_transform(self, meta, args) -> ConceptTransform:
900
629
 
@@ -964,7 +693,7 @@ class ParseToObjects(Transformer):
964
693
  if isinstance(content, ConceptTransform):
965
694
  return SelectItem(content=content, modifiers=modifiers)
966
695
  return SelectItem(
967
- content=self.environment.concepts.__getitem__(content, meta.line),
696
+ content=content,
968
697
  modifiers=modifiers,
969
698
  )
970
699
 
@@ -974,8 +703,12 @@ class ParseToObjects(Transformer):
974
703
  def limit(self, args):
975
704
  return Limit(count=int(args[0].value))
976
705
 
977
- def ORDERING(self, args):
978
- return Ordering(args.lower())
706
+ def ordering(self, args: list[str]):
707
+ base = args[0].lower()
708
+ if len(args) > 1:
709
+ null_sort = args[-1]
710
+ return Ordering(" ".join([base, "nulls", null_sort.lower()]))
711
+ return Ordering(base)
979
712
 
980
713
  def order_list(self, args):
981
714
 
@@ -1003,7 +736,7 @@ class ParseToObjects(Transformer):
1003
736
  return OrderBy(items=args[0])
1004
737
 
1005
738
  def over_list(self, args):
1006
- return [self.environment.concepts[x] for x in args]
739
+ return [x for x in args]
1007
740
 
1008
741
  @v_args(meta=True)
1009
742
  def merge_statement(self, meta: Meta, args) -> MergeStatement:
@@ -1046,9 +779,7 @@ class ParseToObjects(Transformer):
1046
779
  # add the parsed objects of the import in
1047
780
  self.parsed = {**self.parsed, **nparser.parsed}
1048
781
  except Exception as e:
1049
- raise ImportError(
1050
- f"Unable to import file {dirname(target)}, parsing error: {e}"
1051
- )
782
+ raise ImportError(f"Unable to import file {target}, parsing error: {e}")
1052
783
 
1053
784
  for _, concept in nparser.environment.concepts.items():
1054
785
  self.environment.add_concept(concept.with_namespace(alias))
@@ -1312,13 +1043,23 @@ class ParseToObjects(Transformer):
1312
1043
  type = args[0]
1313
1044
  order_by = []
1314
1045
  over = []
1315
- for item in args[2:]:
1316
- if isinstance(item, WindowItemOrder):
1046
+ index = None
1047
+ concept: Concept | None = None
1048
+ for item in args:
1049
+ if isinstance(item, int):
1050
+ index = item
1051
+ elif isinstance(item, WindowItemOrder):
1317
1052
  order_by = item.contents
1318
1053
  elif isinstance(item, WindowItemOver):
1319
1054
  over = item.contents
1320
- concept = self.environment.concepts[args[1]]
1321
- return WindowItem(type=type, content=concept, over=over, order_by=order_by)
1055
+ elif isinstance(item, str):
1056
+ concept = self.environment.concepts[item]
1057
+ elif isinstance(item, Concept):
1058
+ concept = item
1059
+ assert concept
1060
+ return WindowItem(
1061
+ type=type, content=concept, over=over, order_by=order_by, index=index
1062
+ )
1322
1063
 
1323
1064
  def filter_item(self, args) -> FilterItem:
1324
1065
  where: WhereClause
@@ -1516,6 +1257,9 @@ class ParseToObjects(Transformer):
1516
1257
  )
1517
1258
  return SubString(args)
1518
1259
 
1260
+ def logical_operator(self, args):
1261
+ return BooleanOperator(args[0].value.lower())
1262
+
1519
1263
  @v_args(meta=True)
1520
1264
  def lower(self, meta, args):
1521
1265
  args = self.process_function_args(args, meta=meta)
@@ -1908,12 +1652,12 @@ class ParseToObjects(Transformer):
1908
1652
  @v_args(meta=True)
1909
1653
  def fcurrent_date(self, meta, args):
1910
1654
  args = self.process_function_args(args, meta=meta)
1911
- return CurrentDate(args)
1655
+ return CurrentDate([])
1912
1656
 
1913
1657
  @v_args(meta=True)
1914
1658
  def fcurrent_datetime(self, meta, args):
1915
1659
  args = self.process_function_args(args, meta=meta)
1916
- return CurrentDatetime(args)
1660
+ return CurrentDatetime([])
1917
1661
 
1918
1662
  @v_args(meta=True)
1919
1663
  def fnot(self, meta, args):
@@ -1929,10 +1673,14 @@ def unpack_visit_error(e: VisitError):
1929
1673
  elif isinstance(e.orig_exc, (UndefinedConceptException, ImportError)):
1930
1674
  raise e.orig_exc
1931
1675
  elif isinstance(e.orig_exc, (ValidationError, TypeError)):
1932
- raise InvalidSyntaxException(str(e.orig_exc))
1676
+ raise InvalidSyntaxException(str(e.orig_exc) + str(e.rule) + str(e.obj))
1933
1677
  raise e
1934
1678
 
1935
1679
 
1680
+ def parse_text_raw(text: str, environment: Optional[Environment] = None):
1681
+ PARSER.parse(text)
1682
+
1683
+
1936
1684
  def parse_text(text: str, environment: Optional[Environment] = None) -> Tuple[
1937
1685
  Environment,
1938
1686
  List[
File without changes
File without changes
File without changes
File without changes