pytrilogy 0.0.2.20__tar.gz → 0.0.2.22__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.
- {pytrilogy-0.0.2.20/pytrilogy.egg-info → pytrilogy-0.0.2.22}/PKG-INFO +17 -11
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/README.md +16 -10
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22/pytrilogy.egg-info}/PKG-INFO +17 -11
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/SOURCES.txt +1 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_models.py +6 -2
- pytrilogy-0.0.2.22/tests/test_show.py +58 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/constants.py +1 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/constants.py +1 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/enums.py +4 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/env_processor.py +4 -2
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/ergonomics.py +11 -4
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/models.py +103 -28
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/concept_strategies_v3.py +0 -1
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/node_merge_node.py +19 -6
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/rowset_node.py +2 -2
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/select_node.py +2 -1
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/query_processor.py +8 -4
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/base.py +2 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/executor.py +14 -1
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/common.py +16 -2
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/parse_engine.py +1 -34
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/render.py +2 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/trilogy.lark +35 -18
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pyproject.toml +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/setup.cfg +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/setup.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_select.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/utility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.2.
|
|
3
|
+
Version: 0.0.2.22
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -145,7 +145,7 @@ Run the following from the directory the file is in.
|
|
|
145
145
|
trilogy run hello.trilogy duckdb
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-

|
|
149
149
|
|
|
150
150
|
## Backends
|
|
151
151
|
|
|
@@ -158,9 +158,9 @@ The current Trilogy implementation supports these backends:
|
|
|
158
158
|
|
|
159
159
|
## Basic Example - Python
|
|
160
160
|
|
|
161
|
-
Trilogy can be run directly in python.
|
|
161
|
+
Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
|
|
162
162
|
|
|
163
|
-
A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console)
|
|
163
|
+
A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console).
|
|
164
164
|
|
|
165
165
|
```python
|
|
166
166
|
|
|
@@ -224,7 +224,7 @@ and second the dialect to run.
|
|
|
224
224
|
To pass arguments to a backend, append additional --<option> flags after specifying the dialect.
|
|
225
225
|
|
|
226
226
|
Example:
|
|
227
|
-
`trilogy run key
|
|
227
|
+
`trilogy run "key x int; datasource test_source ( i:x) grain(in) address test; select x;" duckdb --path <path/to/database>`
|
|
228
228
|
|
|
229
229
|
### Bigquery Args
|
|
230
230
|
N/A, only supports default auth. In python you can pass in a custom client.
|
|
@@ -267,7 +267,7 @@ Clone repository and install requirements.txt and requirements-test.txt.
|
|
|
267
267
|
Please open an issue first to discuss what you would like to change, and then create a PR against that issue.
|
|
268
268
|
|
|
269
269
|
## Similar in space
|
|
270
|
-
Trilogy combines two aspects; a semantic layer and a query language.
|
|
270
|
+
Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
|
|
271
271
|
|
|
272
272
|
Python "semantic layers" are tools for defining data access to a warehouse in a more abstract way.
|
|
273
273
|
|
|
@@ -284,20 +284,26 @@ but all are worth checking out. Please open PRs/comment for anything missed!
|
|
|
284
284
|
|
|
285
285
|
#### IMPORT
|
|
286
286
|
|
|
287
|
-
`import
|
|
287
|
+
`import [path] as [alias];`
|
|
288
288
|
|
|
289
289
|
#### CONCEPT
|
|
290
290
|
|
|
291
|
-
Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval
|
|
291
|
+
Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval | list<[type]> | map<[type], [type]> | struct<name:[type], name:[type]>`;
|
|
292
292
|
|
|
293
293
|
Key:
|
|
294
|
-
`key
|
|
294
|
+
`key [name] [type];`
|
|
295
295
|
|
|
296
296
|
Property:
|
|
297
|
-
`property
|
|
297
|
+
`property [key>].[name] [type];`
|
|
298
|
+
`property x.y int;`
|
|
299
|
+
or
|
|
300
|
+
`property <[key](,[key])?>.<name> [type];`
|
|
301
|
+
`property <x,y>.z int;`
|
|
302
|
+
|
|
298
303
|
|
|
299
304
|
Transformation:
|
|
300
|
-
`auto
|
|
305
|
+
`auto [name] <- [expression];`
|
|
306
|
+
`auto x <- y + 1;`
|
|
301
307
|
|
|
302
308
|
#### DATASOURCE
|
|
303
309
|
```sql
|
|
@@ -116,7 +116,7 @@ Run the following from the directory the file is in.
|
|
|
116
116
|
trilogy run hello.trilogy duckdb
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-

