pytrilogy 0.0.3.75__tar.gz → 0.0.3.77__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 (150) hide show
  1. {pytrilogy-0.0.3.75/pytrilogy.egg-info → pytrilogy-0.0.3.77}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_parse_engine.py +34 -6
  4. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_parsing.py +36 -1
  5. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/__init__.py +1 -1
  6. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/enums.py +1 -1
  7. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/internal.py +20 -2
  8. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/models/author.py +8 -6
  9. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/models/build.py +6 -4
  10. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/statements/execute.py +7 -1
  11. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/base.py +48 -13
  12. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/executor.py +13 -10
  13. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parsing/parse_engine.py +54 -6
  14. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parsing/trilogy.lark +3 -1
  15. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/LICENSE.md +0 -0
  16. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/README.md +0 -0
  17. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/pyproject.toml +0 -0
  18. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/pytrilogy.egg-info/SOURCES.txt +0 -0
  19. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/pytrilogy.egg-info/dependency_links.txt +0 -0
  20. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/pytrilogy.egg-info/entry_points.txt +0 -0
  21. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/pytrilogy.egg-info/requires.txt +0 -0
  22. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/pytrilogy.egg-info/top_level.txt +0 -0
  23. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/setup.cfg +0 -0
  24. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/setup.py +0 -0
  25. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_datatypes.py +0 -0
  26. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_declarations.py +0 -0
  27. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_derived_concepts.py +0 -0
  28. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_discovery_nodes.py +0 -0
  29. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_enums.py +0 -0
  30. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_environment.py +0 -0
  31. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_execute_models.py +0 -0
  32. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_executor.py +0 -0
  33. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_failure.py +0 -0
  34. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_functions.py +0 -0
  35. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_imports.py +0 -0
  36. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_metadata.py +0 -0
  37. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_models.py +0 -0
  38. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_multi_join_assignments.py +0 -0
  39. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_parsing_failures.py +0 -0
  40. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_partial_handling.py +0 -0
  41. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_query_processing.py +0 -0
  42. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_query_render.py +0 -0
  43. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_select.py +0 -0
  44. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_show.py +0 -0
  45. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_statements.py +0 -0
  46. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_typing.py +0 -0
  47. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_undefined_concept.py +0 -0
  48. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_user_functions.py +0 -0
  49. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/tests/test_where_clause.py +0 -0
  50. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/authoring/__init__.py +0 -0
  51. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/compiler.py +0 -0
  52. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/constants.py +0 -0
  53. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/__init__.py +0 -0
  54. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/constants.py +0 -0
  55. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/env_processor.py +0 -0
  56. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/environment_helpers.py +0 -0
  57. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/ergonomics.py +0 -0
  58. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/exceptions.py +0 -0
  59. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/functions.py +0 -0
  60. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/graph_models.py +0 -0
  61. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/models/__init__.py +0 -0
  62. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/models/build_environment.py +0 -0
  63. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/models/core.py +0 -0
  64. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/models/datasource.py +0 -0
  65. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/models/environment.py +0 -0
  66. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/models/execute.py +0 -0
  67. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/optimization.py +0 -0
  68. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/optimizations/__init__.py +0 -0
  69. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/optimizations/base_optimization.py +0 -0
  70. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/optimizations/inline_datasource.py +0 -0
  71. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  72. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/__init__.py +0 -0
  73. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  74. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/discovery_loop.py +0 -0
  75. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/discovery_node_factory.py +0 -0
  76. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/discovery_utility.py +0 -0
  77. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/discovery_validation.py +0 -0
  78. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/graph_utils.py +0 -0
  79. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/__init__.py +0 -0
  80. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  81. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/common.py +0 -0
  82. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  83. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/group_node.py +0 -0
  84. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  85. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  86. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  87. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  88. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  89. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  90. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  91. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  92. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/select_node.py +0 -0
  93. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  94. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/union_node.py +0 -0
  95. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  96. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/node_generators/window_node.py +0 -0
  97. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/__init__.py +0 -0
  98. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/base_node.py +0 -0
  99. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/filter_node.py +0 -0
  100. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/group_node.py +0 -0
  101. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/merge_node.py +0 -0
  102. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  103. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  104. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/union_node.py +0 -0
  105. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  106. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/nodes/window_node.py +0 -0
  107. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/processing/utility.py +0 -0
  108. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/query_processor.py +0 -0
  109. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/statements/__init__.py +0 -0
  110. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/statements/author.py +0 -0
  111. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/statements/build.py +0 -0
  112. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/statements/common.py +0 -0
  113. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/core/utility.py +0 -0
  114. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/__init__.py +0 -0
  115. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/bigquery.py +0 -0
  116. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/common.py +0 -0
  117. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/config.py +0 -0
  118. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/dataframe.py +0 -0
  119. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/duckdb.py +0 -0
  120. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/enums.py +0 -0
  121. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/postgres.py +0 -0
  122. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/presto.py +0 -0
  123. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/snowflake.py +0 -0
  124. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/dialect/sql_server.py +0 -0
  125. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/engine.py +0 -0
  126. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/hooks/__init__.py +0 -0
  127. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/hooks/base_hook.py +0 -0
  128. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/hooks/graph_hook.py +0 -0
  129. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/hooks/query_debugger.py +0 -0
  130. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/metadata/__init__.py +0 -0
  131. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parser.py +0 -0
  132. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parsing/__init__.py +0 -0
  133. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parsing/common.py +0 -0
  134. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parsing/config.py +0 -0
  135. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parsing/exceptions.py +0 -0
  136. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parsing/helpers.py +0 -0
  137. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/parsing/render.py +0 -0
  138. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/py.typed +0 -0
  139. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/render.py +0 -0
  140. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/scripts/__init__.py +0 -0
  141. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/scripts/trilogy.py +0 -0
  142. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/std/__init__.py +0 -0
  143. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/std/date.preql +0 -0
  144. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/std/display.preql +0 -0
  145. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/std/geography.preql +0 -0
  146. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/std/money.preql +0 -0
  147. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/std/net.preql +0 -0
  148. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/std/ranking.preql +0 -0
  149. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/std/report.preql +0 -0
  150. {pytrilogy-0.0.3.75 → pytrilogy-0.0.3.77}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.75
