pytrilogy 0.0.3.16__tar.gz → 0.0.3.18__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 (134) hide show
  1. {pytrilogy-0.0.3.16/pytrilogy.egg-info → pytrilogy-0.0.3.18}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_show.py +3 -3
  4. pytrilogy-0.0.3.18/tests/test_typing.py +145 -0
  5. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/__init__.py +1 -1
  6. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/authoring/__init__.py +3 -0
  7. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/enums.py +1 -0
  8. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/ergonomics.py +0 -1
  9. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/functions.py +33 -14
  10. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/author.py +34 -19
  11. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/core.py +23 -3
  12. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/environment.py +1 -0
  13. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/author.py +5 -2
  14. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/duckdb.py +2 -1
  15. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/common.py +4 -0
  16. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/parse_engine.py +80 -5
  17. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/trilogy.lark +5 -3
  18. pytrilogy-0.0.3.16/tests/test_typing.py +0 -42
  19. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/LICENSE.md +0 -0
  20. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/README.md +0 -0
  21. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pyproject.toml +0 -0
  22. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/SOURCES.txt +0 -0
  23. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/dependency_links.txt +0 -0
  24. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/entry_points.txt +0 -0
  25. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/requires.txt +0 -0
  26. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/pytrilogy.egg-info/top_level.txt +0 -0
  27. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/setup.cfg +0 -0
  28. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/setup.py +0 -0
  29. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_datatypes.py +0 -0
  30. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_declarations.py +0 -0
  31. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_derived_concepts.py +0 -0
  32. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_discovery_nodes.py +0 -0
  33. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_enums.py +0 -0
  34. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_environment.py +0 -0
  35. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_executor.py +0 -0
  36. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_functions.py +0 -0
  37. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_imports.py +0 -0
  38. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_metadata.py +0 -0
  39. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_models.py +0 -0
  40. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_multi_join_assignments.py +0 -0
  41. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_parse_engine.py +0 -0
  42. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_parsing.py +0 -0
  43. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_partial_handling.py +0 -0
  44. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_query_processing.py +0 -0
  45. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_query_render.py +0 -0
  46. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_select.py +0 -0
  47. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_statements.py +0 -0
  48. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_undefined_concept.py +0 -0
  49. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_user_functions.py +0 -0
  50. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/tests/test_where_clause.py +0 -0
  51. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/compiler.py +0 -0
  52. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/constants.py +0 -0
  53. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/__init__.py +0 -0
  54. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/constants.py +0 -0
  55. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/env_processor.py +0 -0
  56. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/environment_helpers.py +0 -0
  57. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/exceptions.py +0 -0
  58. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/graph_models.py +0 -0
  59. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/internal.py +0 -0
  60. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/__init__.py +0 -0
  61. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/build.py +0 -0
  62. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/build_environment.py +0 -0
  63. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/datasource.py +0 -0
  64. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/models/execute.py +0 -0
  65. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimization.py +0 -0
  66. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/__init__.py +0 -0
  67. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/base_optimization.py +0 -0
  68. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/inline_constant.py +0 -0
  69. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/inline_datasource.py +0 -0
  70. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  71. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/__init__.py +0 -0
  72. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  73. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/graph_utils.py +0 -0
  74. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/__init__.py +0 -0
  75. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  76. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/common.py +0 -0
  77. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  78. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/group_node.py +0 -0
  79. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  80. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  81. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  82. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  83. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  84. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  85. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  86. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/select_node.py +0 -0
  87. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  88. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/union_node.py +0 -0
  89. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  90. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/node_generators/window_node.py +0 -0
  91. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/__init__.py +0 -0
  92. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/base_node.py +0 -0
  93. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/filter_node.py +0 -0
  94. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/group_node.py +0 -0
  95. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/merge_node.py +0 -0
  96. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  97. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/union_node.py +0 -0
  98. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  99. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/nodes/window_node.py +0 -0
  100. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/processing/utility.py +0 -0
  101. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/query_processor.py +0 -0
  102. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/__init__.py +0 -0
  103. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/build.py +0 -0
  104. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/common.py +0 -0
  105. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/core/statements/execute.py +0 -0
  106. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/__init__.py +0 -0
  107. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/base.py +0 -0
  108. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/bigquery.py +0 -0
  109. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/common.py +0 -0
  110. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/config.py +0 -0
  111. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/dataframe.py +0 -0
  112. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/enums.py +0 -0
  113. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/postgres.py +0 -0
  114. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/presto.py +0 -0
  115. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/snowflake.py +0 -0
  116. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/dialect/sql_server.py +0 -0
  117. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/engine.py +0 -0
  118. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/executor.py +0 -0
  119. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/hooks/__init__.py +0 -0
  120. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/hooks/base_hook.py +0 -0
  121. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/hooks/graph_hook.py +0 -0
  122. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/hooks/query_debugger.py +0 -0
  123. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/metadata/__init__.py +0 -0
  124. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parser.py +0 -0
  125. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/__init__.py +0 -0
  126. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/config.py +0 -0
  127. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/exceptions.py +0 -0
  128. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/helpers.py +0 -0
  129. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/parsing/render.py +0 -0
  130. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/py.typed +0 -0
  131. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/render.py +0 -0
  132. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/scripts/__init__.py +0 -0
  133. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/scripts/trilogy.py +0 -0
  134. {pytrilogy-0.0.3.16 → pytrilogy-0.0.3.18}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.16
