jupyter-duckdb 1.2.0.3__tar.gz → 1.2.0.5__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.
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/PKG-INFO +37 -6
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/README.md +36 -5
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/kernel.py +81 -24
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommand.py +34 -10
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommandCallback.py +14 -3
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommandHandler.py +47 -6
- jupyter_duckdb-1.2.0.5/src/duckdb_kernel/magics/StringWrapper.py +3 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/DCParser.py +10 -7
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/LogicParser.py +6 -6
- jupyter_duckdb-1.2.0.5/src/duckdb_kernel/parser/ParserError.py +18 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/RAParser.py +12 -14
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/__init__.py +1 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/DCOperand.py +7 -4
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +30 -9
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/tokenizer/Token.py +24 -3
- jupyter_duckdb-1.2.0.5/src/duckdb_kernel/util/TestError.py +4 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/PKG-INFO +37 -6
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/SOURCES.txt +3 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/test/test_dc.py +96 -2
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/test/test_ra.py +2 -2
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/setup.cfg +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/setup.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/__main__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/Column.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/Connection.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/Constraint.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/DatabaseError.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/ForeignKey.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/Table.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/error/EmptyResultError.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/error/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/duckdb/Connection.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/duckdb/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/postgres/Connection.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/postgres/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/postgres/util.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/sqlite/Connection.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/sqlite/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/kernel.json +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RAElement.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RAOperand.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Cross.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Division.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Join.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/Projection.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/Rename.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/util/RenamableColumnList.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/util/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/util/ResultSetComparator.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/util/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/util/formatting.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/visualization/Drawer.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/visualization/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/top_level.txt +0 -0
- {jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/test/test_result_comparison.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: jupyter-duckdb
|
|
3
|
-
Version: 1.2.0.
|
|
3
|
+
Version: 1.2.0.5
|
|
4
4
|
Summary: a basic wrapper kernel for DuckDB
|
|
5
5
|
Home-page: https://github.com/erictroebs/jupyter-duckdb
|
|
6
6
|
Author: Eric Tröbs
|
|
@@ -32,10 +32,6 @@ This is a simple DuckDB wrapper kernel which accepts SQL as input, executes it
|
|
|
32
32
|
using a previously loaded DuckDB instance and formats the output as a table.
|
|
33
33
|
There are some magic commands that make teaching easier with this kernel.
|
|
34
34
|
|
|
35
|
-
## Quick Start
|
|
36
|
-
|
|
37
|
-
[](https://mybinder.org/v2/git/https%3A%2F%2Fdbgit.prakinf.tu-ilmenau.de%2Fertr8623%2Fjupyter-duckdb.git/master)
|
|
38
|
-
|
|
39
35
|
## Table of Contents
|
|
40
36
|
|
|
41
37
|
- [Setup](#setup)
|
|
@@ -85,6 +81,12 @@ Execute the following command to pull and run a prepared image.
|
|
|
85
81
|
docker run -p 8888:8888 troebs/jupyter-duckdb
|
|
86
82
|
```
|
|
87
83
|
|
|
84
|
+
There is also a second image. It contains an additional instance of PostgreSQL:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
docker run -p 8888:8888 troebs/jupyter-duckdb:postgresql
|
|
88
|
+
```
|
|
89
|
+
|
|
88
90
|
This image can also be used with JupyterHub and the
|
|
89
91
|
[DockerSpawner / SwarmSpawner](https://github.com/jupyterhub/dockerspawner)
|
|
90
92
|
and probably with the
|
|
@@ -138,6 +140,13 @@ Please note that `:memory:` is also a valid file path for DuckDB. The data is
|
|
|
138
140
|
then stored exclusively in the main memory. In combination with `CREATE`
|
|
139
141
|
and `OF` this makes it possible to work on a temporary copy in memory.
|
|
140
142
|
|
|
143
|
+
Although the name suggests otherwise, the kernel can also be used with other
|
|
144
|
+
databases:
|
|
145
|
+
- **SQLite** is automatically used as a fallback if the DuckDB dependency is
|
|
146
|
+
missing.
|
|
147
|
+
- To connect to a **PostgreSQL** instance, you need to specify a database URI
|
|
148
|
+
starting with `(postgresql|postgres|pgsql|psql|pg)://`.
|
|
149
|
+
|
|
141
150
|
### Schema Diagrams
|
|
142
151
|
|
|
143
152
|
The magic command `SCHEMA` can be used to create a simple schema diagram of the
|
|
@@ -153,6 +162,10 @@ representation requires more space, but can improve readability.
|
|
|
153
162
|
%SCHEMA TD
|
|
154
163
|
```
|
|
155
164
|
|
|
165
|
+
The optional argument `ONLY`, followed by one or more table names separated by a
|
|
166
|
+
comma, can be used to display only the named tables and all those connected with
|
|
167
|
+
a foreign key.
|
|
168
|
+
|
|
156
169
|
Graphviz (`dot` in PATH) is required to render schema diagrams.
|
|
157
170
|
|
|
158
171
|
### Number of Rows
|
|
@@ -234,6 +247,11 @@ UNION
|
|
|
234
247
|
SELECT 1, 'Name 1'
|
|
235
248
|
```
|
|
236
249
|
|
|
250
|
+
By default, failed tests will display an explanation, but the notebook will
|
|
251
|
+
continue to run. Set the `DUCKDB_TESTS_RAISE_EXCEPTION` environment variable to
|
|
252
|
+
`true` to raise an exception when a test fails. This can be useful for automated
|
|
253
|
+
testing in CI environments.
|
|
254
|
+
|
|
237
255
|
Disclaimer: The integrated testing is work-in-progress and thus subject to
|
|
238
256
|
potentially incompatible changes and enhancements.
|
|
239
257
|
|
|
@@ -244,7 +262,7 @@ magic command `RA` activates the relational algebra mode for a single cell:
|
|
|
244
262
|
|
|
245
263
|
```
|
|
246
264
|
%RA
|
|
247
|
-
π a, b (σ c = 1 (R))
|
|
265
|
+
π [a, b] (σ [c = 1] (R))
|
|
248
266
|
```
|
|
249
267
|
|
|
250
268
|
The supported operations are:
|
|
@@ -259,6 +277,9 @@ The supported operations are:
|
|
|
259
277
|
- Cross Product `×`
|
|
260
278
|
- Division `÷`
|
|
261
279
|
|
|
280
|
+
The optional flag `ANALYZE` can be used to add an execution diagram to the
|
|
281
|
+
output.
|
|
282
|
+
|
|
262
283
|
The Dockerfile also installs the Jupyter Lab plugin
|
|
263
284
|
[jupyter-ra-extension](https://pypi.org/project/jupyter-ra-extension/). It adds
|
|
264
285
|
the symbols mentioned above and some other supported symbols to the toolbar for
|
|
@@ -273,3 +294,13 @@ magic command `DC` activates the domain calculus mode for a single cell:
|
|
|
273
294
|
%DC
|
|
274
295
|
{ a, b | R(a, b, c) ∧ c = 1 }
|
|
275
296
|
```
|
|
297
|
+
|
|
298
|
+
### Automated Parser Selection
|
|
299
|
+
|
|
300
|
+
`%ALL_RA` or `%ALL_DC` enables the corresponding parser for all subsequently
|
|
301
|
+
executed cells.
|
|
302
|
+
|
|
303
|
+
If the magic command `%AUTO_PARSER` is added to a cell, a parser is
|
|
304
|
+
automatically selected. If `%GUESS_PARSER` is executed, the parser is
|
|
305
|
+
automatically selected for all subsequent cells.
|
|
306
|
+
|
|
@@ -4,10 +4,6 @@ This is a simple DuckDB wrapper kernel which accepts SQL as input, executes it
|
|
|
4
4
|
using a previously loaded DuckDB instance and formats the output as a table.
|
|
5
5
|
There are some magic commands that make teaching easier with this kernel.
|
|
6
6
|
|
|
7
|
-
## Quick Start
|
|
8
|
-
|
|
9
|
-
[](https://mybinder.org/v2/git/https%3A%2F%2Fdbgit.prakinf.tu-ilmenau.de%2Fertr8623%2Fjupyter-duckdb.git/master)
|
|
10
|
-
|
|
11
7
|
## Table of Contents
|
|
12
8
|
|
|
13
9
|
- [Setup](#setup)
|
|
@@ -57,6 +53,12 @@ Execute the following command to pull and run a prepared image.
|
|
|
57
53
|
docker run -p 8888:8888 troebs/jupyter-duckdb
|
|
58
54
|
```
|
|
59
55
|
|
|
56
|
+
There is also a second image. It contains an additional instance of PostgreSQL:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
docker run -p 8888:8888 troebs/jupyter-duckdb:postgresql
|
|
60
|
+
```
|
|
61
|
+
|
|
60
62
|
This image can also be used with JupyterHub and the
|
|
61
63
|
[DockerSpawner / SwarmSpawner](https://github.com/jupyterhub/dockerspawner)
|
|
62
64
|
and probably with the
|
|
@@ -110,6 +112,13 @@ Please note that `:memory:` is also a valid file path for DuckDB. The data is
|
|
|
110
112
|
then stored exclusively in the main memory. In combination with `CREATE`
|
|
111
113
|
and `OF` this makes it possible to work on a temporary copy in memory.
|
|
112
114
|
|
|
115
|
+
Although the name suggests otherwise, the kernel can also be used with other
|
|
116
|
+
databases:
|
|
117
|
+
- **SQLite** is automatically used as a fallback if the DuckDB dependency is
|
|
118
|
+
missing.
|
|
119
|
+
- To connect to a **PostgreSQL** instance, you need to specify a database URI
|
|
120
|
+
starting with `(postgresql|postgres|pgsql|psql|pg)://`.
|
|
121
|
+
|
|
113
122
|
### Schema Diagrams
|
|
114
123
|
|
|
115
124
|
The magic command `SCHEMA` can be used to create a simple schema diagram of the
|
|
@@ -125,6 +134,10 @@ representation requires more space, but can improve readability.
|
|
|
125
134
|
%SCHEMA TD
|
|
126
135
|
```
|
|
127
136
|
|
|
137
|
+
The optional argument `ONLY`, followed by one or more table names separated by a
|
|
138
|
+
comma, can be used to display only the named tables and all those connected with
|
|
139
|
+
a foreign key.
|
|
140
|
+
|
|
128
141
|
Graphviz (`dot` in PATH) is required to render schema diagrams.
|
|
129
142
|
|
|
130
143
|
### Number of Rows
|
|
@@ -206,6 +219,11 @@ UNION
|
|
|
206
219
|
SELECT 1, 'Name 1'
|
|
207
220
|
```
|
|
208
221
|
|
|
222
|
+
By default, failed tests will display an explanation, but the notebook will
|
|
223
|
+
continue to run. Set the `DUCKDB_TESTS_RAISE_EXCEPTION` environment variable to
|
|
224
|
+
`true` to raise an exception when a test fails. This can be useful for automated
|
|
225
|
+
testing in CI environments.
|
|
226
|
+
|
|
209
227
|
Disclaimer: The integrated testing is work-in-progress and thus subject to
|
|
210
228
|
potentially incompatible changes and enhancements.
|
|
211
229
|
|
|
@@ -216,7 +234,7 @@ magic command `RA` activates the relational algebra mode for a single cell:
|
|
|
216
234
|
|
|
217
235
|
```
|
|
218
236
|
%RA
|
|
219
|
-
π a, b (σ c = 1 (R))
|
|
237
|
+
π [a, b] (σ [c = 1] (R))
|
|
220
238
|
```
|
|
221
239
|
|
|
222
240
|
The supported operations are:
|
|
@@ -231,6 +249,9 @@ The supported operations are:
|
|
|
231
249
|
- Cross Product `×`
|
|
232
250
|
- Division `÷`
|
|
233
251
|
|
|
252
|
+
The optional flag `ANALYZE` can be used to add an execution diagram to the
|
|
253
|
+
output.
|
|
254
|
+
|
|
234
255
|
The Dockerfile also installs the Jupyter Lab plugin
|
|
235
256
|
[jupyter-ra-extension](https://pypi.org/project/jupyter-ra-extension/). It adds
|
|
236
257
|
the symbols mentioned above and some other supported symbols to the toolbar for
|
|
@@ -245,3 +266,13 @@ magic command `DC` activates the domain calculus mode for a single cell:
|
|
|
245
266
|
%DC
|
|
246
267
|
{ a, b | R(a, b, c) ∧ c = 1 }
|
|
247
268
|
```
|
|
269
|
+
|
|
270
|
+
### Automated Parser Selection
|
|
271
|
+
|
|
272
|
+
`%ALL_RA` or `%ALL_DC` enables the corresponding parser for all subsequently
|
|
273
|
+
executed cells.
|
|
274
|
+
|
|
275
|
+
If the magic command `%AUTO_PARSER` is added to a cell, a parser is
|
|
276
|
+
automatically selected. If `%GUESS_PARSER` is executed, the parser is
|
|
277
|
+
automatically selected for all subsequent cells.
|
|
278
|
+
|
|
@@ -12,8 +12,9 @@ from ipykernel.kernelbase import Kernel
|
|
|
12
12
|
from .db import Connection, DatabaseError, Table
|
|
13
13
|
from .db.error import *
|
|
14
14
|
from .magics import *
|
|
15
|
-
from .parser import RAParser, DCParser
|
|
15
|
+
from .parser import RAParser, DCParser, ParserError
|
|
16
16
|
from .util.ResultSetComparator import ResultSetComparator
|
|
17
|
+
from .util.TestError import TestError
|
|
17
18
|
from .util.formatting import row_count, rows_table, wrap_image
|
|
18
19
|
from .visualization import *
|
|
19
20
|
|
|
@@ -45,8 +46,12 @@ class DuckDBKernel(Kernel):
|
|
|
45
46
|
MagicCommand('query_max_rows').arg('count').on(self._query_max_rows_magic),
|
|
46
47
|
MagicCommand('schema').flag('td').opt('only').on(self._schema_magic),
|
|
47
48
|
MagicCommand('store').arg('file').flag('noheader').result(True).on(self._store_magic),
|
|
48
|
-
MagicCommand('ra').flag('analyze').code(True).on(self._ra_magic),
|
|
49
|
-
MagicCommand('
|
|
49
|
+
MagicCommand('ra').disable('dc', 'auto_parser').flag('analyze').code(True).on(self._ra_magic),
|
|
50
|
+
MagicCommand('all_ra').arg('value', '1').on(self._all_ra_magic),
|
|
51
|
+
MagicCommand('dc').disable('ra', 'auto_parser').code(True).on(self._dc_magic),
|
|
52
|
+
MagicCommand('all_dc').arg('value', '1').on(self._all_dc_magic),
|
|
53
|
+
MagicCommand('auto_parser').disable('ra', 'dc').code(True).on(self._auto_parser_magic),
|
|
54
|
+
MagicCommand('guess_parser').arg('value', '1').on(self._guess_parser_magic),
|
|
50
55
|
)
|
|
51
56
|
|
|
52
57
|
# create placeholders for database and tests
|
|
@@ -139,6 +144,7 @@ class DuckDBKernel(Kernel):
|
|
|
139
144
|
return False
|
|
140
145
|
|
|
141
146
|
def _execute_stmt(self, query: str, silent: bool,
|
|
147
|
+
column_name_mapping: Dict[str, str],
|
|
142
148
|
max_rows: Optional[int]) -> Tuple[Optional[List[str]], Optional[List[List]]]:
|
|
143
149
|
if self._db is None:
|
|
144
150
|
raise AssertionError('load a database first')
|
|
@@ -168,7 +174,8 @@ class DuckDBKernel(Kernel):
|
|
|
168
174
|
else:
|
|
169
175
|
if columns is not None:
|
|
170
176
|
# table header
|
|
171
|
-
|
|
177
|
+
mapped_columns = (column_name_mapping.get(c, c) for c in columns)
|
|
178
|
+
table_header = ''.join(f'<th>{c}</th>' for c in mapped_columns)
|
|
172
179
|
|
|
173
180
|
# table data
|
|
174
181
|
if max_rows is not None and len(rows) > max_rows:
|
|
@@ -302,12 +309,23 @@ class DuckDBKernel(Kernel):
|
|
|
302
309
|
result_columns = [col.rsplit('.', 1)[-1] for col in result_columns]
|
|
303
310
|
|
|
304
311
|
# extract data for test
|
|
305
|
-
|
|
312
|
+
test_data = self._tests[name]
|
|
306
313
|
|
|
314
|
+
# execute test
|
|
315
|
+
try:
|
|
316
|
+
self._execute_test(test_data, result_columns, result)
|
|
317
|
+
self.print_data(wrap_image(True))
|
|
318
|
+
except TestError as e:
|
|
319
|
+
self.print_data(wrap_image(False, e.message))
|
|
320
|
+
if os.environ.get('DUCKDB_TESTS_RAISE_EXCEPTION', 'false').lower() in ('true', '1'):
|
|
321
|
+
raise e
|
|
322
|
+
|
|
323
|
+
@staticmethod
|
|
324
|
+
def _execute_test(test_data: Dict, result_columns: List[str], result: List[List]):
|
|
307
325
|
# check columns if required
|
|
308
|
-
if isinstance(
|
|
326
|
+
if isinstance(test_data['equals'], dict):
|
|
309
327
|
# get column order
|
|
310
|
-
data_columns = list(
|
|
328
|
+
data_columns = list(test_data['equals'].keys())
|
|
311
329
|
column_order = []
|
|
312
330
|
|
|
313
331
|
for dc in data_columns:
|
|
@@ -318,39 +336,37 @@ class DuckDBKernel(Kernel):
|
|
|
318
336
|
found += 1
|
|
319
337
|
|
|
320
338
|
if found == 0:
|
|
321
|
-
|
|
339
|
+
raise TestError(f'attribute {dc} missing')
|
|
322
340
|
if found >= 2:
|
|
323
|
-
|
|
341
|
+
raise TestError(f'ambiguous attribute {dc}')
|
|
324
342
|
|
|
325
343
|
# abort if columns from result are unnecessary
|
|
326
344
|
for i, rc in enumerate(result_columns):
|
|
327
345
|
if i not in column_order:
|
|
328
|
-
|
|
346
|
+
raise TestError(f'unnecessary attribute {rc}')
|
|
329
347
|
|
|
330
348
|
# reorder columns and transform to list of lists
|
|
331
349
|
sorted_columns = [x for _, x in sorted(zip(column_order, data_columns))]
|
|
332
350
|
rows = []
|
|
333
351
|
|
|
334
|
-
for row in zip(*(
|
|
352
|
+
for row in zip(*(test_data['equals'][col] for col in sorted_columns)):
|
|
335
353
|
rows.append(row)
|
|
336
354
|
|
|
337
355
|
else:
|
|
338
|
-
rows =
|
|
356
|
+
rows = test_data['equals']
|
|
339
357
|
|
|
340
358
|
# ordered test
|
|
341
|
-
if
|
|
359
|
+
if test_data['ordered']:
|
|
342
360
|
# calculate diff
|
|
343
361
|
rsc = ResultSetComparator(result, rows)
|
|
344
362
|
|
|
345
363
|
missing = len(rsc.ordered_right_only)
|
|
346
364
|
if missing > 0:
|
|
347
|
-
|
|
365
|
+
raise TestError(f'{row_count(missing)} missing')
|
|
348
366
|
|
|
349
367
|
missing = len(rsc.ordered_left_only)
|
|
350
368
|
if missing > 0:
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return self.print_data(wrap_image(True))
|
|
369
|
+
raise TestError(f'{row_count(missing)} more than required')
|
|
354
370
|
|
|
355
371
|
# unordered test
|
|
356
372
|
else:
|
|
@@ -362,13 +378,11 @@ class DuckDBKernel(Kernel):
|
|
|
362
378
|
|
|
363
379
|
# print result
|
|
364
380
|
if below > 0 and above > 0:
|
|
365
|
-
|
|
381
|
+
raise TestError(f'{row_count(below)} missing, {row_count(above)} unnecessary')
|
|
366
382
|
elif below > 0:
|
|
367
|
-
|
|
383
|
+
raise TestError(f'{row_count(below)} missing')
|
|
368
384
|
elif above > 0:
|
|
369
|
-
|
|
370
|
-
else:
|
|
371
|
-
self.print_data(wrap_image(True))
|
|
385
|
+
raise TestError(f'{row_count(above)} unnecessary')
|
|
372
386
|
|
|
373
387
|
def _all_magic(self, silent: bool):
|
|
374
388
|
return {
|
|
@@ -486,6 +500,15 @@ class DuckDBKernel(Kernel):
|
|
|
486
500
|
'generated_code': sql
|
|
487
501
|
}
|
|
488
502
|
|
|
503
|
+
def _all_ra_magic(self, silent: bool, value: str):
|
|
504
|
+
if value.lower() in ('1', 'on', 'true'):
|
|
505
|
+
self._magics['ra'].default(True)
|
|
506
|
+
self._magics['dc'].default(False)
|
|
507
|
+
|
|
508
|
+
self.print('All further cells are interpreted as %RA.\n')
|
|
509
|
+
else:
|
|
510
|
+
self._magics['ra'].default(False)
|
|
511
|
+
|
|
489
512
|
def _dc_magic(self, silent: bool, code: str):
|
|
490
513
|
if self._db is None:
|
|
491
514
|
raise AssertionError('load a database first')
|
|
@@ -503,12 +526,42 @@ class DuckDBKernel(Kernel):
|
|
|
503
526
|
root_node = DCParser.parse_query(code)
|
|
504
527
|
|
|
505
528
|
# generate sql
|
|
506
|
-
sql = root_node.
|
|
529
|
+
sql, cnm = root_node.to_sql_with_renamed_columns(tables)
|
|
507
530
|
|
|
508
531
|
return {
|
|
509
|
-
'generated_code': sql
|
|
532
|
+
'generated_code': sql,
|
|
533
|
+
'column_name_mapping': cnm
|
|
510
534
|
}
|
|
511
535
|
|
|
536
|
+
def _all_dc_magic(self, silent: bool, value: str):
|
|
537
|
+
if value.lower() in ('1', 'on', 'true'):
|
|
538
|
+
self._magics['dc'].default(True)
|
|
539
|
+
self._magics['ra'].default(False)
|
|
540
|
+
|
|
541
|
+
self.print('All further cells are interpreted as %DC.\n')
|
|
542
|
+
else:
|
|
543
|
+
self._magics['dc'].default(False)
|
|
544
|
+
|
|
545
|
+
def _guess_parser_magic(self, silent: bool, value: str):
|
|
546
|
+
if value.lower() in ('1', 'on', 'true'):
|
|
547
|
+
self._magics['auto_parser'].default(True)
|
|
548
|
+
self.print('The correct parser is guessed for each subsequently executed cell.\n')
|
|
549
|
+
else:
|
|
550
|
+
self._magics['auto_parser'].default(False)
|
|
551
|
+
|
|
552
|
+
def _auto_parser_magic(self, silent: bool, code: str):
|
|
553
|
+
try:
|
|
554
|
+
return self._ra_magic(silent, code, analyze=False)
|
|
555
|
+
except ParserError as e:
|
|
556
|
+
if e.depth > 0:
|
|
557
|
+
raise e
|
|
558
|
+
|
|
559
|
+
try:
|
|
560
|
+
return self._dc_magic(silent, code)
|
|
561
|
+
except ParserError as e:
|
|
562
|
+
if e.depth > 0:
|
|
563
|
+
raise e
|
|
564
|
+
|
|
512
565
|
# jupyter related functions
|
|
513
566
|
def do_execute(self, code: str, silent: bool,
|
|
514
567
|
store_history: bool = True, user_expressions: dict = None, allow_stdin: bool = False,
|
|
@@ -530,6 +583,10 @@ class DuckDBKernel(Kernel):
|
|
|
530
583
|
clean_code = execution_args['generated_code']
|
|
531
584
|
del execution_args['generated_code']
|
|
532
585
|
|
|
586
|
+
# set default column name mapping if none provided
|
|
587
|
+
if 'column_name_mapping' not in execution_args:
|
|
588
|
+
execution_args['column_name_mapping'] = {}
|
|
589
|
+
|
|
533
590
|
# execute statement if needed
|
|
534
591
|
if clean_code.strip():
|
|
535
592
|
cols, rows = self._execute_stmt(clean_code, silent, **execution_args)
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
from typing import Any, List, Tuple, Callable, Dict
|
|
1
|
+
from typing import Any, List, Tuple, Callable, Dict, Set
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class MagicCommand:
|
|
5
|
-
_ARG = '''([^ ]+?|'.+?'|".+?")'''
|
|
5
|
+
_ARG = '''([^ ]+?|'.+?'|".+?")?'''
|
|
6
6
|
|
|
7
7
|
def __init__(self, *names: str):
|
|
8
|
-
self._names: Tuple[str] = names
|
|
8
|
+
self._names: Tuple[str, ...] = names
|
|
9
9
|
|
|
10
|
-
self._arguments: List[Tuple[str, str]] = []
|
|
10
|
+
self._arguments: List[Tuple[str, Any, str]] = []
|
|
11
11
|
self._flags: List[Tuple[str, str]] = []
|
|
12
12
|
self._optionals: List[Tuple[str, Any, str]] = []
|
|
13
|
-
|
|
13
|
+
self._disables: Set[str] = set()
|
|
14
14
|
self._code: bool = False
|
|
15
15
|
self._result: bool = False
|
|
16
|
+
self._default: bool = False
|
|
16
17
|
|
|
17
18
|
self._on: List[Callable] = []
|
|
18
19
|
|
|
19
20
|
@property
|
|
20
|
-
def names(self) -> Tuple[str]:
|
|
21
|
+
def names(self) -> Tuple[str, ...]:
|
|
21
22
|
return self._names
|
|
22
23
|
|
|
23
24
|
@property
|
|
24
|
-
def args(self) -> List[Tuple[str, str]]:
|
|
25
|
+
def args(self) -> List[Tuple[str, Any, str]]:
|
|
25
26
|
return self._arguments
|
|
26
27
|
|
|
27
28
|
@property
|
|
@@ -32,6 +33,10 @@ class MagicCommand:
|
|
|
32
33
|
def optionals(self) -> List[Tuple[str, Any, str]]:
|
|
33
34
|
return self._optionals
|
|
34
35
|
|
|
36
|
+
@property
|
|
37
|
+
def disables(self) -> Set[str]:
|
|
38
|
+
return self._disables
|
|
39
|
+
|
|
35
40
|
@property
|
|
36
41
|
def requires_code(self) -> bool:
|
|
37
42
|
return self._code
|
|
@@ -40,8 +45,17 @@ class MagicCommand:
|
|
|
40
45
|
def requires_query_result(self) -> bool:
|
|
41
46
|
return self._result
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
@property
|
|
49
|
+
def is_default(self) -> bool:
|
|
50
|
+
return self._default
|
|
51
|
+
|
|
52
|
+
def arg(self, name: str, default_value: Any = None, description: str = None) -> 'MagicCommand':
|
|
53
|
+
if len(self._arguments) > 0:
|
|
54
|
+
ln, ldv, _ = self._arguments[-1]
|
|
55
|
+
if ldv is not None and default_value is None:
|
|
56
|
+
raise ValueError(f'argument {name} without default value registered after argument {ln} with default value {ldv}')
|
|
57
|
+
|
|
58
|
+
self._arguments.append((name, default_value, description))
|
|
45
59
|
return self
|
|
46
60
|
|
|
47
61
|
def opt(self, name: str, default_value: Any = None, description: str = None) -> 'MagicCommand':
|
|
@@ -52,6 +66,12 @@ class MagicCommand:
|
|
|
52
66
|
self._flags.append((name, description))
|
|
53
67
|
return self
|
|
54
68
|
|
|
69
|
+
def disable(self, *name: str) -> 'MagicCommand':
|
|
70
|
+
for n in name:
|
|
71
|
+
self._disables.add(n)
|
|
72
|
+
|
|
73
|
+
return self
|
|
74
|
+
|
|
55
75
|
def code(self, code: bool) -> 'MagicCommand':
|
|
56
76
|
self._code = code
|
|
57
77
|
return self
|
|
@@ -60,10 +80,14 @@ class MagicCommand:
|
|
|
60
80
|
self._result = result
|
|
61
81
|
return self
|
|
62
82
|
|
|
63
|
-
def on(self, fun: Callable):
|
|
83
|
+
def on(self, fun: Callable) -> 'MagicCommand':
|
|
64
84
|
self._on.append(fun)
|
|
65
85
|
return self
|
|
66
86
|
|
|
87
|
+
def default(self, default: bool) -> 'MagicCommand':
|
|
88
|
+
self._default = default
|
|
89
|
+
return self
|
|
90
|
+
|
|
67
91
|
@property
|
|
68
92
|
def parameters(self) -> str:
|
|
69
93
|
args = ' +'.join([self._ARG] * len(self._arguments))
|
{jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommandCallback.py
RENAMED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
from typing import Optional, List
|
|
2
2
|
|
|
3
3
|
from . import MagicCommand
|
|
4
|
+
from .StringWrapper import StringWrapper
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class MagicCommandCallback:
|
|
7
|
-
def __init__(self, mc: MagicCommand, silent: bool, code:
|
|
8
|
+
def __init__(self, mc: MagicCommand, silent: bool, code: StringWrapper, *args, **kwargs):
|
|
8
9
|
self._mc: MagicCommand = mc
|
|
9
10
|
self._silent: bool = silent
|
|
10
|
-
self._code:
|
|
11
|
+
self._code: StringWrapper = code
|
|
11
12
|
self._args = args
|
|
12
13
|
self._kwargs = kwargs
|
|
13
14
|
|
|
15
|
+
@property
|
|
16
|
+
def magic(self) -> MagicCommand:
|
|
17
|
+
return self._mc
|
|
18
|
+
|
|
14
19
|
def __call__(self, columns: Optional[List[str]] = None, rows: Optional[List[List]] = None):
|
|
15
20
|
if self._mc.requires_code:
|
|
16
|
-
|
|
21
|
+
result = self._mc(self._silent, self._code.value, *self._args, **self._kwargs)
|
|
22
|
+
if 'generated_code' in result:
|
|
23
|
+
self._code.value = result['generated_code']
|
|
24
|
+
|
|
25
|
+
return result
|
|
26
|
+
|
|
17
27
|
if self._mc.requires_query_result:
|
|
18
28
|
return self._mc(self._silent, columns, rows, *self._args, **self._kwargs)
|
|
29
|
+
|
|
19
30
|
else:
|
|
20
31
|
return self._mc(self._silent, *self._args, **self._kwargs)
|
{jupyter_duckdb-1.2.0.3 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommandHandler.py
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from typing import Dict, Tuple, List
|
|
2
|
+
from typing import Dict, Tuple, List, Optional
|
|
3
3
|
|
|
4
4
|
from . import MagicCommand, MagicCommandException, MagicCommandCallback
|
|
5
|
+
from .StringWrapper import StringWrapper
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class MagicCommandHandler:
|
|
@@ -14,10 +15,23 @@ class MagicCommandHandler:
|
|
|
14
15
|
key = key.lower()
|
|
15
16
|
self._magics[key] = cmd
|
|
16
17
|
|
|
18
|
+
def __getitem__(self, key: str) -> MagicCommand:
|
|
19
|
+
return self._magics[key.lower()]
|
|
20
|
+
|
|
17
21
|
def __call__(self, silent: bool, code: str) -> Tuple[str, List[MagicCommandCallback], List[MagicCommandCallback]]:
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
code_wrapper = StringWrapper()
|
|
23
|
+
enabled_callbacks: List[MagicCommandCallback] = []
|
|
24
|
+
|
|
25
|
+
# enable commands with default==True
|
|
26
|
+
for magic in self._magics.values():
|
|
27
|
+
if magic.is_default:
|
|
28
|
+
flags = {name: False for name, _ in magic.flags}
|
|
29
|
+
optionals = {name: default for name, default, _ in magic.optionals}
|
|
30
|
+
callback = MagicCommandCallback(magic, silent, code_wrapper, **flags, **optionals)
|
|
20
31
|
|
|
32
|
+
enabled_callbacks.append(callback)
|
|
33
|
+
|
|
34
|
+
# search for magic commands in code
|
|
21
35
|
while True:
|
|
22
36
|
# ensure code starts with '%' or '%%' but not with '%%%'
|
|
23
37
|
match = re.match(r'^%{1,2}([^% ]+?)([ \t]*$| .+?$)', code, re.MULTILINE | re.IGNORECASE)
|
|
@@ -45,7 +59,11 @@ class MagicCommandHandler:
|
|
|
45
59
|
raise MagicCommandException(f'could not parse parameters for command "{command}"')
|
|
46
60
|
|
|
47
61
|
# extract args
|
|
48
|
-
args = [
|
|
62
|
+
args = [group if group is not None else default
|
|
63
|
+
for group, (_, default, _) in zip(match.groups(), magic.args)]
|
|
64
|
+
|
|
65
|
+
if any(arg is None for arg in args):
|
|
66
|
+
raise MagicCommandException(f'could not parse parameters for command "{command}"')
|
|
49
67
|
|
|
50
68
|
i = len(args) + 1
|
|
51
69
|
|
|
@@ -73,12 +91,35 @@ class MagicCommandHandler:
|
|
|
73
91
|
optionals[name.lower()] = value
|
|
74
92
|
|
|
75
93
|
# add to callbacks
|
|
76
|
-
callback = MagicCommandCallback(magic, silent,
|
|
94
|
+
callback = MagicCommandCallback(magic, silent, code_wrapper, *args, **flags, **optionals)
|
|
95
|
+
enabled_callbacks.append(callback)
|
|
96
|
+
|
|
97
|
+
# disable overwritten callbacks
|
|
98
|
+
callbacks = []
|
|
99
|
+
blacklist = set()
|
|
100
|
+
|
|
101
|
+
for callback in reversed(enabled_callbacks):
|
|
102
|
+
for name in callback.magic.names:
|
|
103
|
+
if name in blacklist:
|
|
104
|
+
break
|
|
105
|
+
else:
|
|
106
|
+
callbacks.append(callback)
|
|
107
|
+
|
|
108
|
+
for name in callback.magic.names:
|
|
109
|
+
blacklist.add(name)
|
|
110
|
+
for disable in callback.magic.disables:
|
|
111
|
+
blacklist.add(disable)
|
|
112
|
+
|
|
113
|
+
# prepare callback lists
|
|
114
|
+
pre_query_callbacks = []
|
|
115
|
+
post_query_callbacks = []
|
|
77
116
|
|
|
78
|
-
|
|
117
|
+
for callback in reversed(callbacks):
|
|
118
|
+
if not callback.magic.requires_query_result:
|
|
79
119
|
pre_query_callbacks.append(callback)
|
|
80
120
|
else:
|
|
81
121
|
post_query_callbacks.append(callback)
|
|
82
122
|
|
|
83
123
|
# return callbacks
|
|
124
|
+
code_wrapper.value = code
|
|
84
125
|
return code, pre_query_callbacks, post_query_callbacks
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from .ParserError import DCParserError
|
|
1
2
|
from .elements import *
|
|
2
3
|
from .tokenizer import *
|
|
3
4
|
|
|
@@ -18,17 +19,17 @@ class DCParser:
|
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
# raise exception if query is not in the correct format
|
|
21
|
-
raise
|
|
22
|
+
raise DCParserError('The expression shall be of the format "{ x1, ..., xn | f(x1, ..., xn) }".', 0)
|
|
22
23
|
|
|
23
24
|
@staticmethod
|
|
24
|
-
def parse_projection(*tokens: Token) -> LogicOperand:
|
|
25
|
+
def parse_projection(*tokens: Token, depth: int = 0) -> LogicOperand:
|
|
25
26
|
if len(tokens) == 1:
|
|
26
27
|
tokens = tuple(Tokenizer.tokenize(tokens[0]))
|
|
27
28
|
|
|
28
29
|
return LogicOperand(*tokens)
|
|
29
30
|
|
|
30
31
|
@staticmethod
|
|
31
|
-
def parse_condition(*tokens: Token) -> LogicElement:
|
|
32
|
+
def parse_condition(*tokens: Token, depth: int = 0) -> LogicElement:
|
|
32
33
|
if len(tokens) == 1:
|
|
33
34
|
tokens = tuple(Tokenizer.tokenize(tokens[0]))
|
|
34
35
|
|
|
@@ -40,8 +41,8 @@ class DCParser:
|
|
|
40
41
|
# return the operator
|
|
41
42
|
# with left part of tokens and right part of tokens
|
|
42
43
|
return operator(
|
|
43
|
-
DCParser.parse_condition(*tokens[:-i]),
|
|
44
|
-
DCParser.parse_condition(*tokens[-i + 1:])
|
|
44
|
+
DCParser.parse_condition(*tokens[:-i], depth=depth + 1),
|
|
45
|
+
DCParser.parse_condition(*tokens[-i + 1:], depth=depth + 1)
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
# not
|
|
@@ -56,10 +57,12 @@ class DCParser:
|
|
|
56
57
|
elif len(tokens) == 2:
|
|
57
58
|
return DCOperand(
|
|
58
59
|
tokens[0],
|
|
59
|
-
tuple(Tokenizer.tokenize(tokens[1]))
|
|
60
|
+
tuple(Tokenizer.tokenize(tokens[1])),
|
|
61
|
+
depth=depth + 1
|
|
60
62
|
)
|
|
61
63
|
else:
|
|
62
64
|
return DCOperand(
|
|
63
65
|
tokens[0],
|
|
64
|
-
tokens[1:]
|
|
66
|
+
tokens[1:],
|
|
67
|
+
depth=depth + 1
|
|
65
68
|
)
|