3
+ Version: 0.0.3.77
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.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.75
3
+ Version: 0.0.3.77
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -3,6 +3,7 @@ from pytest import raises
3
3
  from trilogy.core.exceptions import UndefinedConceptException
4
4
  from trilogy.core.models.environment import Environment
5
5
  from trilogy.parsing.parse_engine import (
6
+ ERROR_CODES,
6
7
  PARSER,
7
8
  InvalidSyntaxException,
8
9
  ParseToObjects,
@@ -66,18 +67,45 @@ address fun;
66
67
  x.run_second_parse_pass()
67
68
 
68
69
 
69
- TEXT2 = """
70
+ def test_from_error():
71
+ env = Environment()
72
+ TEXT2 = """
73
+ const a <- 1;
74
+
75
+ select
76
+ a,
77
+ FROM a
78
+ ;
79
+ """
80
+ with raises(InvalidSyntaxException) as e:
81
+ env.parse(TEXT2)
82
+ assert ERROR_CODES[101] in str(e.value), e.value
83
+
84
+
85
+ def test_error_order_by_missing():
86
+ env = Environment()
87
+ TEXT2 = """
70
88
  const a <- 1;
71
89
 
72
90
  select
73
91
  a,
74
- FROM a
92
+ order by a
75
93
  ;
76
- """
94
+ """
95
+ with raises(InvalidSyntaxException) as e:
96
+ env.parse(TEXT2)
97
+ assert ERROR_CODES[210] in str(e.value)
77
98
 
78
99
 
79
- def test_from_error():
100
+ def test_alias_error():
80
101
  env = Environment()
81
-
82
- with raises(InvalidSyntaxException):
102
+ TEXT2 = """
103
+ const a <- 1;
104
+
105
+ select
106
+ a+2 fun,
107
+ ;
108
+ """
109
+ with raises(InvalidSyntaxException) as e:
83
110
  env.parse(TEXT2)
111
+ assert ERROR_CODES[201] in str(e.value), e.value
@@ -2,7 +2,13 @@ from trilogy import Dialects
2
2
  from trilogy.constants import MagicConstants
3
3
  from trilogy.core.enums import BooleanOperator, ComparisonOperator, Purpose
4
4
  from trilogy.core.functions import argument_to_purpose, function_args_to_output_purpose
5
- from trilogy.core.models.author import Comparison, Conditional, SubselectComparison
5
+ from trilogy.core.models.author import (
6
+ Comparison,
7
+ Conditional,
8
+ ListWrapper,
9
+ SubselectComparison,
10
+ )
11
+ from trilogy.core.models.build import BuildComparison
6
12
  from trilogy.core.models.core import (
7
13
  DataType,
8
14
  TupleWrapper,
@@ -63,6 +69,35 @@ def test_not_in():
63
69
  assert rendered.strip() == "(1,2,3)".strip()
64
70
 
65
71
 
72
+ def test_datetime_lit_rendering():
73
+ env, parsed = parse_text("const order_id <- 4;")
74
+
75
+ from datetime import datetime
76
+
77
+ now = datetime.now()
78
+ wrapper = ListWrapper(
79
+ (now,),
80
+ type=DataType.DATETIME,
81
+ )
82
+ assert wrapper[0] == now, wrapper
83
+ rendered = BaseDialect().render_expr(
84
+ BuildComparison(
85
+ left=env.materialize_for_select().concepts["order_id"],
86
+ operator=ComparisonOperator.NOT_IN,
87
+ right=wrapper,
88
+ )
89
+ )
90
+ assert "not in (date '" in rendered, rendered
91
+ rendered = BaseDialect().render_expr(
92
+ BuildComparison(
93
+ left=env.materialize_for_select().concepts["order_id"],
94
+ operator=ComparisonOperator.IN,
95
+ right=wrapper,
96
+ )
97
+ )
98
+ assert "in (date '" in rendered, rendered
99
+
100
+
66
101
  def test_is_not_null():
67
102
  _, parsed = parse_text(
68
103
  "const order_id <- 4; SELECT order_id WHERE order_id is not null;"
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.75"
7
+ __version__ = "0.0.3.77"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -353,7 +353,7 @@ class SourceType(Enum):
353
353
 
354
354
 
355
355
  class ShowCategory(Enum):
356
- MODELS = "models"
356
+ DATASOURCES = "datasources"
357
357
  CONCEPTS = "concepts"
358
358
 
359
359
 
@@ -19,8 +19,26 @@ DEFAULT_CONCEPTS = {
19
19
  granularity=Granularity.SINGLE_ROW,
20
20
  derivation=Derivation.CONSTANT,
21
21
  ),
22
- "concept_name": Concept(
23
- name="concept_name",
22
+ "concept_address": Concept(
23
+ name="concept_address",
24
+ namespace=INTERNAL_NAMESPACE,
25
+ datatype=DataType.STRING,
26
+ purpose=Purpose.KEY,
27
+ grain=Grain(),
28
+ granularity=Granularity.SINGLE_ROW,
29
+ derivation=Derivation.CONSTANT,
30
+ ),
31
+ "concept_datatype": Concept(
32
+ name="concept_datatype",
33
+ namespace=INTERNAL_NAMESPACE,
34
+ datatype=DataType.STRING,
35
+ purpose=Purpose.KEY,
36
+ grain=Grain(),
37
+ granularity=Granularity.SINGLE_ROW,
38
+ derivation=Derivation.CONSTANT,
39
+ ),
40
+ "concept_description": Concept(
41
+ name="concept_description",
24
42
  namespace=INTERNAL_NAMESPACE,
25
43
  datatype=DataType.STRING,
26
44
  purpose=Purpose.KEY,
@@ -77,7 +77,7 @@ class Mergeable(ABC):
77
77
  def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
78
78
  raise NotImplementedError
79
79
 
80
- def with_reference_replacement(self, source: str, target: Expr):
80
+ def with_reference_replacement(self, source: str, target: Expr | ArgBinding):
81
81
  raise NotImplementedError(type(self))
82
82
 
83
83
 
@@ -161,7 +161,7 @@ class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
161
161
  metadata=self.metadata,
162
162
  )
163
163
 
164
- def with_reference_replacement(self, source: str, target: Expr):
164
+ def with_reference_replacement(self, source: str, target: Expr | ArgBinding):
165
165
  if self.address == source:
166
166
  return target
167
167
  return self
@@ -1708,7 +1708,7 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1708
1708
  )
1709
1709
  return v
1710
1710
 
1711
- def with_reference_replacement(self, source: str, target: Expr):
1711
+ def with_reference_replacement(self, source: str, target: Expr | ArgBinding):
1712
1712
  from trilogy.core.functions import arg_to_datatype, merge_datatypes
1713
1713
 
1714
1714
  nargs = [
@@ -2303,13 +2303,13 @@ class CustomFunctionFactory:
2303
2303
  ]
2304
2304
  return self
2305
2305
 
2306
- def __call__(self, *creation_args: Expr):
2306
+ def __call__(self, *creation_args: ArgBinding | Expr):
2307
2307
  nout = (
2308
2308
  self.function.model_copy(deep=True)
2309
2309
  if isinstance(self.function, BaseModel)
2310
2310
  else self.function
2311
2311
  )
2312
- creation_arg_list: list[Expr] = list(creation_args)
2312
+ creation_arg_list: list[ArgBinding | Expr] = list(creation_args)
2313
2313
  if len(creation_args) < len(self.function_arguments):
2314
2314
  for binding in self.function_arguments[len(creation_arg_list) :]:
2315
2315
  if binding.default is None:
@@ -2361,7 +2361,9 @@ class Comment(BaseModel):
2361
2361
  class ArgBinding(Namespaced, DataTyped, BaseModel):
2362
2362
  name: str
2363
2363
  default: Expr | None = None
2364
- datatype: DataType = DataType.UNKNOWN
2364
+ datatype: (
2365
+ DataType | MapType | ArrayType | NumericType | StructType | TraitDataType
2366
+ ) = DataType.UNKNOWN
2365
2367
 
2366
2368
  def with_namespace(self, namespace):
2367
2369
  return ArgBinding(
@@ -606,7 +606,6 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
606
606
  int,
607
607
  str,
608
608
  float,
609
- list,
610
609
  bool,
611
610
  datetime,
612
611
  date,
@@ -619,12 +618,13 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
619
618
  MagicConstants,
620
619
  BuildWindowItem,
621
620
  BuildAggregateWrapper,
621
+ ListWrapper,
622
+ TupleWrapper,
622
623
  ]
623
624
  right: Union[
624
625
  int,
625
626
  str,
626
627
  float,
627
- list,
628
628
  bool,
629
629
  date,
630
630
  datetime,
@@ -638,6 +638,7 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
638
638
  BuildWindowItem,
639
639
  BuildAggregateWrapper,
640
640
  TupleWrapper,
641
+ ListWrapper,
641
642
  ]
642
643
  operator: ComparisonOperator
643
644
 
@@ -734,7 +735,6 @@ class BuildSubselectComparison(BuildComparison):
734
735
  int,
735
736
  str,
736
737
  float,
737
- list,
738
738
  bool,
739
739
  datetime,
740
740
  date,
@@ -747,12 +747,13 @@ class BuildSubselectComparison(BuildComparison):
747
747
  MagicConstants,
748
748
  BuildWindowItem,
749
749
  BuildAggregateWrapper,
750
+ ListWrapper,
751
+ TupleWrapper,
750
752
  ]
751
753
  right: Union[
752
754
  int,
753
755
  str,
754
756
  float,
755
- list,
756
757
  bool,
757
758
  date,
758
759
  datetime,
@@ -766,6 +767,7 @@ class BuildSubselectComparison(BuildComparison):
766
767
  BuildWindowItem,
767
768
  BuildAggregateWrapper,
768
769
  TupleWrapper,
770
+ ListWrapper,
769
771
  ]
770
772
  operator: ComparisonOperator
771
773
 
@@ -38,6 +38,12 @@ class ProcessedRawSQLStatement(BaseModel):
38
38
  text: str
39
39
 
40
40
 
41
+ class ProcessedStaticValueOutput(BaseModel):
42
+ values: List[dict]
43
+
44
+
41
45
  class ProcessedShowStatement(BaseModel):
42
46
  output_columns: List[ConceptRef]
43
- output_values: List[Union[BuildConcept, BuildDatasource, ProcessedQuery]]
47
+ output_values: List[
48
+ Union[BuildConcept, BuildDatasource, ProcessedQuery, ProcessedStaticValueOutput]
49
+ ]
@@ -16,6 +16,7 @@ from trilogy.core.enums import (
16
16
  DatePart,
17
17
  FunctionType,
18
18
  Ordering,
19
+ ShowCategory,
19
20
  UnnestMode,
20
21
  WindowType,
21
22
  )
@@ -77,6 +78,7 @@ from trilogy.core.statements.execute import (
77
78
  ProcessedQueryPersist,
78
79
  ProcessedRawSQLStatement,
79
80
  ProcessedShowStatement,
81
+ ProcessedStaticValueOutput,
80
82
  )
81
83
  from trilogy.core.utility import safe_quote
82
84
  from trilogy.dialect.common import render_join, render_unnest
@@ -638,18 +640,7 @@ class BaseDialect:
638
640
  ):
639
641
  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)}"
640
642
 
641
- elif isinstance(
642
- e.right,
643
- (
644
- str,
645
- int,
646
- bool,
647
- float,
648
- ),
649
- ):
650
- 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)})"
651
- else:
652
- 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)}"
643
+ 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)})"
653
644
  elif isinstance(e, COMPARISON_ITEMS):
654
645
  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)}"
655
646
  elif isinstance(e, CONDITIONAL_ITEMS):
@@ -953,6 +944,46 @@ class BaseDialect:
953
944
  self.render_cte(sort_select_output(query.ctes[-1], query), auto_sort=False)
954
945
  ]
955
946
 
947
+ def create_show_output(
948
+ self,
949
+ environment: Environment,
950
+ content: ShowCategory,
951
+ ):
952
+ if content == ShowCategory.CONCEPTS:
953
+ output_columns = [
954
+ environment.concepts[
955
+ DEFAULT_CONCEPTS["concept_address"].address
956
+ ].reference,
957
+ environment.concepts[
958
+ DEFAULT_CONCEPTS["concept_datatype"].address
959
+ ].reference,
960
+ environment.concepts[
961
+ DEFAULT_CONCEPTS["concept_description"].address
962
+ ].reference,
963
+ ]
964
+ output_values = [
965
+ {
966
+ DEFAULT_CONCEPTS["concept_address"].address: (
967
+ concept.name
968
+ if concept.namespace == DEFAULT_NAMESPACE
969
+ else concept.address
970
+ ),
971
+ DEFAULT_CONCEPTS["concept_datatype"].address: str(concept.datatype),
972
+ DEFAULT_CONCEPTS[
973
+ "concept_description"
974
+ ].address: concept.metadata.description
975
+ or "",
976
+ }
977
+ for _, concept in environment.concepts.items()
978
+ if not concept.is_internal
979
+ ]
980
+ else:
981
+ raise NotImplementedError(f"Show category {content} not implemented")
982
+ return ProcessedShowStatement(
983
+ output_columns=output_columns,
984
+ output_values=[ProcessedStaticValueOutput(values=output_values)],
985
+ )
986
+
956
987
  def generate_queries(
957
988
  self,
958
989
  environment: Environment,
@@ -1027,8 +1058,12 @@ class BaseDialect:
1027
1058
  ],