3
+ Version: 0.0.3.18
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.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.16
3
+ Version: 0.0.3.18
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -52,6 +52,6 @@ def test_show_bigquery():
52
52
  .execute_query(select)
53
53
  .fetchall()
54
54
  )
55
- assert "FULL JOIN wakeful on 1=1" in query[0]["__preql_internal_query_text"], query[
56
- 0
57
- ]["__preql_internal_query_text"]
55
+ assert (
56
+ "FULL JOIN cheerful on 1=1" in query[0]["__preql_internal_query_text"]
57
+ ), query[0]["__preql_internal_query_text"]
@@ -0,0 +1,145 @@
1
+ from decimal import Decimal
2
+
3
+ from trilogy import Dialects
4
+
5
+
6
+ def test_typing():
7
+ env = Dialects.DUCK_DB.default_executor()
8
+ env.environment.parse(
9
+ """
10
+
11
+ type email string;
12
+
13
+ key customer_id int;
14
+ property customer_id.email string::email;
15
+
16
+ def is_valid_email(email) -> contains(email, '@');
17
+
18
+ datasource customers (
19
+ id:customer_id,
20
+ email: email
21
+ )
22
+ grain (customer_id)
23
+ query '''
24
+ select 1 as id, 'bright@gmail.com' as email
25
+ union all
26
+ select 2 as id, 'funky@hotmail.com' as email
27
+ ''';
28
+
29
+
30
+ """
31
+ )
32
+
33
+ results = env.execute_query(
34
+ """SELECT
35
+ customer_id,
36
+ @is_valid_email(email)->valid;"""
37
+ )
38
+
39
+ for row in results.fetchall():
40
+ assert row.valid is True
41
+
42
+ assert "email" in env.environment.data_types
43
+
44
+ assert env.environment.concepts["email"].datatype.traits == ["email"]
45
+
46
+
47
+ def test_typing_aggregate():
48
+ env = Dialects.DUCK_DB.default_executor()
49
+ env.environment.parse(
50
+ """
51
+
52
+ type money float;
53
+
54
+ key revenue float::money;
55
+ key revenue_two float::money;
56
+ key multiplier float;
57
+
58
+ datasource orders (
59
+ revenue:revenue,
60
+ revenue_two:revenue_two,
61
+ multiplier:multiplier
62
+ )
63
+ grain (revenue)
64
+ query '''
65
+ select 5.0 as revenue, 3.3 as revenue_two, 2.0 as multiplier
66
+ union all
67
+ select 10.0 as revenue, 13.1 as revenue_two, 3.0 as multiplier
68
+ ''';
69
+
70
+
71
+ """
72
+ )
73
+
74
+ results = env.execute_query(
75
+ """SELECT
76
+ sum(revenue)->direct_total,
77
+ sum(revenue*multiplier)->total,
78
+ direct_total-total -> diff;"""
79
+ )
80
+
81
+ for row in results.fetchall():
82
+ assert row.total == Decimal("40.00")
83
+
84
+ assert "money" in env.environment.data_types
85
+ assert env.environment.concepts["direct_total"].datatype.traits == ["money"]
86
+ assert env.environment.concepts["total"].datatype.traits == ["money"]
87
+ assert env.environment.concepts["diff"].datatype.traits == [
88
+ "money"
89
+ ], env.environment.concepts["diff"].datatype
90
+
91
+ results = env.execute_query(
92
+ """SELECT
93
+ revenue+revenue_two->add_total,
94
+ revenue-revenue_two->sub_total
95
+ ;"""
96
+ )
97
+
98
+ assert "money" in env.environment.data_types
99
+ assert env.environment.concepts["add_total"].datatype.traits == ["money"]
100
+ assert env.environment.concepts["sub_total"].datatype.traits == ["money"]
101
+
102
+
103
+ def test_custom_function_typing():
104
+ env = Dialects.DUCK_DB.default_executor()
105
+ env.environment.parse(
106
+ """
107
+
108
+ type money float;
109
+
110
+ key revenue float::money;
111
+
112
+ def revenue_times_2(revenue) -> revenue*2;
113
+
114
+ def revenue_times_multiplier(revenue, multiplier) -> revenue*multiplier;
115
+
116
+ datasource orders (
117
+ revenue:revenue,
118
+
119
+ )
120
+ grain (revenue)
121
+ query '''
122
+ select 5.0 as revenue, 3.3 as revenue_two, 2.0 as multiplier
123
+ union all
124
+ select 10.0 as revenue, 13.1 as revenue_two, 3.0 as multiplier
125
+ ''';
126
+
127
+
128
+ """
129
+ )
130
+
131
+ _ = env.execute_query(
132
+ """
133
+ with scaled as
134
+ SELECT
135
+ @revenue_times_2(revenue)->revenue
136
+ ;
137
+
138
+ SELECT
139
+ sum(
140
+ round( @revenue_times_multiplier(lag 1 @revenue_times_2(scaled.revenue), 2.0), 2)
141
+ )->total;
142
+ """
143
+ )
144
+
145
+ assert env.environment.concepts["total"].datatype.traits == ["money"]
@@ -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.16"
7
+ __version__ = "0.0.3.18"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -40,6 +40,7 @@ from trilogy.core.models.core import (
40
40
  MapType,
41
41
  StructType,
42
42
  )
