jupyter-duckdb 1.2.0.4__tar.gz → 1.2.0.6__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.4 → jupyter_duckdb-1.2.0.6}/PKG-INFO +12 -2
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/README.md +11 -1
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/implementation/duckdb/Connection.py +5 -1
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/implementation/sqlite/Connection.py +2 -1
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/kernel.py +60 -3
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/magics/MagicCommand.py +34 -10
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/magics/MagicCommandCallback.py +14 -3
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/magics/MagicCommandHandler.py +47 -6
- jupyter_duckdb-1.2.0.6/src/duckdb_kernel/magics/StringWrapper.py +3 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/DCParser.py +10 -7
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/LogicParser.py +6 -6
- jupyter_duckdb-1.2.0.6/src/duckdb_kernel/parser/ParserError.py +18 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/RAParser.py +12 -14
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/__init__.py +1 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/DCOperand.py +7 -4
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +14 -6
- jupyter_duckdb-1.2.0.6/src/duckdb_kernel/util/SQL.py +6 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/jupyter_duckdb.egg-info/PKG-INFO +12 -2
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/jupyter_duckdb.egg-info/SOURCES.txt +5 -1
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/test/test_ra.py +2 -2
- jupyter_duckdb-1.2.0.6/test/test_sql.py +48 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/setup.cfg +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/setup.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/__main__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/Column.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/Connection.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/Constraint.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/DatabaseError.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/ForeignKey.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/Table.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/error/EmptyResultError.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/error/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/implementation/duckdb/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/implementation/postgres/Connection.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/implementation/postgres/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/implementation/postgres/util.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/implementation/sqlite/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/kernel.json +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/magics/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/RAElement.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/RAOperand.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Cross.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Division.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Join.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/unary/Projection.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/unary/Rename.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/unary/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/tokenizer/Token.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/util/RenamableColumnList.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/util/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/util/ResultSetComparator.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/util/TestError.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/util/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/util/formatting.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/visualization/Drawer.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/visualization/__init__.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/jupyter_duckdb.egg-info/top_level.txt +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/test/test_dc.py +0 -0
- {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/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.6
|
|
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
|
|
@@ -262,7 +262,7 @@ magic command `RA` activates the relational algebra mode for a single cell:
|
|
|
262
262
|
|
|
263
263
|
```
|
|
264
264
|
%RA
|
|
265
|
-
π a, b (σ c = 1 (R))
|
|
265
|
+
π [a, b] (σ [c = 1] (R))
|
|
266
266
|
```
|
|
267
267
|
|
|
268
268
|
The supported operations are:
|
|
@@ -294,3 +294,13 @@ magic command `DC` activates the domain calculus mode for a single cell:
|
|
|
294
294
|
%DC
|
|
295
295
|
{ a, b | R(a, b, c) ∧ c = 1 }
|
|
296
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
|
+
|
|
@@ -234,7 +234,7 @@ magic command `RA` activates the relational algebra mode for a single cell:
|
|
|
234
234
|
|
|
235
235
|
```
|
|
236
236
|
%RA
|
|
237
|
-
π a, b (σ c = 1 (R))
|
|
237
|
+
π [a, b] (σ [c = 1] (R))
|
|
238
238
|
```
|
|
239
239
|
|
|
240
240
|
The supported operations are:
|
|
@@ -266,3 +266,13 @@ magic command `DC` activates the domain calculus mode for a single cell:
|
|
|
266
266
|
%DC
|
|
267
267
|
{ a, b | R(a, b, c) ∧ c = 1 }
|
|
268
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
|
+
|
|
@@ -4,6 +4,7 @@ import duckdb
|
|
|
4
4
|
|
|
5
5
|
from ... import DatabaseError, Column, Constraint, ForeignKey, Table
|
|
6
6
|
from ...Connection import Connection as Base
|
|
7
|
+
from ...error import EmptyResultError
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Connection(Base):
|
|
@@ -36,7 +37,10 @@ class Connection(Base):
|
|
|
36
37
|
raise e
|
|
37
38
|
|
|
38
39
|
# get rows
|
|
39
|
-
|
|
40
|
+
try:
|
|
41
|
+
rows = cursor.fetchall()
|
|
42
|
+
except duckdb.InvalidInputException as e:
|
|
43
|
+
raise EmptyResultError(str(e))
|
|
40
44
|
|
|
41
45
|
# get columns
|
|
42
46
|
if cursor.description is None:
|
|
@@ -4,6 +4,7 @@ from typing import Dict, List, Tuple, Any
|
|
|
4
4
|
|
|
5
5
|
from ... import DatabaseError, Column, Constraint, ForeignKey, Table
|
|
6
6
|
from ...Connection import Connection as Base
|
|
7
|
+
from ...error import EmptyResultError
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Connection(Base):
|
|
@@ -41,7 +42,7 @@ class Connection(Base):
|
|
|
41
42
|
|
|
42
43
|
# get columns
|
|
43
44
|
if cursor.description is None:
|
|
44
|
-
|
|
45
|
+
raise EmptyResultError()
|
|
45
46
|
else:
|
|
46
47
|
columns = [e[0] for e in cursor.description]
|
|
47
48
|
|
|
@@ -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.SQL import SQL_KEYWORDS
|
|
17
18
|
from .util.TestError import TestError
|
|
18
19
|
from .util.formatting import row_count, rows_table, wrap_image
|
|
19
20
|
from .visualization import *
|
|
@@ -46,8 +47,13 @@ class DuckDBKernel(Kernel):
|
|
|
46
47
|
MagicCommand('query_max_rows').arg('count').on(self._query_max_rows_magic),
|
|
47
48
|
MagicCommand('schema').flag('td').opt('only').on(self._schema_magic),
|
|
48
49
|
MagicCommand('store').arg('file').flag('noheader').result(True).on(self._store_magic),
|
|
49
|
-
MagicCommand('
|
|
50
|
-
MagicCommand('dc').code(True).on(self.
|
|
50
|
+
MagicCommand('sql').disable('ra', 'dc', 'auto_parser'),
|
|
51
|
+
MagicCommand('ra').disable('sql', 'dc', 'auto_parser').flag('analyze').code(True).on(self._ra_magic),
|
|
52
|
+
MagicCommand('all_ra').arg('value', '1').on(self._all_ra_magic),
|
|
53
|
+
MagicCommand('dc').disable('sql', 'ra', 'auto_parser').code(True).on(self._dc_magic),
|
|
54
|
+
MagicCommand('all_dc').arg('value', '1').on(self._all_dc_magic),
|
|
55
|
+
MagicCommand('auto_parser').disable('sql', 'ra', 'dc').code(True).on(self._auto_parser_magic),
|
|
56
|
+
MagicCommand('guess_parser').arg('value', '1').on(self._guess_parser_magic),
|
|
51
57
|
)
|
|
52
58
|
|
|
53
59
|
# create placeholders for database and tests
|
|
@@ -297,6 +303,11 @@ class DuckDBKernel(Kernel):
|
|
|
297
303
|
self.print(f'loaded tests from {with_tests}\n')
|
|
298
304
|
|
|
299
305
|
def _test_magic(self, silent: bool, result_columns: List[str], result: List[List], name: str):
|
|
306
|
+
# If the query was empty, result_columns and result may be None.
|
|
307
|
+
if result_columns is None or result is None:
|
|
308
|
+
self.print_data(wrap_image(False, 'Statement did not return data.'))
|
|
309
|
+
return
|
|
310
|
+
|
|
300
311
|
# Testing makes no sense if there is no output.
|
|
301
312
|
if silent:
|
|
302
313
|
return
|
|
@@ -496,6 +507,15 @@ class DuckDBKernel(Kernel):
|
|
|
496
507
|
'generated_code': sql
|
|
497
508
|
}
|
|
498
509
|
|
|
510
|
+
def _all_ra_magic(self, silent: bool, value: str):
|
|
511
|
+
if value.lower() in ('1', 'on', 'true'):
|
|
512
|
+
self._magics['ra'].default(True)
|
|
513
|
+
self._magics['dc'].default(False)
|
|
514
|
+
|
|
515
|
+
self.print('All further cells are interpreted as %RA.\n')
|
|
516
|
+
else:
|
|
517
|
+
self._magics['ra'].default(False)
|
|
518
|
+
|
|
499
519
|
def _dc_magic(self, silent: bool, code: str):
|
|
500
520
|
if self._db is None:
|
|
501
521
|
raise AssertionError('load a database first')
|
|
@@ -520,6 +540,43 @@ class DuckDBKernel(Kernel):
|
|
|
520
540
|
'column_name_mapping': cnm
|
|
521
541
|
}
|
|
522
542
|
|
|
543
|
+
def _all_dc_magic(self, silent: bool, value: str):
|
|
544
|
+
if value.lower() in ('1', 'on', 'true'):
|
|
545
|
+
self._magics['dc'].default(True)
|
|
546
|
+
self._magics['ra'].default(False)
|
|
547
|
+
|
|
548
|
+
self.print('All further cells are interpreted as %DC.\n')
|
|
549
|
+
else:
|
|
550
|
+
self._magics['dc'].default(False)
|
|
551
|
+
|
|
552
|
+
def _guess_parser_magic(self, silent: bool, value: str):
|
|
553
|
+
if value.lower() in ('1', 'on', 'true'):
|
|
554
|
+
self._magics['auto_parser'].default(True)
|
|
555
|
+
self.print('The correct parser is guessed for each subsequently executed cell.\n')
|
|
556
|
+
else:
|
|
557
|
+
self._magics['auto_parser'].default(False)
|
|
558
|
+
|
|
559
|
+
def _auto_parser_magic(self, silent: bool, code: str):
|
|
560
|
+
# do not handle statements starting with SQL keywords
|
|
561
|
+
first_word = code.strip().split(maxsplit=1)
|
|
562
|
+
if len(first_word) > 0:
|
|
563
|
+
if first_word[0].upper() in SQL_KEYWORDS:
|
|
564
|
+
return
|
|
565
|
+
|
|
566
|
+
# try to parse DC
|
|
567
|
+
try:
|
|
568
|
+
return self._dc_magic(silent, code)
|
|
569
|
+
except ParserError as e:
|
|
570
|
+
if e.depth > 0:
|
|
571
|
+
raise e
|
|
572
|
+
|
|
573
|
+
# try to parse RA
|
|
574
|
+
try:
|
|
575
|
+
return self._ra_magic(silent, code, analyze=False)
|
|
576
|
+
except ParserError as e:
|
|
577
|
+
if e.depth > 0:
|
|
578
|
+
raise e
|
|
579
|
+
|
|
523
580
|
# jupyter related functions
|
|
524
581
|
def do_execute(self, code: str, silent: bool,
|
|
525
582
|
store_history: bool = True, user_expressions: dict = None, allow_stdin: bool = False,
|
|
@@ -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.4 → jupyter_duckdb-1.2.0.6}/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.4 → jupyter_duckdb-1.2.0.6}/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
|
)
|
|
@@ -4,12 +4,12 @@ from .tokenizer import *
|
|
|
4
4
|
|
|
5
5
|
class LogicParser:
|
|
6
6
|
@staticmethod
|
|
7
|
-
def parse_query(query: str) -> LogicElement:
|
|
7
|
+
def parse_query(query: str, depth: int = 0) -> LogicElement:
|
|
8
8
|
initial_token = Token(query)
|
|
9
|
-
return LogicParser.parse_tokens(initial_token)
|
|
9
|
+
return LogicParser.parse_tokens(initial_token, depth=depth)
|
|
10
10
|
|
|
11
11
|
@staticmethod
|
|
12
|
-
def parse_tokens(*tokens: Token) -> LogicElement:
|
|
12
|
+
def parse_tokens(*tokens: Token, depth: int = 0) -> LogicElement:
|
|
13
13
|
if len(tokens) == 1:
|
|
14
14
|
tokens = tuple(Tokenizer.tokenize(tokens[0]))
|
|
15
15
|
|
|
@@ -21,14 +21,14 @@ class LogicParser:
|
|
|
21
21
|
# return the operator
|
|
22
22
|
# with left part of tokens and right part of tokens
|
|
23
23
|
return operator(
|
|
24
|
-
LogicParser.parse_tokens(*tokens[:-i]),
|
|
25
|
-
LogicParser.parse_tokens(*tokens[-i + 1:])
|
|
24
|
+
LogicParser.parse_tokens(*tokens[:-i], depth=depth + 1),
|
|
25
|
+
LogicParser.parse_tokens(*tokens[-i + 1:], depth=depth + 1)
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
# not
|
|
29
29
|
if tokens[0] in LOGIC_NOT.symbols():
|
|
30
30
|
return LOGIC_NOT(
|
|
31
|
-
LogicParser.parse_tokens(*tokens[1:])
|
|
31
|
+
LogicParser.parse_tokens(*tokens[1:], depth=depth + 1)
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
# ArgList
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class ParserError(Exception):
|
|
2
|
+
def __init__(self, message: str, depth: int):
|
|
3
|
+
super().__init__(message)
|
|
4
|
+
|
|
5
|
+
self.message: str = message
|
|
6
|
+
self.depth: int = depth
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RAParserError(ParserError):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DCParserError(ParserError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LogicParserError(ParserError):
|
|
18
|
+
pass
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .LogicParser import LogicParser
|
|
2
|
+
from .ParserError import RAParserError
|
|
2
3
|
from .elements import *
|
|
3
4
|
from .tokenizer import *
|
|
4
5
|
|
|
@@ -10,10 +11,10 @@ class RAParser:
|
|
|
10
11
|
@staticmethod
|
|
11
12
|
def parse_query(query: str) -> RAElement:
|
|
12
13
|
initial_token = Token(query)
|
|
13
|
-
return RAParser.parse_tokens(initial_token)
|
|
14
|
+
return RAParser.parse_tokens(initial_token, depth=0)
|
|
14
15
|
|
|
15
16
|
@staticmethod
|
|
16
|
-
def parse_tokens(*tokens: Token, target: RAOperator | RAOperand = None) -> RAElement:
|
|
17
|
+
def parse_tokens(*tokens: Token, target: RAOperator | RAOperand = None, depth: int = 0) -> RAElement:
|
|
17
18
|
if len(tokens) == 1:
|
|
18
19
|
tokens = tuple(Tokenizer.tokenize(tokens[0]))
|
|
19
20
|
|
|
@@ -24,15 +25,15 @@ class RAParser:
|
|
|
24
25
|
if tokens[-i].lower() in operator.symbols():
|
|
25
26
|
# raise error if left or right operand missing
|
|
26
27
|
if i == 1:
|
|
27
|
-
raise
|
|
28
|
+
raise RAParserError(f'right operand missing after {tokens[-i]}', depth)
|
|
28
29
|
if i == len(tokens):
|
|
29
|
-
raise
|
|
30
|
+
raise RAParserError(f'left operand missing before {tokens[-i]}', depth)
|
|
30
31
|
|
|
31
32
|
# return the operator
|
|
32
33
|
# with left part of tokens and right part of tokens
|
|
33
34
|
return operator(
|
|
34
|
-
RAParser.parse_tokens(*tokens[:-i]),
|
|
35
|
-
RAParser.parse_tokens(*tokens[-i + 1:])
|
|
35
|
+
RAParser.parse_tokens(*tokens[:-i], depth=depth + 1),
|
|
36
|
+
RAParser.parse_tokens(*tokens[-i + 1:], depth=depth + 1)
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
# unary operators
|
|
@@ -44,8 +45,8 @@ class RAParser:
|
|
|
44
45
|
# the last token is the operators target.
|
|
45
46
|
if target is None:
|
|
46
47
|
op = operator(
|
|
47
|
-
RAParser.parse_tokens(tokens[-1]),
|
|
48
|
-
LogicParser.parse_tokens(*tokens[-i + 1:-1])
|
|
48
|
+
RAParser.parse_tokens(tokens[-1], depth=depth + 1),
|
|
49
|
+
LogicParser.parse_tokens(*tokens[-i + 1:-1], depth=depth + 1)
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
# Otherwise the handed target is this operator's
|
|
@@ -53,16 +54,13 @@ class RAParser:
|
|
|
53
54
|
else:
|
|
54
55
|
op = operator(
|
|
55
56
|
target,
|
|
56
|
-
LogicParser.parse_tokens(*tokens[-i + 1:])
|
|
57
|
+
LogicParser.parse_tokens(*tokens[-i + 1:], depth=depth + 1)
|
|
57
58
|
)
|
|
58
59
|
|
|
59
60
|
# If there are any more tokens the operator is
|
|
60
61
|
# the target for the next step.
|
|
61
62
|
if i < len(tokens):
|
|
62
|
-
return RAParser.parse_tokens(
|
|
63
|
-
*tokens[:-i],
|
|
64
|
-
target=op
|
|
65
|
-
)
|
|
63
|
+
return RAParser.parse_tokens(*tokens[:-i], target=op, depth=depth + 1)
|
|
66
64
|
|
|
67
65
|
# Otherwise the operator is the return value.
|
|
68
66
|
else:
|
|
@@ -70,6 +68,6 @@ class RAParser:
|
|
|
70
68
|
|
|
71
69
|
# return as name
|
|
72
70
|
if len(tokens) > 1:
|
|
73
|
-
raise
|
|
71
|
+
raise RAParserError(f'{tokens=}', depth)
|
|
74
72
|
|
|
75
73
|
return RAOperand(tokens[0])
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/DCOperand.py
RENAMED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from typing import Tuple
|
|
2
2
|
|
|
3
3
|
from .LogicOperand import LogicOperand
|
|
4
|
+
from ..ParserError import DCParserError
|
|
4
5
|
from ..tokenizer import Token
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class DCOperand(LogicOperand):
|
|
8
|
-
def __new__(cls, relation: Token, columns: Tuple[Token], skip_comma: bool = False):
|
|
9
|
+
def __new__(cls, relation: Token, columns: Tuple[Token, ...], skip_comma: bool = False, depth: int = 0):
|
|
9
10
|
if not skip_comma and not all(t == ',' for i, t in enumerate(columns) if i % 2 == 1):
|
|
10
|
-
raise
|
|
11
|
+
raise DCParserError('arguments must be separated by commas', 0)
|
|
11
12
|
|
|
12
13
|
return tuple.__new__(
|
|
13
14
|
cls,
|
|
@@ -18,9 +19,11 @@ class DCOperand(LogicOperand):
|
|
|
18
19
|
))
|
|
19
20
|
)
|
|
20
21
|
|
|
21
|
-
def __init__(self,
|
|
22
|
+
def __init__(self, relation: Token, columns: Tuple[Token, ...], skip_comma: bool = False, depth: int = 0):
|
|
22
23
|
super().__init__()
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
self.depth: int = depth
|
|
26
|
+
self.invert: bool = False
|
|
24
27
|
|
|
25
28
|
@property
|
|
26
29
|
def relation(self) -> Token:
|
|
@@ -8,6 +8,7 @@ from ..LogicElement import LogicElement
|
|
|
8
8
|
from ..LogicOperand import LogicOperand
|
|
9
9
|
from ..LogicOperator import LogicOperator
|
|
10
10
|
from ..unary import Not
|
|
11
|
+
from ...ParserError import DCParserError
|
|
11
12
|
from ...tokenizer import Token
|
|
12
13
|
from ...util.RenamableColumnList import RenamableColumnList
|
|
13
14
|
from ....db import Table
|
|
@@ -43,7 +44,10 @@ class ConditionalSet:
|
|
|
43
44
|
# If a constant was found, we store the value and replace it with a random attribute name.
|
|
44
45
|
constant = le.names[i]
|
|
45
46
|
new_token = Token.random(constant)
|
|
46
|
-
new_operand = DCOperand(le.relation,
|
|
47
|
+
new_operand = DCOperand(le.relation,
|
|
48
|
+
le.names[:i] + (new_token,) + le.names[i + 1:],
|
|
49
|
+
skip_comma=True,
|
|
50
|
+
depth=le.depth)
|
|
47
51
|
|
|
48
52
|
# We now need an equality comparison to ensure the introduced attribute is equal to the constant.
|
|
49
53
|
equality = Equal(
|
|
@@ -121,7 +125,8 @@ class ConditionalSet:
|
|
|
121
125
|
# Raise an exception if the given number of operands does not match
|
|
122
126
|
# the number of attributes in the relation.
|
|
123
127
|
if len(source_columns) != len(operand.names):
|
|
124
|
-
raise
|
|
128
|
+
raise DCParserError(f'invalid number of attributes for relation {operand.relation}',
|
|
129
|
+
depth=operand.depth)
|
|
125
130
|
|
|
126
131
|
# Create a column list for this operand.
|
|
127
132
|
rcl: RenamableColumnList = RenamableColumnList.from_iter(source_columns)
|
|
@@ -215,7 +220,8 @@ class ConditionalSet:
|
|
|
215
220
|
if left_name != right_name:
|
|
216
221
|
break
|
|
217
222
|
else:
|
|
218
|
-
raise
|
|
223
|
+
raise DCParserError(f'could not build join for relation {left_name}',
|
|
224
|
+
depth=left_op.depth)
|
|
219
225
|
|
|
220
226
|
join_tuple = min(left_name, right_name), max(left_name, right_name)
|
|
221
227
|
|
|
@@ -253,7 +259,7 @@ class ConditionalSet:
|
|
|
253
259
|
|
|
254
260
|
# If no joins were discovered using this table, an exception is raised.
|
|
255
261
|
if discovered_joins == 0:
|
|
256
|
-
raise
|
|
262
|
+
raise DCParserError('no common attributes found for join', depth=right_op.depth)
|
|
257
263
|
|
|
258
264
|
# The joins have to be sorted in a topologic order starting from t0.
|
|
259
265
|
used_relations: Set[str] = {'t0'}
|
|
@@ -287,7 +293,8 @@ class ConditionalSet:
|
|
|
287
293
|
break
|
|
288
294
|
|
|
289
295
|
else:
|
|
290
|
-
raise
|
|
296
|
+
raise DCParserError('no valid topologic order found for positive joins',
|
|
297
|
+
depth=min(op.depth for _, _, op in relevant_positive))
|
|
291
298
|
|
|
292
299
|
all_negative_conditions: Dict[str, List[str]] = {}
|
|
293
300
|
all_negative_filters: Dict[str, List[str]] = {}
|
|
@@ -317,7 +324,8 @@ class ConditionalSet:
|
|
|
317
324
|
used_relations.add(target_name)
|
|
318
325
|
break
|
|
319
326
|
else:
|
|
320
|
-
raise
|
|
327
|
+
raise DCParserError('no valid topologic order found for negative joins',
|
|
328
|
+
depth=min(op.depth for _, _, op in relevant_negative))
|
|
321
329
|
|
|
322
330
|
# Build the SQL statement.
|
|
323
331
|
sql_select = ', '.join(select_columns[col] if col in select_columns else col
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# see https://www.postgresql.org/docs/current/sql-commands.html
|
|
2
|
+
SQL_KEYWORDS = ('ABORT', 'ALTER', 'ANALYZE', 'BEGIN', 'CALL', 'CHECKPOINT', 'CLOSE', 'CLUSTER', 'COMMENT', 'COMMIT',
|
|
3
|
+
'COPY', 'CREATE', 'DEALLOCATE', 'DECLARE', 'DELETE', 'DISCARD', 'DO', 'DROP', 'END', 'EXECUTE',
|
|
4
|
+
'EXPLAIN', 'FETCH', 'GRANT', 'IMPORT', 'INSERT', 'LISTEN', 'LOAD', 'LOCK', 'MERGE', 'MOVE', 'NOTIFY',
|
|
5
|
+
'PREPARE', 'REASSIGN', 'REFRESH', 'REINDEX', 'RELEASE', 'RESET', 'REVOKE', 'ROLLBACK', 'SAVEPOINT',
|
|
6
|
+
'SECURITY', 'SELECT', 'SET', 'SHOW', 'START', 'TRUNCATE', 'UNLISTEN', 'UPDATE', 'VACUUM', 'VALUES')
|
|
@@ -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.6
|
|
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
|
|
@@ -262,7 +262,7 @@ magic command `RA` activates the relational algebra mode for a single cell:
|
|
|
262
262
|
|
|
263
263
|
```
|
|
264
264
|
%RA
|
|
265
|
-
π a, b (σ c = 1 (R))
|
|
265
|
+
π [a, b] (σ [c = 1] (R))
|
|
266
266
|
```
|
|
267
267
|
|
|
268
268
|
The supported operations are:
|
|
@@ -294,3 +294,13 @@ magic command `DC` activates the domain calculus mode for a single cell:
|
|
|
294
294
|
%DC
|
|
295
295
|
{ a, b | R(a, b, c) ∧ c = 1 }
|
|
296
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
|
+
|
|
@@ -24,9 +24,11 @@ src/duckdb_kernel/magics/MagicCommand.py
|
|
|
24
24
|
src/duckdb_kernel/magics/MagicCommandCallback.py
|
|
25
25
|
src/duckdb_kernel/magics/MagicCommandException.py
|
|
26
26
|
src/duckdb_kernel/magics/MagicCommandHandler.py
|
|
27
|
+
src/duckdb_kernel/magics/StringWrapper.py
|
|
27
28
|
src/duckdb_kernel/magics/__init__.py
|
|
28
29
|
src/duckdb_kernel/parser/DCParser.py
|
|
29
30
|
src/duckdb_kernel/parser/LogicParser.py
|
|
31
|
+
src/duckdb_kernel/parser/ParserError.py
|
|
30
32
|
src/duckdb_kernel/parser/RAParser.py
|
|
31
33
|
src/duckdb_kernel/parser/__init__.py
|
|
32
34
|
src/duckdb_kernel/parser/elements/DCOperand.py
|
|
@@ -72,6 +74,7 @@ src/duckdb_kernel/parser/util/RenamableColumn.py
|
|
|
72
74
|
src/duckdb_kernel/parser/util/RenamableColumnList.py
|
|
73
75
|
src/duckdb_kernel/parser/util/__init__.py
|
|
74
76
|
src/duckdb_kernel/util/ResultSetComparator.py
|
|
77
|
+
src/duckdb_kernel/util/SQL.py
|
|
75
78
|
src/duckdb_kernel/util/TestError.py
|
|
76
79
|
src/duckdb_kernel/util/__init__.py
|
|
77
80
|
src/duckdb_kernel/util/formatting.py
|
|
@@ -86,4 +89,5 @@ src/jupyter_duckdb.egg-info/requires.txt
|
|
|
86
89
|
src/jupyter_duckdb.egg-info/top_level.txt
|
|
87
90
|
test/test_dc.py
|
|
88
91
|
test/test_ra.py
|
|
89
|
-
test/test_result_comparison.py
|
|
92
|
+
test/test_result_comparison.py
|
|
93
|
+
test/test_sql.py
|
|
@@ -2,7 +2,7 @@ import pytest
|
|
|
2
2
|
|
|
3
3
|
import duckdb_kernel.parser.elements.binary as BinaryOperators
|
|
4
4
|
import duckdb_kernel.parser.elements.unary as UnaryOperators
|
|
5
|
-
from duckdb_kernel.parser import RAParser
|
|
5
|
+
from duckdb_kernel.parser import RAParser, RAParserError
|
|
6
6
|
from duckdb_kernel.parser.elements import RAOperand, LogicElement
|
|
7
7
|
from . import Connection
|
|
8
8
|
|
|
@@ -644,7 +644,7 @@ def test_special_queries():
|
|
|
644
644
|
with Connection() as con:
|
|
645
645
|
# Consecutive operators triggered a recursion error in a previous
|
|
646
646
|
# version, leading to an infinite loop / stack overflow.
|
|
647
|
-
with pytest.raises(
|
|
647
|
+
with pytest.raises(RAParserError, match='right operand missing after x'):
|
|
648
648
|
RAParser.parse_query(r'''
|
|
649
649
|
Users x x BannedUsers
|
|
650
650
|
''')
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from duckdb_kernel.db.error import EmptyResultError
|
|
4
|
+
from . import Connection
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_simple_queries():
|
|
8
|
+
query = 'SELECT Username FROM Users'
|
|
9
|
+
|
|
10
|
+
with Connection() as con:
|
|
11
|
+
assert con.execute_sql(query) == [
|
|
12
|
+
('Alice',),
|
|
13
|
+
('Bob',),
|
|
14
|
+
('Charlie',)
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_empty_result():
|
|
19
|
+
with Connection() as con:
|
|
20
|
+
query = "SELECT Username FROM Users WHERE Username = 'abcdef'"
|
|
21
|
+
assert con.execute_sql(query) == []
|
|
22
|
+
|
|
23
|
+
with Connection() as con:
|
|
24
|
+
query = 'CREATE TABLE foo (bar INTEGER PRIMARY KEY)'
|
|
25
|
+
try:
|
|
26
|
+
assert con.execute_sql(query) == []
|
|
27
|
+
except EmptyResultError:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_empty_queries():
|
|
32
|
+
for query in [
|
|
33
|
+
'',
|
|
34
|
+
' ',
|
|
35
|
+
'\n',
|
|
36
|
+
'-- this is an empty query too'
|
|
37
|
+
]:
|
|
38
|
+
with pytest.raises(EmptyResultError):
|
|
39
|
+
with Connection() as con:
|
|
40
|
+
con.execute_sql(query)
|
|
41
|
+
|
|
42
|
+
with pytest.raises(Exception):
|
|
43
|
+
with Connection() as con:
|
|
44
|
+
query = '-- this is a query with syntax errors\nFOR foo IN bar'
|
|
45
|
+
try:
|
|
46
|
+
con.execute_sql(query)
|
|
47
|
+
except EmptyResultError:
|
|
48
|
+
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/db/error/EmptyResultError.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/magics/MagicCommandException.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/LogicElement.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/LogicOperand.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/LogicOperator.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/RAElement.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/RAOperand.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/RAOperator.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/__init__.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Add.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/And.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Cross.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Divide.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Equal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Join.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Minus.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Or.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/binary/Union.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/unary/Not.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/elements/unary/Rename.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/tokenizer/Token.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/tokenizer/__init__.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/parser/util/RenamableColumn.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/util/ResultSetComparator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/visualization/RATreeDrawer.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/visualization/SchemaDrawer.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/duckdb_kernel/visualization/__init__.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.6}/src/jupyter_duckdb.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|