|
|
120
120
|
|
|
121
121
|
## Backends
|
|
122
122
|
|
|
@@ -129,9 +129,9 @@ The current Trilogy implementation supports these backends:
|
|
|
129
129
|
|
|
130
130
|
## Basic Example - Python
|
|
131
131
|
|
|
132
|
-
Trilogy can be run directly in python.
|
|
132
|
+
Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
|
|
133
133
|
|
|
134
|
-
A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console)
|
|
134
|
+
A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console).
|
|
135
135
|
|
|
136
136
|
```python
|
|
137
137
|
|
|
@@ -195,7 +195,7 @@ and second the dialect to run.
|
|
|
195
195
|
To pass arguments to a backend, append additional --<option> flags after specifying the dialect.
|
|
196
196
|
|
|
197
197
|
Example:
|
|
198
|
-
`trilogy run key
|
|
198
|
+
`trilogy run "key x int; datasource test_source ( i:x) grain(in) address test; select x;" duckdb --path <path/to/database>`
|
|
199
199
|
|
|
200
200
|
### Bigquery Args
|
|
201
201
|
N/A, only supports default auth. In python you can pass in a custom client.
|
|
@@ -238,7 +238,7 @@ Clone repository and install requirements.txt and requirements-test.txt.
|
|
|
238
238
|
Please open an issue first to discuss what you would like to change, and then create a PR against that issue.
|
|
239
239
|
|
|
240
240
|
## Similar in space
|
|
241
|
-
Trilogy combines two aspects; a semantic layer and a query language.
|
|
241
|
+
Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
|
|
242
242
|
|
|
243
243
|
Python "semantic layers" are tools for defining data access to a warehouse in a more abstract way.
|
|
244
244
|
|
|
@@ -255,20 +255,26 @@ but all are worth checking out. Please open PRs/comment for anything missed!
|
|
|
255
255
|
|
|
256
256
|
#### IMPORT
|
|
257
257
|
|
|
258
|
-
`import
|
|
258
|
+
`import [path] as [alias];`
|
|
259
259
|
|
|
260
260
|
#### CONCEPT
|
|
261
261
|
|
|
262
|
-
Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval
|
|
262
|
+
Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval | list<[type]> | map<[type], [type]> | struct<name:[type], name:[type]>`;
|
|
263
263
|
|
|
264
264
|
Key:
|
|
265
|
-
`key
|
|
265
|
+
`key [name] [type];`
|
|
266
266
|
|
|
267
267
|
Property:
|
|
268
|
-
`property
|
|
268
|
+
`property [key>].[name] [type];`
|
|
269
|
+
`property x.y int;`
|
|
270
|
+
or
|
|
271
|
+
`property <[key](,[key])?>.<name> [type];`
|
|
272
|
+
`property <x,y>.z int;`
|
|
273
|
+
|
|
269
274
|
|
|
270
275
|
Transformation:
|
|
271
|
-
`auto
|
|
276
|
+
`auto [name] <- [expression];`
|
|
277
|
+
`auto x <- y + 1;`
|
|
272
278
|
|
|
273
279
|
#### DATASOURCE
|
|
274
280
|
```sql
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.2.
|
|
3
|
+
Version: 0.0.2.22
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -145,7 +145,7 @@ Run the following from the directory the file is in.
|
|
|
145
145
|
trilogy run hello.trilogy duckdb
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-