43
+ from trilogy.core.models.datasource import Datasource, DatasourceMetadata
43
44
  from trilogy.core.models.environment import Environment
44
45
  from trilogy.core.statements.author import (
45
46
  ConceptDeclarationStatement,
@@ -100,4 +101,6 @@ __all__ = [
100
101
  "MultiSelectStatement",
101
102
  "PersistStatement",
102
103
  "RawSQLStatement",
104
+ "Datasource",
105
+ "DatasourceMetadata",
103
106
  ]
@@ -193,6 +193,7 @@ class FunctionType(Enum):
193
193
  DATE_PART = "date_part"
194
194
  DATE_TRUNCATE = "date_truncate"
195
195
  DATE_ADD = "date_add"
196
+ DATE_SUB = "date_sub"
196
197
  DATE_DIFF = "date_diff"
197
198
 
198
199
  # UNIX
@@ -3,7 +3,6 @@ from trilogy.constants import CONFIG
3
3
  # source: https://github.com/aaronbassett/Pass-phrase
4
4
  CTE_NAMES = """quizzical
5
5
  highfalutin
6
- dynamic
7
6
  wakeful
8
7
  cheerful
9
8
  thoughtful
@@ -29,6 +29,7 @@ from trilogy.core.models.core import (
29
29
  MapType,
30
30
  NumericType,
31
31
  StructType,
32
+ TraitDataType,
32
33
  arg_to_datatype,
33
34
  merge_datatypes,
34
35
  )
@@ -94,6 +95,10 @@ def get_cast_output_type(
94
95
  return args[1]
95
96
 
96
97
 
98
+ def get_output_type_at_index(args, index: int):
99
+ return arg_to_datatype(args[index])
100
+
101
+
97
102
  def validate_case_output(
98
103
  args: list[Any],
99
104
  ) -> DataType:
@@ -140,6 +145,12 @@ def get_date_trunc_output(
140
145
 
141
146
 
142
147
  FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
148
+ FunctionType.ALIAS: FunctionConfig(
149
+ arg_count=1,
150
+ ),
151
+ FunctionType.PARENTHETICAL: FunctionConfig(
152
+ arg_count=1,
153
+ ),
143
154
  FunctionType.UNNEST: FunctionConfig(
144
155
  valid_inputs={
145
156
  DataType.ARRAY,
@@ -173,7 +184,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
173
184
  DataType.BOOL,
174
185
  },
175
186
  output_purpose=Purpose.METRIC,
176
- output_type=DataType.INTEGER,
177
187
  arg_count=1,
178
188
  ),
179
189
  FunctionType.MIN: FunctionConfig(
@@ -186,7 +196,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
186
196
  DataType.TIMESTAMP,
187
197
  },
188
198
  output_purpose=Purpose.METRIC,
189
- output_type=DataType.INTEGER,
190
199
  arg_count=1,
191
200
  ),
192
201
  FunctionType.SPLIT: FunctionConfig(
@@ -237,7 +246,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
237
246
  FunctionType.ABS: FunctionConfig(
238
247
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
239
248
  output_purpose=Purpose.PROPERTY,
240
- output_type=DataType.INTEGER,
241
249
  arg_count=1,
242
250
  ),
243
251
  FunctionType.COALESCE: FunctionConfig(
@@ -370,6 +378,21 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
370
378
  output_type=DataType.DATE,
371
379
  arg_count=3,
372
380
  ),
381
+ FunctionType.DATE_SUB: FunctionConfig(
382
+ valid_inputs=[
383
+ {
384
+ DataType.DATE,
385
+ DataType.TIMESTAMP,
386
+ DataType.DATETIME,
387
+ DataType.STRING,
388
+ },
389
+ {DataType.DATE_PART},
390
+ {DataType.INTEGER},
391
+ ],
392
+ output_purpose=Purpose.PROPERTY,
393
+ output_type=DataType.DATE,
394
+ arg_count=3,
395
+ ),
373
396
  FunctionType.DATE_DIFF: FunctionConfig(
374
397
  valid_inputs=[
375
398
  {
@@ -519,7 +542,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
519
542
  DataType.NUMERIC,
520
543
  },
521
544
  output_purpose=Purpose.PROPERTY,
522
- output_type=DataType.INTEGER,
523
545
  arg_count=InfiniteFunctionArgs,
524
546
  ),
525
547
  FunctionType.SUBTRACT: FunctionConfig(
@@ -530,7 +552,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
530
552
  DataType.NUMERIC,
531
553
  },
532
554
  output_purpose=Purpose.PROPERTY,
533
- output_type=DataType.INTEGER,
534
555
  arg_count=InfiniteFunctionArgs,
535
556
  ),
536
557
  FunctionType.MULTIPLY: FunctionConfig(
@@ -541,7 +562,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
541
562
  DataType.NUMERIC,
542
563
  },
543
564
  output_purpose=Purpose.PROPERTY,
544
- output_type=DataType.INTEGER,
545
565
  arg_count=InfiniteFunctionArgs,
546
566
  ),
547
567
  FunctionType.DIVIDE: FunctionConfig(
@@ -552,7 +572,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
552
572
  DataType.NUMERIC,
553
573
  },
554
574
  output_purpose=Purpose.PROPERTY,
555
- output_type=DataType.INTEGER,
556
575
  arg_count=InfiniteFunctionArgs,
557
576
  ),
558
577
  FunctionType.MOD: FunctionConfig(
@@ -570,7 +589,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
570
589
  {DataType.INTEGER},
571
590
  ],
572
591
  output_purpose=Purpose.PROPERTY,
573
- output_type=DataType.INTEGER,
592
+ output_type_function=lambda args: get_output_type_at_index(args, 0),
574
593
  arg_count=2,
575
594
  ),
576
595
  FunctionType.CUSTOM: FunctionConfig(
@@ -621,13 +640,11 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
621
640
  FunctionType.SUM: FunctionConfig(
622
641
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
623
642
  output_purpose=Purpose.METRIC,
624
- output_type=DataType.INTEGER,
625
643
  arg_count=1,
626
644
  ),
627
645
  FunctionType.AVG: FunctionConfig(
628
646
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
629
647
  output_purpose=Purpose.METRIC,
630
- output_type=DataType.INTEGER,
631
648
  arg_count=1,
632
649
  ),
633
650
  FunctionType.UNIX_TO_TIMESTAMP: FunctionConfig(
@@ -640,8 +657,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
640
657
 
641
658
  EXCLUDED_FUNCTIONS = {
642
659
  FunctionType.CUSTOM,
643
- FunctionType.ALIAS,
644
- FunctionType.PARENTHETICAL,
645
660
  # Temporary
646
661
  FunctionType.DATE_LITERAL,
647
662
  FunctionType.DATETIME_LITERAL,
@@ -650,7 +665,9 @@ EXCLUDED_FUNCTIONS = {
650
665
 
651
666
  for k in FunctionType.__members__.values():
652
667
  if k not in FUNCTION_REGISTRY and k not in EXCLUDED_FUNCTIONS:
653
- raise InvalidSyntaxException(f"Function {k} not in registry")
668
+ raise InvalidSyntaxException(
669
+ f"Function enum value {k} not in creation registry"
670
+ )
654
671
 
655
672
 
656
673
  class FunctionFactory:
@@ -686,8 +703,10 @@ class FunctionFactory:
686
703
  full_args = []
687
704
  final_output_type: CONCRETE_TYPES
688
705
  if config.output_type_function:
706
+
689
707
  final_output_type = config.output_type_function(full_args)
690
708
  elif not base_output_type:
709
+
691
710
  final_output_type = merge_datatypes([arg_to_datatype(x) for x in full_args])
692
711
  elif base_output_type:
693
712
  final_output_type = base_output_type
@@ -715,7 +734,7 @@ def create_function_derived_concept(
715
734
  operator: FunctionType,
716
735
  arguments: list[Concept],
717
736
  output_type: Optional[
718
- DataType | ListType | StructType | MapType | NumericType
737
+ DataType | ListType | StructType | MapType | NumericType | TraitDataType
719
738
  ] = None,
720
739
  output_purpose: Optional[Purpose] = None,
721
740
  ) -> Concept:
@@ -793,6 +793,10 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
793
793
  base = f"{self.address}@{self.grain}"
794
794
  return base
795
795
 
796
+ @property
797
+ def is_internal(self) -> bool:
798
+ return self.namespace.startswith("_") or self.name.startswith("_")
799
+
796
800
  @property
797
801
  def reference(self) -> ConceptRef:
798
802
  return ConceptRef.model_construct(
@@ -1370,7 +1374,7 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1370
1374
 
1371
1375
 
1372
1376
  def get_basic_type(
1373
- type: DataType | ListType | StructType | MapType | NumericType,
1377
+ type: DataType | ListType | StructType | MapType | NumericType | TraitDataType,
1374
1378
  ) -> DataType:
1375
1379
  if isinstance(type, ListType):
1376
1380
  return DataType.LIST
@@ -1380,6 +1384,8 @@ def get_basic_type(
1380
1384
  return DataType.MAP
1381
1385
  if isinstance(type, NumericType):
1382
1386
  return DataType.NUMERIC
1387
+ if isinstance(type, TraitDataType):
1388
+ return type.type
1383
1389
  return type
1384
1390
 
1385
1391
 
@@ -1531,7 +1537,9 @@ def get_concept_arguments(expr) -> List["ConceptRef"]:
1531
1537
  class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1532
1538
  operator: FunctionType
1533
1539
  arg_count: int = Field(default=1)
1534
- output_datatype: DataType | ListType | StructType | MapType | NumericType
1540
+ output_datatype: (
1541
+ DataType | ListType | StructType | MapType | NumericType | TraitDataType
1542
+ )
1535
1543
  output_purpose: Purpose
1536
1544
  valid_inputs: Optional[
1537
1545
  Union[
@@ -1629,23 +1637,30 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1629
1637
  return v
1630
1638
 
1631
1639
  def with_reference_replacement(self, source: str, target: Expr):
1640
+ from trilogy.core.functions import arg_to_datatype, merge_datatypes
1641
+
1642
+ nargs = [
1643
+ (
1644
+ c.with_reference_replacement(
1645
+ source,
1646
+ target,
1647
+ )
1648
+ if isinstance(
1649
+ c,
1650
+ Mergeable,
1651
+ )
1652
+ else c
1653
+ )
1654
+ for c in self.arguments
1655
+ ]
1656
+ if self.output_datatype == DataType.UNKNOWN:
1657
+ new_output = merge_datatypes([arg_to_datatype(x) for x in nargs])
1658
+ else:
1659
+ new_output = self.output_datatype
1632
1660
  return Function.model_construct(
1633
1661
  operator=self.operator,
1634
- arguments=[
1635
- (
1636
- c.with_reference_replacement(
1637
- source,
1638
- target,
1639
- )
1640
- if isinstance(
1641
- c,
1642
- Mergeable,
1643
- )
1644
- else c
1645
- )
1646
- for c in self.arguments
1647
- ],
1648
- output_datatype=self.output_datatype,
1662
+ arguments=nargs,
1663
+ output_datatype=new_output,
1649
1664
  output_purpose=self.output_purpose,
1650
1665
  valid_inputs=self.valid_inputs,
1651
1666
  arg_count=self.arg_count,
@@ -1777,7 +1792,7 @@ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel)
1777
1792
  )
1778
1793
 
1779
1794
 
1780
- class FilterItem(Namespaced, ConceptArgs, BaseModel):
1795
+ class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
1781
1796
  content: ConceptRef
1782
1797
  where: "WhereClause"
1783
1798
 
@@ -1837,7 +1852,7 @@ class RowsetLineage(Namespaced, Mergeable, BaseModel):
1837
1852
  )
1838
1853
 
1839
1854
 
1840
- class RowsetItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1855
+ class RowsetItem(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel):
1841
1856
  content: ConceptRef
1842
1857
  rowset: RowsetLineage
1843
1858
 
@@ -51,7 +51,13 @@ class Addressable(ABC):
51
51
 
52
52
 
53
53
  TYPEDEF_TYPES = Union[
54
- "DataType", "MapType", "ListType", "NumericType", "StructType", "DataTyped"
54
+ "DataType",
55
+ "MapType",
56
+ "ListType",
57
+ "NumericType",
58
+ "StructType",
59
+ "DataTyped",
60
+ "TraitDataType",
55
61
  ]
56
62
 
57
63
  CONCRETE_TYPES = Union[
@@ -60,6 +66,7 @@ CONCRETE_TYPES = Union[
60
66
  "ListType",
61
67
  "NumericType",
62
68
  "StructType",
69
+ "TraitDataType",
63
70
  ]
64
71
 
65
72
  KT = TypeVar("KT")
@@ -101,6 +108,16 @@ class TraitDataType(BaseModel):
101
108
  type: DataType
102
109
  traits: list[str]
103
110
 
111
+ def __hash__(self):
112
+ return hash(self.type)
113
+
114
+ def __eq__(self, other):
115
+ if isinstance(other, DataType):
116
+ return self.type == other
117
+ elif isinstance(other, TraitDataType):
118
+ return self.type == other.type and self.traits == other.traits
119
+ return False
120
+
104
121
  @property
105
122
  def data_type(self):
106
123
  return self.type
@@ -312,8 +329,10 @@ def dict_to_map_wrapper(arg):
312
329
 
313
330
 
314
331
  def merge_datatypes(
315
- inputs: list[DataType | ListType | StructType | MapType | NumericType],
316
- ) -> DataType | ListType | StructType | MapType | NumericType:
332
+ inputs: list[
333
+ DataType | ListType | StructType | MapType | NumericType | TraitDataType
334
+ ],
335
+ ) -> DataType | ListType | StructType | MapType | NumericType | TraitDataType:
317
336
  """This is a temporary hack for doing between
318
337
  allowable datatype transformation matrix"""
319
338
  if len(inputs) == 1:
@@ -348,6 +367,7 @@ def is_compatible_datatype(left, right):
348
367
 
349
368
 
350
369
  def arg_to_datatype(arg) -> CONCRETE_TYPES:
370
+
351
371
  if isinstance(arg, MagicConstants):
352
372
  if arg == MagicConstants.NULL:
353
373
  return DataType.NULL
@@ -58,6 +58,7 @@ if TYPE_CHECKING:
58
58
  class Import:
59
59
  alias: str
60
60
  path: Path
61
+ input_path: str | None = None
61
62
 
62
63
 
63
64
  class BaseImportResolver(BaseModel):
@@ -134,7 +134,6 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
134
134
  output.grain = output.calculate_grain(environment)
135
135
 
136
136
  for x in selection:
137
-
138
137
  if x.is_undefined and environment.concepts.fail_on_missing:
139
138
  environment.concepts.raise_undefined(
140
139
  x.concept.address, meta.line_number if meta else None
@@ -158,7 +157,6 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
158
157
  output.local_concepts[x.content.address] = environment.concepts[
159
158
  x.content.address
160
159
  ] # .set_select_grain(output.grain, environment)
161
-
162
160
  output.validate_syntax(environment)
163
161
  return output
164
162
 
@@ -384,7 +382,12 @@ class MergeStatementV2(HasUUID, BaseModel):
384
382
 
385
383
 
386
384
  class ImportStatement(HasUUID, BaseModel):
385
+ # import abc.def as bar
386
+ # the bit after 'as', eg bar
387
387
  alias: str
388
+ # the bit after import, abc.def
389
+ input_path: str
390
+ # what it actually resolves to, typically a filepath
388
391
  path: Path
389
392
 
390
393
 
@@ -8,6 +8,7 @@ from trilogy.dialect.base import BaseDialect
8
8
 
9
9
  WINDOW_FUNCTION_MAP: Mapping[WindowType, Callable[[Any, Any, Any], str]] = {}
10
10
 
11
+
11
12
  FUNCTION_MAP = {
12
13
  FunctionType.COUNT: lambda args: f"count({args[0]})",
13
14
  FunctionType.SUM: lambda args: f"sum({args[0]})",
@@ -27,7 +28,7 @@ FUNCTION_MAP = {
27
28
  # datetime is aliased
28
29
  FunctionType.CURRENT_DATETIME: lambda x: "cast(get_current_timestamp() as datetime)",
29
30
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc('{x[1]}', {x[0]})",
30
- FunctionType.DATE_ADD: lambda x: f"date_add({x[0]}, INTERVAL {x[2]} {x[1]})",
31
+ FunctionType.DATE_ADD: lambda x: f"date_add({x[0]}, {x[2]} * INTERVAL 1 {x[1]})",
31
32
  FunctionType.DATE_PART: lambda x: f"date_part('{x[1]}', {x[0]})",
32
33
  FunctionType.DATE_DIFF: lambda x: f"date_diff('{x[2]}', {x[0]}, {x[1]})",
33
34
  FunctionType.CONCAT: lambda x: f"({' || '.join(x)})",
@@ -100,6 +100,8 @@ def process_function_arg(
100
100
  concept.metadata.line_number = meta.line
101
101
  environment.add_concept(concept, meta=meta)
102
102
  return concept
103
+ elif isinstance(arg, ConceptRef):
104
+ return environment.concepts[arg.address]
103
105
  return arg
104
106
 
105
107
 
@@ -192,6 +194,8 @@ def concept_is_relevant(
192
194
  ) -> bool:
193
195
  if isinstance(concept, UndefinedConcept):
194
196
 
197
+ return False
198
+ if concept.datatype == DataType.UNKNOWN:
195
199
  return False
196
200
  if isinstance(concept, ConceptRef):
197
201
  if environment: