pytrilogy 0.0.3.9__tar.gz → 0.0.3.11__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 (133) hide show
  1. {pytrilogy-0.0.3.9/pytrilogy.egg-info → pytrilogy-0.0.3.11}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/pytrilogy.egg-info/SOURCES.txt +3 -0
  4. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_parsing.py +43 -1
  5. pytrilogy-0.0.3.11/tests/test_query_render.py +13 -0
  6. pytrilogy-0.0.3.11/tests/test_typing.py +42 -0
  7. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/__init__.py +1 -1
  8. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/authoring/__init__.py +14 -1
  9. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/enums.py +1 -0
  10. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/functions.py +9 -0
  11. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/models/author.py +13 -7
  12. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/models/core.py +13 -0
  13. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/models/environment.py +20 -4
  14. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/group_node.py +1 -1
  15. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/statements/author.py +13 -0
  16. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/base.py +1 -0
  17. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/bigquery.py +2 -3
  18. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/duckdb.py +2 -2
  19. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/enums.py +5 -0
  20. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/presto.py +1 -1
  21. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/sql_server.py +1 -1
  22. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/executor.py +2 -34
  23. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parsing/parse_engine.py +59 -13
  24. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parsing/trilogy.lark +12 -8
  25. pytrilogy-0.0.3.11/trilogy/render.py +38 -0
  26. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/LICENSE.md +0 -0
  27. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/README.md +0 -0
  28. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/pyproject.toml +0 -0
  29. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/pytrilogy.egg-info/dependency_links.txt +0 -0
  30. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/pytrilogy.egg-info/entry_points.txt +0 -0
  31. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/pytrilogy.egg-info/requires.txt +0 -0
  32. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/pytrilogy.egg-info/top_level.txt +0 -0
  33. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/setup.cfg +0 -0
  34. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/setup.py +0 -0
  35. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_datatypes.py +0 -0
  36. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_declarations.py +0 -0
  37. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_derived_concepts.py +0 -0
  38. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_discovery_nodes.py +0 -0
  39. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_enums.py +0 -0
  40. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_environment.py +0 -0
  41. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_executor.py +0 -0
  42. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_functions.py +0 -0
  43. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_imports.py +0 -0
  44. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_metadata.py +0 -0
  45. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_models.py +0 -0
  46. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_multi_join_assignments.py +0 -0
  47. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_parse_engine.py +0 -0
  48. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_partial_handling.py +0 -0
  49. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_query_processing.py +0 -0
  50. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_select.py +0 -0
  51. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_show.py +0 -0
  52. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_statements.py +0 -0
  53. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_undefined_concept.py +0 -0
  54. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_user_functions.py +0 -0
  55. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/tests/test_where_clause.py +0 -0
  56. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/compiler.py +0 -0
  57. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/constants.py +0 -0
  58. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/__init__.py +0 -0
  59. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/constants.py +0 -0
  60. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/env_processor.py +0 -0
  61. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/environment_helpers.py +0 -0
  62. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/ergonomics.py +0 -0
  63. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/exceptions.py +0 -0
  64. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/graph_models.py +0 -0
  65. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/internal.py +0 -0
  66. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/models/__init__.py +0 -0
  67. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/models/build.py +0 -0
  68. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/models/build_environment.py +0 -0
  69. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/models/datasource.py +0 -0
  70. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/models/execute.py +0 -0
  71. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/optimization.py +0 -0
  72. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/optimizations/__init__.py +0 -0
  73. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/optimizations/base_optimization.py +0 -0
  74. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/optimizations/inline_constant.py +0 -0
  75. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/optimizations/inline_datasource.py +0 -0
  76. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  77. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/__init__.py +0 -0
  78. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  79. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/graph_utils.py +0 -0
  80. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/__init__.py +0 -0
  81. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  82. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/common.py +0 -0
  83. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  84. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/group_node.py +0 -0
  85. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  86. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  87. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  88. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  89. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  90. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  91. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  92. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/select_node.py +0 -0
  93. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  94. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/union_node.py +0 -0
  95. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  96. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/node_generators/window_node.py +0 -0
  97. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/__init__.py +0 -0
  98. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/base_node.py +0 -0
  99. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/filter_node.py +0 -0
  100. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/merge_node.py +0 -0
  101. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  102. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/union_node.py +0 -0
  103. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  104. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/nodes/window_node.py +0 -0
  105. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/processing/utility.py +0 -0
  106. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/query_processor.py +0 -0
  107. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/statements/__init__.py +0 -0
  108. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/statements/build.py +0 -0
  109. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/statements/common.py +0 -0
  110. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/core/statements/execute.py +0 -0
  111. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/__init__.py +0 -0
  112. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/common.py +0 -0
  113. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/config.py +0 -0
  114. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/dataframe.py +0 -0
  115. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/postgres.py +0 -0
  116. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/dialect/snowflake.py +0 -0
  117. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/engine.py +0 -0
  118. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/hooks/__init__.py +0 -0
  119. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/hooks/base_hook.py +0 -0
  120. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/hooks/graph_hook.py +0 -0
  121. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/hooks/query_debugger.py +0 -0
  122. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/metadata/__init__.py +0 -0
  123. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parser.py +0 -0
  124. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parsing/__init__.py +0 -0
  125. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parsing/common.py +0 -0
  126. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parsing/config.py +0 -0
  127. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parsing/exceptions.py +0 -0
  128. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parsing/helpers.py +0 -0
  129. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/parsing/render.py +0 -0
  130. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/py.typed +0 -0
  131. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/scripts/__init__.py +0 -0
  132. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/scripts/trilogy.py +0 -0
  133. {pytrilogy-0.0.3.9 → pytrilogy-0.0.3.11}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.9