1028
1059
  )
1029
1060
  )
1061
+ elif isinstance(statement.content, ShowCategory):
1062
+ output.append(
1063
+ self.create_show_output(environment, statement.content)
1064
+ )
1030
1065
  else:
1031
- raise NotImplementedError(type(statement))
1066
+ raise NotImplementedError(type(statement.content))
1032
1067
  elif isinstance(statement, RawSQLStatement):
1033
1068
  output.append(ProcessedRawSQLStatement(text=statement.text))
1034
1069
  elif isinstance(
@@ -30,6 +30,7 @@ from trilogy.core.statements.execute import (
30
30
  ProcessedQueryPersist,
31
31
  ProcessedRawSQLStatement,
32
32
  ProcessedShowStatement,
33
+ ProcessedStaticValueOutput,
33
34
  )
34
35
  from trilogy.dialect.base import BaseDialect
35
36
  from trilogy.dialect.enums import Dialects
@@ -470,16 +471,18 @@ class Executor(object):
470
471
  # connection = self.engine.connect()
471
472
  for statement in self.parse_text_generator(command):
472
473
  if isinstance(statement, ProcessedShowStatement):
473
- output.append(
474
- generate_result_set(
475
- statement.output_columns,
476
- [
477
- self.generator.compile_statement(x)
478
- for x in statement.output_values
479
- if isinstance(x, ProcessedQuery)
480
- ],
481
- )
482
- )
474
+ for x in statement.output_values:
475
+ if isinstance(x, ProcessedStaticValueOutput):
476
+ output.append(
477
+ generate_result_set(statement.output_columns, x.values)
478
+ )
479
+ elif isinstance(x, ProcessedQuery):
480
+ output.append(
481
+ generate_result_set(
482
+ statement.output_columns,
483
+ [self.generator.compile_statement(x)],
484
+ )
485
+ )
483
486
  continue
484
487
  if non_interactive:
485
488
  if not isinstance(
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  from re import IGNORECASE
8
8
  from typing import Any, List, Optional, Tuple, Union
9
9
 
10
- from lark import Lark, ParseTree, Transformer, Tree, v_args
10
+ from lark import Lark, ParseTree, Token, Transformer, Tree, v_args
11
11
  from lark.exceptions import (
12
12
  UnexpectedCharacters,
13
13
  UnexpectedEOF,
@@ -2034,12 +2034,17 @@ class ParseToObjects(Transformer):
2034
2034
  return self.environment.functions[args[0]]
2035
2035
 
2036
2036
  @v_args(meta=True)
2037
- def farray_transform(self, meta, args):
2037
+ def farray_transform(self, meta, args) -> Function:
2038
2038
  factory: CustomFunctionFactory = args[1]
2039
2039
  if not len(factory.function_arguments) == 1:
2040
2040
  raise InvalidSyntaxException(
2041
2041
  "Array transform function must have exactly one argument;"
2042
2042
  )
2043
+ array_type = arg_to_datatype(args[0])
2044
+ if not isinstance(array_type, ArrayType):
2045
+ raise InvalidSyntaxException(
2046
+ f"Array transform function must be applied to an array, not {array_type}"
2047
+ )
2043
2048
  return self.function_factory.create_function(
2044
2049
  [
2045
2050
  args[0],
@@ -2047,7 +2052,7 @@ class ParseToObjects(Transformer):
2047
2052
  factory(
2048
2053
  ArgBinding(
2049
2054
  name=factory.function_arguments[0].name,
2050
- datatype=arg_to_datatype(args[0]).value_data_type,
2055
+ datatype=array_type.value_data_type,
2051
2056
  )
2052
2057
  ),
2053
2058
  ],
@@ -2086,6 +2091,15 @@ def parse_text_raw(text: str, environment: Optional[Environment] = None):
2086
2091
  PARSER.parse(text)
2087
2092
 
2088
2093
 
2094
+ ERROR_CODES: dict[int, str] = {
2095
+ # 100 code are SQL compatability errors
2096
+ 101: "Trilogy does not have a FROM clause (Datasource resolution is automatic). Remove the FROM clause.",
2097
+ # 200 codes relate to required explicit syntax (we could loosen these?)
2098
+ 201: 'Alias must be specified with "AS" - e.g. `SELECT x AS y`',
2099
+ 210: "Order by must be explicit about direction - specify `asc` or `desc`.",
2100
+ }
2101
+
2102
+
2089
2103
  def parse_text(
2090
2104
  text: str,
2091
2105
  environment: Optional[Environment] = None,
@@ -2135,9 +2149,43 @@ def parse_text(
2135
2149
  ValidationError,
2136
2150
  TypeError,
2137
2151
  ) as e:
2138
- if isinstance(
2139
- e, (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput, UnexpectedToken)
2140
- ):
2152
+ if isinstance(e, UnexpectedToken):
2153
+ if e.expected == {"ORDERING_DIRECTION"}:
2154
+ code = 210
2155
+ raise InvalidSyntaxException(
2156
+ f"Syntax [{code}]:"
2157
+ + ERROR_CODES[code]
2158
+ + "\nContext:\n"
2159
+ + e.get_context(text.replace("\n", " "), 20)
2160
+ )
2161
+ parsed_tokens = (
2162
+ [x.value for x in e.token_history] if e.token_history else []
2163
+ )
2164
+ if parsed_tokens == ["FROM"]:
2165
+ code = 101
2166
+ raise InvalidSyntaxException(
2167
+ f"Syntax [{code}]:"
2168
+ + ERROR_CODES[code]
2169
+ + "\nContext:\n"
2170
+ + e.get_context(text.replace("\n", " "), 20)
2171
+ )
2172
+ # recovery attempt for aliasing
2173
+ try:
2174
+ e.interactive_parser.feed_token(Token("AS", "AS"))
2175
+ e.interactive_parser.feed_token(e.token)
2176
+ raise InvalidSyntaxException(
2177
+ f"Syntax [{ERROR_CODES[201]}]:"
2178
+ + ERROR_CODES[201]
2179
+ + "\nContext:\n"
2180
+ + e.get_context(text.replace("\n", " "), 20)
2181
+ )
2182
+ except UnexpectedToken:
2183
+ pass
2184
+ raise InvalidSyntaxException(
2185
+ str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
2186
+ )
2187
+ elif isinstance(e, (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput)):
2188
+
2141
2189
  raise InvalidSyntaxException(
2142
2190
  str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
2143
2191
  )
@@ -130,7 +130,9 @@
130
130
 
131
131
  over_list: concept_lit ("," concept_lit )* ","?
132
132
 
133
- !ordering: /ASC|DESC/i ("NULLS"i /FIRST|LAST|AUTO/i )?
133
+ ORDERING_DIRECTION: /ASC|DESC/i
134
+
135
+ !ordering: ORDERING_DIRECTION ("NULLS"i /FIRST|LAST|AUTO/i )?
134
136
 
135
137
  order_by: "ORDER"i "BY"i order_list
136
138
 
File without changes
File without changes
File without changes
File without changes