|
|
149
149
|
|
|
150
150
|
## Backends
|
|
151
151
|
|
|
@@ -158,9 +158,9 @@ The current Trilogy implementation supports these backends:
|
|
|
158
158
|
|
|
159
159
|
## Basic Example - Python
|
|
160
160
|
|
|
161
|
-
Trilogy can be run directly in python.
|
|
161
|
+
Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
|
|
162
162
|
|
|
163
|
-
A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console)
|
|
163
|
+
A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console).
|
|
164
164
|
|
|
165
165
|
```python
|
|
166
166
|
|
|
@@ -224,7 +224,7 @@ and second the dialect to run.
|
|
|
224
224
|
To pass arguments to a backend, append additional --<option> flags after specifying the dialect.
|
|
225
225
|
|
|
226
226
|
Example:
|
|
227
|
-
`trilogy run key
|
|
227
|
+
`trilogy run "key x int; datasource test_source ( i:x) grain(in) address test; select x;" duckdb --path <path/to/database>`
|
|
228
228
|
|
|
229
229
|
### Bigquery Args
|
|
230
230
|
N/A, only supports default auth. In python you can pass in a custom client.
|
|
@@ -267,7 +267,7 @@ Clone repository and install requirements.txt and requirements-test.txt.
|
|
|
267
267
|
Please open an issue first to discuss what you would like to change, and then create a PR against that issue.
|
|
268
268
|
|
|
269
269
|
## Similar in space
|
|
270
|
-
Trilogy combines two aspects; a semantic layer and a query language.
|
|
270
|
+
Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
|
|
271
271
|
|
|
272
272
|
Python "semantic layers" are tools for defining data access to a warehouse in a more abstract way.
|
|
273
273
|
|
|
@@ -284,20 +284,26 @@ but all are worth checking out. Please open PRs/comment for anything missed!
|
|
|
284
284
|
|
|
285
285
|
#### IMPORT
|
|
286
286
|
|
|
287
|
-
`import
|
|
287
|
+
`import [path] as [alias];`
|
|
288
288
|
|
|
289
289
|
#### CONCEPT
|
|
290
290
|
|
|
291
|
-
Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval
|
|
291
|
+
Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval | list<[type]> | map<[type], [type]> | struct<name:[type], name:[type]>`;
|
|
292
292
|
|
|
293
293
|
Key:
|
|
294
|
-
`key
|
|
294
|
+
`key [name] [type];`
|
|
295
295
|
|
|
296
296
|
Property:
|
|
297
|
-
`property
|
|
297
|
+
`property [key>].[name] [type];`
|
|
298
|
+
`property x.y int;`
|
|
299
|
+
or
|
|
300
|
+
`property <[key](,[key])?>.<name> [type];`
|
|
301
|
+
`property <x,y>.z int;`
|
|
302
|
+
|
|
298
303
|
|
|
299
304
|
Transformation:
|
|
300
|
-
`auto
|
|
305
|
+
`auto [name] <- [expression];`
|
|
306
|
+
`auto x <- y + 1;`
|
|
301
307
|
|
|
302
308
|
#### DATASOURCE
|
|
303
309
|
```sql
|
|
@@ -100,12 +100,16 @@ def test_conditional(test_environment, test_environment_graph):
|
|
|
100
100
|
merged = condition_a + condition_b
|
|
101
101
|
assert merged == condition_a
|
|
102
102
|
|
|
103
|
-
test_concept_two =
|
|
103
|
+
test_concept_two = [
|
|
104
|
+
x
|
|
105
|
+
for x in test_environment.concepts.values()
|
|
106
|
+
if x.address != test_concept.address
|
|
107
|
+
].pop()
|
|
104
108
|
condition_c = Conditional(
|
|
105
109
|
left=test_concept, right=test_concept_two, operator=BooleanOperator.AND
|
|
106
110
|
)
|
|
107
111
|
merged_two = condition_a + condition_c
|
|
108
|
-
assert merged_two.left == condition_a
|
|
112
|
+
assert merged_two.left == condition_a, f"{str(merged_two.left), str(condition_a)}"
|
|
109
113
|
assert merged_two.right == condition_c
|
|
110
114
|
assert merged_two.operator == BooleanOperator.AND
|
|
111
115
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from trilogy.core.models import ShowStatement
|
|
2
|
+
from trilogy.core.query_processor import process_query
|
|
3
|
+
from trilogy import Dialects
|
|
4
|
+
from trilogy.parser import parse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_show_bigquery():
|
|
8
|
+
declarations = """
|
|
9
|
+
key user_id int metadata(description="the description");
|
|
10
|
+
property user_id.display_name string metadata(description="The display name ");
|
|
11
|
+
property user_id.about_me string metadata(description="User provided description");
|
|
12
|
+
key post_id int;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
datasource posts (
|
|
16
|
+
user_id: user_id,
|
|
17
|
+
id: post_id
|
|
18
|
+
)
|
|
19
|
+
grain (post_id)
|
|
20
|
+
address bigquery-public-data.stackoverflow.post_history
|
|
21
|
+
;
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
datasource users (
|
|
25
|
+
id: user_id,
|
|
26
|
+
display_name: display_name,
|
|
27
|
+
about_me: about_me,
|
|
28
|
+
)
|
|
29
|
+
grain (user_id)
|
|
30
|
+
address bigquery-public-data.stackoverflow.users
|
|
31
|
+
;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
env, parsed = parse(declarations)
|
|
36
|
+
|
|
37
|
+
q1 = """
|
|
38
|
+
metric post_count<- count(post_id);
|
|
39
|
+
metric distinct_post_count <- count_distinct(post_id);
|
|
40
|
+
|
|
41
|
+
metric user_count <- count(user_id);
|
|
42
|
+
|
|
43
|
+
show select
|
|
44
|
+
post_count,
|
|
45
|
+
distinct_post_count,
|
|
46
|
+
user_count
|
|
47
|
+
;"""
|
|
48
|
+
env, parsed = parse(q1, environment=env)
|
|
49
|
+
select: ShowStatement = parsed[-1]
|
|
50
|
+
|
|
51
|
+
query = (
|
|
52
|
+
Dialects.DUCK_DB.default_executor(environment=env)
|
|
53
|
+
.execute_query(select)
|
|
54
|
+
.fetchall()
|
|
55
|
+
)
|
|
56
|
+
assert "FULL JOIN wakeful on 1=1" in query[0]["__preql_internal_query_text"], query[
|
|
57
|
+
0
|
|
58
|
+
]["__preql_internal_query_text"]
|
|
@@ -44,6 +44,7 @@ class Comments:
|
|
|
44
44
|
class Config:
|
|
45
45
|
strict_mode: bool = True
|
|
46
46
|
human_identifiers: bool = True
|
|
47
|
+
randomize_cte_names: bool = False
|
|
47
48
|
validate_missing: bool = True
|
|
48
49
|
comments: Comments = field(default_factory=Comments)
|
|
49
50
|
optimizations: Optimizations = field(default_factory=Optimizations)
|
|
@@ -12,6 +12,8 @@ class UnnestMode(Enum):
|
|
|
12
12
|
|
|
13
13
|
class ConceptSource(Enum):
|
|
14
14
|
MANUAL = "manual"
|
|
15
|
+
CTE = "cte"
|
|
16
|
+
PERSIST_STATEMENT = "persist_statement"
|
|
15
17
|
AUTO_DERIVED = "auto_derived"
|
|
16
18
|
|
|
17
19
|
|
|
@@ -205,6 +207,8 @@ class FunctionClass(Enum):
|
|
|
205
207
|
FunctionType.CURRENT_DATETIME,
|
|
206
208
|
]
|
|
207
209
|
|
|
210
|
+
ONE_TO_MANY = [FunctionType.UNNEST]
|
|
211
|
+
|
|
208
212
|
|
|
209
213
|
class Boolean(Enum):
|
|
210
214
|
TRUE = "true"
|
|
@@ -10,9 +10,11 @@ def add_concept(concept: Concept, g: ReferenceGraph):
|
|
|
10
10
|
g.add_node(concept)
|
|
11
11
|
# if we have sources, recursively add them
|
|
12
12
|
node_name = concept_to_node(concept)
|
|
13
|
-
if concept.
|
|
14
|
-
for source in concept.
|
|
13
|
+
if concept.concept_arguments:
|
|
14
|
+
for source in concept.concept_arguments:
|
|
15
15
|
generic = source.with_default_grain()
|
|
16
|
+
add_concept(generic, g)
|
|
17
|
+
|
|
16
18
|
g.add_edge(generic, node_name)
|
|
17
19
|
for _, pseudonym in concept.pseudonyms.items():
|
|
18
20
|
pseudonym = pseudonym.with_default_grain()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from trilogy.constants import CONFIG
|
|
2
|
+
|
|
1
3
|
# source: https://github.com/aaronbassett/Pass-phrase
|
|
2
4
|
CTE_NAMES = """quizzical
|
|
3
5
|
highfalutin
|
|
@@ -103,8 +105,6 @@ mandrill
|
|
|
103
105
|
marlin
|
|
104
106
|
monitor
|
|
105
107
|
ocelot
|
|
106
|
-
osprey
|
|
107
|
-
owl
|
|
108
108
|
petrel
|
|
109
109
|
python
|
|
110
110
|
ray
|
|
@@ -132,7 +132,6 @@ cuckoo
|
|
|
132
132
|
darter
|
|
133
133
|
dove
|
|
134
134
|
duck
|
|
135
|
-
eagle
|
|
136
135
|
falcon
|
|
137
136
|
finch
|
|
138
137
|
flamingo
|
|
@@ -184,4 +183,12 @@ warbler""".split(
|
|
|
184
183
|
"\n"
|
|
185
184
|
)
|
|
186
185
|
|
|
187
|
-
|
|
186
|
+
|
|
187
|
+
def generate_cte_names():
|
|
188
|
+
if CONFIG.randomize_cte_names:
|
|
189
|
+
from random import shuffle
|
|
190
|
+
|
|
191
|
+
new = [*CTE_NAMES]
|
|
192
|
+
shuffle(new)
|
|
193
|
+
return new
|
|
194
|
+
return CTE_NAMES
|
|
@@ -44,6 +44,7 @@ from trilogy.core.constants import (
|
|
|
44
44
|
ALL_ROWS_CONCEPT,
|
|
45
45
|
INTERNAL_NAMESPACE,
|
|
46
46
|
CONSTANT_DATASET,
|
|
47
|
+
PERSISTED_CONCEPT_PREFIX,
|
|
47
48
|
)
|
|
48
49
|
from trilogy.core.enums import (
|
|
49
50
|
InfiniteFunctionArgs,
|
|
@@ -912,10 +913,10 @@ class Grain(Mergeable, BaseModel):
|
|
|
912
913
|
)
|
|
913
914
|
|
|
914
915
|
@cached_property
|
|
915
|
-
def set(self):
|
|
916
|
+
def set(self) -> set[str]:
|
|
916
917
|
base = []
|
|
917
918
|
for x in self.components_copy:
|
|
918
|
-
if x.
|
|
919
|
+
if isinstance(x.lineage, RowsetItem):
|
|
919
920
|
base.append(x.lineage.content.address)
|
|
920
921
|
else:
|
|
921
922
|
base.append(x.address)
|
|
@@ -3131,6 +3132,12 @@ class EnvironmentConceptDict(dict):
|
|
|
3131
3132
|
def values(self) -> ValuesView[Concept]: # type: ignore
|
|
3132
3133
|
return super().values()
|
|
3133
3134
|
|
|
3135
|
+
def get(self, key: str, default: Concept | None = None) -> Concept | None: # type: ignore
|
|
3136
|
+
try:
|
|
3137
|
+
return self.__getitem__(key)
|
|
3138
|
+
except UndefinedConceptException:
|
|
3139
|
+
return default
|
|
3140
|
+
|
|
3134
3141
|
def __getitem__(
|
|
3135
3142
|
self, key, line_no: int | None = None
|
|
3136
3143
|
) -> Concept | UndefinedConcept:
|
|
@@ -3257,24 +3264,62 @@ class Environment(BaseModel):
|
|
|
3257
3264
|
for datasource in self.datasources.values():
|
|
3258
3265
|
for concept in datasource.output_concepts:
|
|
3259
3266
|
concrete_addresses.add(concept.address)
|
|
3260
|
-
self.materialized_concepts =
|
|
3261
|
-
c for c in self.concepts.values() if c.address in concrete_addresses
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
+
self.materialized_concepts = unique(
|
|
3268
|
+
[c for c in self.concepts.values() if c.address in concrete_addresses]
|
|
3269
|
+
+ [
|
|
3270
|
+
c
|
|
3271
|
+
for c in self.alias_origin_lookup.values()
|
|
3272
|
+
if c.address in concrete_addresses
|
|
3273
|
+
],
|
|
3274
|
+
"address",
|
|
3275
|
+
)
|
|
3267
3276
|
|
|
3268
|
-
def validate_concept(self,
|
|
3277
|
+
def validate_concept(self, new_concept: Concept, meta: Meta | None = None):
|
|
3278
|
+
lookup = new_concept.address
|
|
3269
3279
|
existing: Concept = self.concepts.get(lookup) # type: ignore
|
|
3270
3280
|
if not existing:
|
|
3271
3281
|
return
|
|
3272
|
-
|
|
3282
|
+
|
|
3283
|
+
def handle_persist():
|
|
3284
|
+
deriv_lookup = (
|
|
3285
|
+
f"{existing.namespace}.{PERSISTED_CONCEPT_PREFIX}_{existing.name}"
|
|
3286
|
+
)
|
|
3287
|
+
|
|
3288
|
+
alt_source = self.alias_origin_lookup.get(deriv_lookup)
|
|
3289
|
+
if not alt_source:
|
|
3290
|
+
return None
|
|
3291
|
+
# if the new concept binding has no lineage
|
|
3292
|
+
# nothing to cause us to think a persist binding
|
|
3293
|
+
# needs to be invalidated
|
|
3294
|
+
if not new_concept.lineage:
|
|
3295
|
+
return existing
|
|
3296
|
+
if str(alt_source.lineage) == str(new_concept.lineage):
|
|
3297
|
+
logger.info(
|
|
3298
|
+
f"Persisted concept {existing.address} matched redeclaration, keeping current persistence binding."
|
|
3299
|
+
)
|
|
3300
|
+
return existing
|
|
3301
|
+
logger.warning(
|
|
3302
|
+
f"Persisted concept {existing.address} lineage {str(alt_source.lineage)} did not match redeclaration {str(new_concept.lineage)}, overwriting and invalidating persist binding."
|
|
3303
|
+
)
|
|
3304
|
+
for k, datasource in self.datasources.items():
|
|
3305
|
+
if existing.address in datasource.output_concepts:
|
|
3306
|
+
datasource.columns = [
|
|
3307
|
+
x
|
|
3308
|
+
for x in datasource.columns
|
|
3309
|
+
if x.concept.address != existing.address
|
|
3310
|
+
]
|
|
3311
|
+
return None
|
|
3312
|
+
|
|
3313
|
+
if existing and self.environment_config.allow_duplicate_declaration:
|
|
3314
|
+
if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
|
|
3315
|
+
return handle_persist()
|
|
3273
3316
|
return
|
|
3274
3317
|
elif existing.metadata:
|
|
3318
|
+
if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
|
|
3319
|
+
return handle_persist()
|
|
3275
3320
|
# if the existing concept is auto derived, we can overwrite it
|
|
3276
3321
|
if existing.metadata.concept_source == ConceptSource.AUTO_DERIVED:
|
|
3277
|
-
return
|
|
3322
|
+
return None
|
|
3278
3323
|
elif meta and existing.metadata:
|
|
3279
3324
|
raise ValueError(
|
|
3280
3325
|
f"Assignment to concept '{lookup}' on line {meta.line} is a duplicate"
|
|
@@ -3390,11 +3435,12 @@ class Environment(BaseModel):
|
|
|
3390
3435
|
_ignore_cache: bool = False,
|
|
3391
3436
|
):
|
|
3392
3437
|
if not force:
|
|
3393
|
-
self.validate_concept(concept
|
|
3438
|
+
existing = self.validate_concept(concept, meta=meta)
|
|
3439
|
+
if existing:
|
|
3440
|
+
concept = existing
|
|
3394
3441
|
if concept.namespace == DEFAULT_NAMESPACE:
|
|
3395
3442
|
self.concepts[concept.name] = concept
|
|
3396
|
-
|
|
3397
|
-
self.concepts[concept.address] = concept
|
|
3443
|
+
self.concepts[concept.address] = concept
|
|
3398
3444
|
from trilogy.core.environment_helpers import generate_related_concepts
|
|
3399
3445
|
|
|
3400
3446
|
generate_related_concepts(concept, self, meta=meta, add_derived=add_derived)
|
|
@@ -3408,21 +3454,42 @@ class Environment(BaseModel):
|
|
|
3408
3454
|
meta: Meta | None = None,
|
|
3409
3455
|
_ignore_cache: bool = False,
|
|
3410
3456
|
):
|
|
3411
|
-
|
|
3412
3457
|
self.datasources[datasource.env_label] = datasource
|
|
3413
|
-
for
|
|
3414
|
-
current_concept = column.concept
|
|
3458
|
+
for current_concept in datasource.output_concepts:
|
|
3415
3459
|
current_derivation = current_concept.derivation
|
|
3460
|
+
# TODO: refine this section;
|
|
3461
|
+
# too hacky for maintainability
|
|
3416
3462
|
if current_derivation not in (PurposeLineage.ROOT, PurposeLineage.CONSTANT):
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3463
|
+
persisted = f"{PERSISTED_CONCEPT_PREFIX}_" + current_concept.name
|
|
3464
|
+
# override the current concept source to reflect that it's now coming from a datasource
|
|
3465
|
+
if (
|
|
3466
|
+
current_concept.metadata.concept_source
|
|
3467
|
+
!= ConceptSource.PERSIST_STATEMENT
|
|
3468
|
+
):
|
|
3469
|
+
new_concept = current_concept.model_copy(deep=True)
|
|
3470
|
+
new_concept.set_name(persisted)
|
|
3471
|
+
self.add_concept(
|
|
3472
|
+
new_concept, meta=meta, force=True, _ignore_cache=True
|
|
3473
|
+
)
|
|
3474
|
+
current_concept.metadata.concept_source = (
|
|
3475
|
+
ConceptSource.PERSIST_STATEMENT
|
|
3476
|
+
)
|
|
3477
|
+
# remove the associated lineage
|
|
3478
|
+
# to make this a root for discovery purposes
|
|
3479
|
+
# as it now "exists" in a table
|
|
3480
|
+
current_concept.lineage = None
|
|
3481
|
+
current_concept = current_concept.with_default_grain()
|
|
3482
|
+
self.add_concept(
|
|
3483
|
+
current_concept, meta=meta, force=True, _ignore_cache=True
|
|
3484
|
+
)
|
|
3485
|
+
self.merge_concept(new_concept, current_concept, [])
|
|
3486
|
+
else:
|
|
3487
|
+
self.add_concept(current_concept, meta=meta, _ignore_cache=True)
|
|
3488
|
+
|
|
3489
|
+
# else:
|
|
3490
|
+
# self.add_concept(
|
|
3491
|
+
# current_concept, meta=meta, _ignore_cache=True
|
|
3492
|
+
# )
|
|
3426
3493
|
if not _ignore_cache:
|
|
3427
3494
|
self.gen_concept_list_caches()
|
|
3428
3495
|
return datasource
|
|
@@ -4230,6 +4297,9 @@ class RowsetDerivationStatement(Namespaced, BaseModel):
|
|
|
4230
4297
|
def __repr__(self):
|
|
4231
4298
|
return f"RowsetDerivation<{str(self.select)}>"
|
|
4232
4299
|
|
|
4300
|
+
def __str__(self):
|
|
4301
|
+
return self.__repr__()
|
|
4302
|
+
|
|
4233
4303
|
@property
|
|
4234
4304
|
def derived_concepts(self) -> List[Concept]:
|
|
4235
4305
|
output: list[Concept] = []
|
|
@@ -4248,7 +4318,8 @@ class RowsetDerivationStatement(Namespaced, BaseModel):
|
|
|
4248
4318
|
content=orig_concept, where=self.select.where_clause, rowset=self
|
|
4249
4319
|
),
|
|
4250
4320
|
grain=orig_concept.grain,
|
|
4251
|
-
metadata
|
|
4321
|
+
# TODO: add proper metadata
|
|
4322
|
+
metadata=Metadata(concept_source=ConceptSource.CTE),
|
|
4252
4323
|
namespace=(
|
|
4253
4324
|
f"{self.name}.{orig_concept.namespace}"
|
|
4254
4325
|
if orig_concept.namespace != self.namespace
|
|
@@ -4275,6 +4346,7 @@ class RowsetDerivationStatement(Namespaced, BaseModel):
|
|
|
4275
4346
|
components=[orig[c.address] for c in x.grain.components_copy]
|
|
4276
4347
|
)
|
|
4277
4348
|
else:
|
|
4349
|
+
|
|
4278
4350
|
x.grain = default_grain
|
|
4279
4351
|
return output
|
|
4280
4352
|
|
|
@@ -4300,6 +4372,9 @@ class RowsetItem(Mergeable, Namespaced, BaseModel):
|
|
|
4300
4372
|
f"<Rowset<{self.rowset.name}>: {str(self.content)} where {str(self.where)}>"
|
|
4301
4373
|
)
|
|
4302
4374
|
|
|
4375
|
+
def __str__(self):
|
|
4376
|
+
return self.__repr__()
|
|
4377
|
+
|
|
4303
4378
|
def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
|
|
4304
4379
|
return RowsetItem(
|
|
4305
4380
|
content=self.content.with_merge(source, target, modifiers),
|