3
+ Version: 0.0.3.11
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.9
3
+ Version: 0.0.3.11
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -24,9 +24,11 @@ tests/test_parse_engine.py
24
24
  tests/test_parsing.py
25
25
  tests/test_partial_handling.py
26
26
  tests/test_query_processing.py
27
+ tests/test_query_render.py
27
28
  tests/test_select.py
28
29
  tests/test_show.py
29
30
  tests/test_statements.py
31
+ tests/test_typing.py
30
32
  tests/test_undefined_concept.py
31
33
  tests/test_user_functions.py
32
34
  tests/test_where_clause.py
@@ -37,6 +39,7 @@ trilogy/engine.py
37
39
  trilogy/executor.py
38
40
  trilogy/parser.py
39
41
  trilogy/py.typed
42
+ trilogy/render.py
40
43
  trilogy/utility.py
41
44
  trilogy/authoring/__init__.py
42
45
  trilogy/core/__init__.py
@@ -8,7 +8,11 @@ from trilogy.core.models.core import (
8
8
  TupleWrapper,
9
9
  )
10
10
  from trilogy.core.models.datasource import Datasource
11
- from trilogy.core.models.environment import Environment
11
+ from trilogy.core.models.environment import (
12
+ DictImportResolver,
13
+ Environment,
14
+ EnvironmentOptions,
15
+ )
12
16
  from trilogy.core.statements.author import SelectStatement, ShowStatement
13
17
  from trilogy.core.statements.execute import ProcessedQuery
14
18
  from trilogy.dialect.base import BaseDialect
@@ -619,3 +623,41 @@ select [1,2,3,4] as int_array, 2 as scalar
619
623
 
620
624
  env, parsed = parse_text(x)
621
625
  assert env.concepts["split"].datatype == DataType.INTEGER
626
+
627
+
628
+ def test_non_file_imports():
629
+
630
+ env = Environment(
631
+ config=EnvironmentOptions(
632
+ import_resolver=DictImportResolver(
633
+ content={
634
+ "test": """
635
+ import test_dep as test_dep;
636
+ key x int;
637
+
638
+ datasource test (
639
+ x: x)
640
+ grain(x)
641
+ query '''
642
+ select 1 as x
643
+ union all
644
+ select 11 as x
645
+ ''';
646
+ """,
647
+ "test_dep": """
648
+ key x int;
649
+ """,
650
+ }
651
+ )
652
+ )
653
+ )
654
+ assert isinstance(env.config.import_resolver, DictImportResolver)
655
+ env.parse(
656
+ """
657
+ import test;
658
+
659
+ select x % 10 -> x_mod_10;
660
+
661
+
662
+ """
663
+ )
@@ -0,0 +1,13 @@
1
+ from trilogy import Dialects, Environment
2
+ from trilogy.core.query_processor import process_query
3
+ from trilogy.render import get_dialect_generator
4
+
5
+
6
+ def test_sql_generators():
7
+ env = Environment()
8
+ _, statements = env.parse("""const a <- 1; select a;""")
9
+ processed = process_query(env, statements[-1])
10
+ for dialect in Dialects:
11
+ generator = get_dialect_generator(dialect)
12
+ compiled = generator.compile_statement(processed)
13
+ assert compiled.startswith("""SELECT\n :a"""), f'{dialect} compiled'
@@ -0,0 +1,42 @@
1
+ from trilogy import Dialects
2
+
3
+
4
+ def test_typing():
5
+ env = Dialects.DUCK_DB.default_executor()
6
+ env.environment.parse(
7
+ """
8
+
9
+ type email string;
10
+
11
+ key customer_id int;
12
+ property customer_id.email string::email;
13
+
14
+ def is_valid_email(email) -> contains(email, '@');
15
+
16
+ datasource customers (
17
+ id:customer_id,
18
+ email: email
19
+ )
20
+ grain (customer_id)
21
+ query '''
22
+ select 1 as id, 'bright@gmail.com' as email
23
+ union all
24
+ select 2 as id, 'funky@hotmail.com' as email
25
+ ''';
26
+
27
+
28
+ """
29
+ )
30
+
31
+ results = env.execute_query(
32
+ """SELECT
33
+ customer_id,
34
+ @is_valid_email(email)->valid;"""
35
+ )
36
+
37
+ for row in results.fetchall():
38
+ assert row.valid is True
39
+
40
+ assert "email" in env.environment.data_types
41
+
42
+ assert env.environment.concepts["email"].datatype.traits == ["email"]
@@ -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.9"
7
+ __version__ = "0.0.3.11"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -33,11 +33,20 @@ from trilogy.core.models.author import (
33
33
  WindowOrder,
34
34
  WindowType,
35
35
  )
36
- from trilogy.core.models.core import DataType, ListType, ListWrapper, StructType
36
+ from trilogy.core.models.core import (
37
+ DataType,
38
+ ListType,
39
+ ListWrapper,
40
+ MapType,
41
+ StructType,
42
+ )
37
43
  from trilogy.core.models.environment import Environment
38
44
  from trilogy.core.statements.author import (
39
45
  ConceptDeclarationStatement,
40
46
  ConceptTransform,
47
+ MultiSelectStatement,
48
+ PersistStatement,
49
+ RawSQLStatement,
41
50
  SelectItem,
42
51
  SelectStatement,
43
52
  )
@@ -60,6 +69,7 @@ __all__ = [
60
69
  "DataType",
61
70
  "StructType",
62
71
  "ListType",
72
+ "MapType",
63
73
  "ListWrapper",
64
74
  "FunctionType",
65
75
  "FunctionFactory",
@@ -87,4 +97,7 @@ __all__ = [
87
97
  "DEFAULT_NAMESPACE",
88
98
  "arbitrary_to_concept",
89
99
  "arg_to_datatype",
100
+ "MultiSelectStatement",
101
+ "PersistStatement",
102
+ "RawSQLStatement",
90
103
  ]
@@ -172,6 +172,7 @@ class FunctionType(Enum):
172
172
  UPPER = "upper"
173
173
  SUBSTRING = "substring"
174
174
  STRPOS = "strpos"
175
+ CONTAINS = "contains"
175
176
 
176
177
  # Dates
177
178
  DATE = "date"
@@ -271,6 +271,15 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
271
271
  output_type=DataType.INTEGER,
272
272
  arg_count=2,
273
273
  ),
274
+ FunctionType.CONTAINS: FunctionConfig(
275
+ valid_inputs=[
276
+ {DataType.STRING},
277
+ {DataType.STRING},
278
+ ],
279
+ output_purpose=Purpose.PROPERTY,
280
+ output_type=DataType.BOOL,
281
+ arg_count=2,
282
+ ),
274
283
  FunctionType.SUBSTRING: FunctionConfig(
275
284
  valid_inputs=[{DataType.STRING}, {DataType.INTEGER}, {DataType.INTEGER}],
276
285
  output_purpose=Purpose.PROPERTY,
@@ -55,6 +55,7 @@ from trilogy.core.models.core import (
55
55
  MapWrapper,
56
56
  NumericType,
57
57
  StructType,
58
+ TraitDataType,
58
59
  TupleWrapper,
59
60
  arg_to_datatype,
60
61
  is_compatible_datatype,
@@ -101,9 +102,9 @@ class HasUUID(ABC):
101
102
 
102
103
  class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
103
104
  address: str
104
- datatype: DataType | ListType | StructType | MapType | NumericType = (
105
- DataType.UNKNOWN
106
- )
105
+ datatype: (
106
+ DataType | TraitDataType | ListType | StructType | MapType | NumericType
107
+ ) = DataType.UNKNOWN
107
108
  metadata: Optional["Metadata"] = None
108
109
 
109
110
  @property
@@ -756,7 +757,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
756
757
  extra="forbid",
757
758
  )
758
759
  name: str
759
- datatype: DataType | ListType | StructType | MapType | NumericType
760
+ datatype: DataType | TraitDataType | ListType | StructType | MapType | NumericType
760
761
  purpose: Purpose
761
762
  derivation: Derivation = Derivation.ROOT
762
763
  granularity: Granularity = Granularity.MULTI_ROW
@@ -1217,9 +1218,9 @@ class UndefinedConceptFull(Concept, Mergeable, Namespaced):
1217
1218
  model_config = ConfigDict(arbitrary_types_allowed=True)
1218
1219
  name: str
1219
1220
  line_no: int | None = None
1220
- datatype: DataType | ListType | StructType | MapType | NumericType = (
1221
- DataType.UNKNOWN
1222
- )
1221
+ datatype: (
1222
+ DataType | TraitDataType | ListType | StructType | MapType | NumericType
1223
+ ) = DataType.UNKNOWN
1223
1224
  purpose: Purpose = Purpose.UNKNOWN
1224
1225
 
1225
1226
  @property
@@ -2153,6 +2154,11 @@ class ArgBinding(BaseModel):
2153
2154
  default: Expr | None = None
2154
2155
 
2155
2156
 
2157
+ class CustomType(BaseModel):
2158
+ name: str
2159
+ type: DataType
2160
+
2161
+
2156
2162
  Expr = (
2157
2163
  MagicConstants
2158
2164
  | bool
@@ -97,6 +97,19 @@ class DataType(Enum):
97
97
  return self
98
98
 
99
99
 
100
+ class TraitDataType(BaseModel):
101
+ type: DataType
102
+ traits: list[str]
103
+
104
+ @property
105
+ def data_type(self):
106
+ return self.type
107
+
108
+ @property
109
+ def value(self):
110
+ return self.data_type.value
111
+
112
+
100
113
  class NumericType(BaseModel):
101
114
  precision: int = 20
102
115
  scale: int = 5
@@ -40,6 +40,7 @@ from trilogy.core.exceptions import (
40
40
  from trilogy.core.models.author import (
41
41
  Concept,
42
42
  ConceptRef,
43
+ CustomType,
43
44
  Function,
44
45
  SelectLineage,
45
46
  UndefinedConcept,
@@ -59,8 +60,23 @@ class Import:
59
60
  path: Path
60
61
 
61
62
 
63
+ class BaseImportResolver(BaseModel):
64
+ pass
65
+
66
+
67
+ class FileSystemImportResolver(BaseImportResolver):
68
+ pass
69
+
70
+
71
+ class DictImportResolver(BaseImportResolver):
72
+ content: Dict[str, str]
73
+
74
+
62
75
  class EnvironmentOptions(BaseModel):
63
76
  allow_duplicate_declaration: bool = True
77
+ import_resolver: BaseImportResolver = Field(
78
+ default_factory=FileSystemImportResolver
79
+ )
64
80
 
65
81
 
66
82
  class EnvironmentConceptDict(dict):
@@ -191,14 +207,14 @@ class Environment(BaseModel):
191
207
  EnvironmentDatasourceDict, PlainValidator(validate_datasources)
192
208
  ] = Field(default_factory=EnvironmentDatasourceDict)
193
209
  functions: Dict[str, Callable[..., Any]] = Field(default_factory=dict)
194
- data_types: Dict[str, DataType] = Field(default_factory=dict)
210
+ data_types: Dict[str, CustomType] = Field(default_factory=dict)
195
211
  named_statements: Dict[str, SelectLineage] = Field(default_factory=dict)
196
212
  imports: Dict[str, list[Import]] = Field(
197
213
  default_factory=lambda: defaultdict(list) # type: ignore
198
214
  )
199
215
  namespace: str = DEFAULT_NAMESPACE
200
216
  working_path: str | Path = Field(default_factory=lambda: os.getcwd())
201
- environment_config: EnvironmentOptions = Field(default_factory=EnvironmentOptions)
217
+ config: EnvironmentOptions = Field(default_factory=EnvironmentOptions)
202
218
  version: str = Field(default_factory=get_version)
203
219
  cte_name_map: Dict[str, str] = Field(default_factory=dict)
204
220
  materialized_concepts: set[str] = Field(default_factory=set)
@@ -233,7 +249,7 @@ class Environment(BaseModel):
233
249
  imports=dict(self.imports),
234
250
  namespace=self.namespace,
235
251
  working_path=self.working_path,
236
- environment_config=self.environment_config,
252
+ environment_config=self.config,
237
253
  version=self.version,
238
254
  cte_name_map=dict(self.cte_name_map),
239
255
  materialized_concepts=set(self.materialized_concepts),
@@ -341,7 +357,7 @@ class Environment(BaseModel):
341
357
 
342
358
  return None
343
359
 
344
- if existing and self.environment_config.allow_duplicate_declaration:
360
+ if existing and self.config.allow_duplicate_declaration:
345
361
  if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
346
362
  return handle_persist()
347
363
  return
@@ -92,7 +92,7 @@ class GroupNode(StrategyNode):
92
92
  comp_grain += source.grain
93
93
  for x in source.output_concepts:
94
94
  concept_map[x.address] = x
95
- lookups = [
95
+ lookups: list[BuildConcept | str] = [
96
96
  concept_map[x] if x in concept_map else x for x in comp_grain.components
97
97
  ]
98
98
  comp_grain = BuildGrain.from_concepts(lookups, environment=environment)
@@ -15,8 +15,11 @@ from trilogy.core.enums import (
15
15
  from trilogy.core.models.author import (
16
16
  AggregateWrapper,
17
17
  AlignClause,
18
+ ArgBinding,
18
19
  Concept,
19
20
  ConceptRef,
21
+ CustomType,
22
+ Expr,
20
23
  FilterItem,
21
24
  Function,
22
25
  Grain,
@@ -413,3 +416,13 @@ class ConceptDeclarationStatement(HasUUID, BaseModel):
413
416
 
414
417
  class ConceptDerivationStatement(BaseModel):
415
418
  concept: Concept
419
+
420
+
421
+ class TypeDeclaration(BaseModel):
422
+ type: CustomType
423
+
424
+
425
+ class FunctionDeclaration(BaseModel):
426
+ name: str
427
+ args: list[ArgBinding]
428
+ expr: Expr
@@ -184,6 +184,7 @@ FUNCTION_MAP = {
184
184
  FunctionType.LOWER: lambda x: f"LOWER({x[0]}) ",
185
185
  FunctionType.SUBSTRING: lambda x: f"SUBSTRING({x[0]},{x[1]},{x[2]})",
186
186
  FunctionType.STRPOS: lambda x: f"STRPOS({x[0]},{x[1]})",
187
+ FunctionType.CONTAINS: lambda x: f"CONTAINS({x[0]},{x[1]})",
187
188
  # FunctionType.NOT_LIKE: lambda x: f" CASE WHEN {x[0]} like {x[1]} THEN 0 ELSE 1 END",
188
189
  # date types
189
190
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
@@ -42,10 +42,9 @@ CREATE OR REPLACE TABLE {{ output.address.location }} AS
42
42
  {% endif %}{%- if ctes %}
43
43
  WITH {% for cte in ctes %}
44
44
  {{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% endfor %}{% endif %}
45
- {%- if full_select %}
45
+ {%- if full_select -%}
46
46
  {{full_select}}
47
- {% else -%}
48
-
47
+ {%- else -%}
49
48
  SELECT
50
49
  {%- for select in select_columns %}
51
50
  {{ select }}{% if not loop.last %},{% endif %}{% endfor %}
@@ -57,8 +57,8 @@ CREATE OR REPLACE TABLE {{ output.address.location }} AS
57
57
  WITH {% for cte in ctes %}
58
58
  {{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% endfor %}{% endif %}
59
59
  {%- if full_select -%}{{full_select}}
60
- {%- else -%}{%- if comment %}
61
- -- {{ comment }}{% endif %}
60
+ {%- else -%}{%- if comment -%}
61
+ -- {{ comment }}{%- endif -%}
62
62
  SELECT
63
63
  {%- for select in select_columns %}
64
64
  {{ select }}{% if not loop.last %},{% endif %}{% endfor %}
@@ -41,6 +41,11 @@ class Dialects(Enum):
41
41
  return cls.DUCK_DB
42
42
  return super()._missing_(value)
43
43
 
44
+ def default_renderer(self, conf=None, _engine_factory: Callable = default_factory):
45
+ from trilogy.render import get_dialect_generator
46
+
47
+ return get_dialect_generator(self)
48
+
44
49
  def default_engine(self, conf=None, _engine_factory: Callable = default_factory):
45
50
  if self == Dialects.BIGQUERY:
46
51
  from google.auth import default
@@ -50,7 +50,7 @@ WITH {% for cte in ctes %}
50
50
  {{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% endfor %}{% endif %}
51
51
  {%- if full_select -%}
52
52
  {{full_select}}
53
- {%- else %}
53
+ {%- else -%}
54
54
  SELECT
55
55
  {%- for select in select_columns %}
56
56
  {{ select }}{% if not loop.last %},{% endif %}{% endfor %}
@@ -42,7 +42,7 @@ WITH {% for cte in ctes %}
42
42
  {{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% endfor %}{% endif %}
43
43
  {%- if full_select -%}{{full_select}}
44
44
  {%- else -%}{%- if comment %}
45
- -- {{ comment }}{% endif %}
45
+ -- {{ comment }}{%- endif -%}
46
46
  SELECT
47
47
  {%- if limit is not none %}
48
48
  TOP {{ limit }}{% endif %}
@@ -36,6 +36,7 @@ from trilogy.dialect.enums import Dialects
36
36
  from trilogy.engine import ExecutionEngine
37
37
  from trilogy.hooks.base_hook import BaseHook
38
38
  from trilogy.parser import parse_text
39
+ from trilogy.render import get_dialect_generator
39
40
 
40
41
 
41
42
  class ResultProtocol(Protocol):
@@ -82,40 +83,7 @@ class Executor(object):
82
83
  self.generator: BaseDialect
83
84
  self.logger = logger
84
85
  self.hooks = hooks
85
- if self.dialect == Dialects.BIGQUERY:
86
- from trilogy.dialect.bigquery import BigqueryDialect
87
-
88
- self.generator = BigqueryDialect()
89
- elif self.dialect == Dialects.SQL_SERVER:
90
- from trilogy.dialect.sql_server import SqlServerDialect
91
-
92
- self.generator = SqlServerDialect()
93
- elif self.dialect == Dialects.DUCK_DB:
94
- from trilogy.dialect.duckdb import DuckDBDialect
95
-
96
- self.generator = DuckDBDialect()
97
- elif self.dialect == Dialects.PRESTO:
98
- from trilogy.dialect.presto import PrestoDialect
99
-
100
- self.generator = PrestoDialect()
101
- elif self.dialect == Dialects.TRINO:
102
- from trilogy.dialect.presto import TrinoDialect
103
-
104
- self.generator = TrinoDialect()
105
- elif self.dialect == Dialects.POSTGRES:
106
- from trilogy.dialect.postgres import PostgresDialect
107
-
108
- self.generator = PostgresDialect()
109
- elif self.dialect == Dialects.SNOWFLAKE:
110
- from trilogy.dialect.snowflake import SnowflakeDialect
111
-
112
- self.generator = SnowflakeDialect()
113
- elif self.dialect == Dialects.DATAFRAME:
114
- from trilogy.dialect.dataframe import DataframeDialect
115
-
116
- self.generator = DataframeDialect()
117
- else:
118
- raise ValueError(f"Unsupported dialect {self.dialect}")
86
+ self.generator = get_dialect_generator(self.dialect)
119
87
  self.connection = self.engine.connect()
120
88
  # TODO: make generic
121
89
  if self.dialect == Dialects.DATAFRAME: