jupyter-duckdb 1.2.0.4__py3-none-any.whl → 1.2.0.5__py3-none-any.whl
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.
- duckdb_kernel/kernel.py +45 -3
- duckdb_kernel/magics/MagicCommand.py +34 -10
- duckdb_kernel/magics/MagicCommandCallback.py +14 -3
- duckdb_kernel/magics/MagicCommandHandler.py +47 -6
- duckdb_kernel/magics/StringWrapper.py +3 -0
- duckdb_kernel/parser/DCParser.py +10 -7
- duckdb_kernel/parser/LogicParser.py +6 -6
- duckdb_kernel/parser/ParserError.py +18 -0
- duckdb_kernel/parser/RAParser.py +12 -14
- duckdb_kernel/parser/__init__.py +1 -0
- duckdb_kernel/parser/elements/DCOperand.py +7 -4
- duckdb_kernel/parser/elements/binary/ConditionalSet.py +14 -6
- {jupyter_duckdb-1.2.0.4.dist-info → jupyter_duckdb-1.2.0.5.dist-info}/METADATA +12 -2
- {jupyter_duckdb-1.2.0.4.dist-info → jupyter_duckdb-1.2.0.5.dist-info}/RECORD +16 -14
- {jupyter_duckdb-1.2.0.4.dist-info → jupyter_duckdb-1.2.0.5.dist-info}/WHEEL +0 -0
- {jupyter_duckdb-1.2.0.4.dist-info → jupyter_duckdb-1.2.0.5.dist-info}/top_level.txt +0 -0
duckdb_kernel/kernel.py
CHANGED
|
@@ -12,7 +12,7 @@ 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
17
|
from .util.TestError import TestError
|
|
18
18
|
from .util.formatting import row_count, rows_table, wrap_image
|
|
@@ -46,8 +46,12 @@ class DuckDBKernel(Kernel):
|
|
|
46
46
|
MagicCommand('query_max_rows').arg('count').on(self._query_max_rows_magic),
|
|
47
47
|
MagicCommand('schema').flag('td').opt('only').on(self._schema_magic),
|
|
48
48
|
MagicCommand('store').arg('file').flag('noheader').result(True).on(self._store_magic),
|
|
49
|
-
MagicCommand('ra').flag('analyze').code(True).on(self._ra_magic),
|
|
50
|
-
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),
|
|
51
55
|
)
|
|
52
56
|
|
|
53
57
|
# create placeholders for database and tests
|
|
@@ -496,6 +500,15 @@ class DuckDBKernel(Kernel):
|
|
|
496
500
|
'generated_code': sql
|
|
497
501
|
}
|
|
498
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
|
+
|
|
499
512
|
def _dc_magic(self, silent: bool, code: str):
|
|
500
513
|
if self._db is None:
|
|
501
514
|
raise AssertionError('load a database first')
|
|
@@ -520,6 +533,35 @@ class DuckDBKernel(Kernel):
|
|
|
520
533
|
'column_name_mapping': cnm
|
|
521
534
|
}
|
|
522
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
|
+
|
|
523
565
|
# jupyter related functions
|
|
524
566
|
def do_execute(self, code: str, silent: bool,
|
|
525
567
|
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))
|
|
@@ -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)
|
|
@@ -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
|
duckdb_kernel/parser/DCParser.py
CHANGED
|
@@ -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
|
duckdb_kernel/parser/RAParser.py
CHANGED
|
@@ -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])
|
duckdb_kernel/parser/__init__.py
CHANGED
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
duckdb_kernel/__init__.py,sha256=6auU6zeJrsA4fxPSr2PYamS8fG-SMXTn5YQFXF2cseo,33
|
|
2
2
|
duckdb_kernel/__main__.py,sha256=Z3GwHEBWoQjNm2Y84ijnbA0Lk66L7nsFREuqhZ_ptk0,165
|
|
3
3
|
duckdb_kernel/kernel.json,sha256=_7E8Ci2FSdCvnzCjsOaue8QE8AvpS5JLQuxORO5IGtA,127
|
|
4
|
-
duckdb_kernel/kernel.py,sha256=
|
|
4
|
+
duckdb_kernel/kernel.py,sha256=4OFNtWvrfSs27udrk9e1ClBMdltvLA9gJemSBZskj0k,21827
|
|
5
5
|
duckdb_kernel/db/Column.py,sha256=GM5P6sFdlYK92hiKln5-6038gIDOTxh1AYbR4kiga_w,559
|
|
6
6
|
duckdb_kernel/db/Connection.py,sha256=5pH-CwGh-r9Q2QwJKGSxvoINBU-sqmvZyG4Q1digfeE,599
|
|
7
7
|
duckdb_kernel/db/Constraint.py,sha256=1YgUHk7s8mHCVedbcuJKyXDykj7_ybbwT3Dk9p2VMis,287
|
|
@@ -18,16 +18,18 @@ duckdb_kernel/db/implementation/postgres/__init__.py,sha256=HKogB1es4wOiQUoh7_eT
|
|
|
18
18
|
duckdb_kernel/db/implementation/postgres/util.py,sha256=4nr1mqXhlwkMVXbJSfJ7dRlUm6UskpvgKApe7GRwmBI,281
|
|
19
19
|
duckdb_kernel/db/implementation/sqlite/Connection.py,sha256=L-aSUuN_7Z-hc5wczLjap2Xu-Vv6-dXmwXcN_WE8o98,6994
|
|
20
20
|
duckdb_kernel/db/implementation/sqlite/__init__.py,sha256=HKogB1es4wOiQUoh7_eT32xnUFLmzoCyR_0LuY9r8YQ,35
|
|
21
|
-
duckdb_kernel/magics/MagicCommand.py,sha256=
|
|
22
|
-
duckdb_kernel/magics/MagicCommandCallback.py,sha256=
|
|
21
|
+
duckdb_kernel/magics/MagicCommand.py,sha256=l0EmnqgGZ0HyqQhdTljAaftflVo_RYp-U5UiDftYxAA,3180
|
|
22
|
+
duckdb_kernel/magics/MagicCommandCallback.py,sha256=9XVidNtzs8Lwq_T59VrH89t5LUgJcc5C7grusElXVW4,1041
|
|
23
23
|
duckdb_kernel/magics/MagicCommandException.py,sha256=MwuWkpA6NoCqz437urdI0RVXhbSbVdziuRoi7slYFPc,49
|
|
24
|
-
duckdb_kernel/magics/MagicCommandHandler.py,sha256=
|
|
24
|
+
duckdb_kernel/magics/MagicCommandHandler.py,sha256=TCCtAEaop5yTUn2af4wzcReEto3nql-40-DpIdIjKEw,4367
|
|
25
|
+
duckdb_kernel/magics/StringWrapper.py,sha256=W6qIfeHU51do1edd6yXgw6K7orzjwSHU4oWAI5DuKEE,96
|
|
25
26
|
duckdb_kernel/magics/__init__.py,sha256=DA8gnQeRCUt1Scy3_NQ9w5CPmMEY9i8YwB-g392pN1U,204
|
|
26
|
-
duckdb_kernel/parser/DCParser.py,sha256=
|
|
27
|
-
duckdb_kernel/parser/LogicParser.py,sha256=
|
|
28
|
-
duckdb_kernel/parser/
|
|
29
|
-
duckdb_kernel/parser/
|
|
30
|
-
duckdb_kernel/parser/
|
|
27
|
+
duckdb_kernel/parser/DCParser.py,sha256=16c1mxa494KP9OreUKQHsSQKoDGZ7NNp2u_gi_D-dkw,2293
|
|
28
|
+
duckdb_kernel/parser/LogicParser.py,sha256=_vZwE5OPRUEN8aEC_fSZAYKR_dpexqNthXog9OFHYRY,1233
|
|
29
|
+
duckdb_kernel/parser/ParserError.py,sha256=qJQVloFtID1HgVDQ1Io247bODT1ic3oO9Z1ZrWR-2Mk,321
|
|
30
|
+
duckdb_kernel/parser/RAParser.py,sha256=dL5Q5J8tmlGuKQMfadvJK6cb9KmzfZLkfcUAthqDmXI,2993
|
|
31
|
+
duckdb_kernel/parser/__init__.py,sha256=nTmDm1ADvNPDHhVJQLxKYmArNJk6967EUXqn5AkT8FM,126
|
|
32
|
+
duckdb_kernel/parser/elements/DCOperand.py,sha256=qEg_6Us4WV1eK4Bq6oUsmFt_L_x5pJPGce_wSapzIYA,1149
|
|
31
33
|
duckdb_kernel/parser/elements/LogicElement.py,sha256=YasKHxWLDDP8UdyLIKbXzqIRA8-XaakjmvTj-1Iuzyc,280
|
|
32
34
|
duckdb_kernel/parser/elements/LogicOperand.py,sha256=B9NvriloQE5eP734dNMZBZwrdaaIfsuAmZlG1t2eMhs,1021
|
|
33
35
|
duckdb_kernel/parser/elements/LogicOperator.py,sha256=lkM4TAGkXUhsO4w4PLKVA0bgCRGPQQFpNA1FcWWOW9Q,1028
|
|
@@ -40,7 +42,7 @@ duckdb_kernel/parser/elements/__init__.py,sha256=4DA2M43hh9d1fZb5Z6YnTTI-IBkDyhC
|
|
|
40
42
|
duckdb_kernel/parser/elements/binary/Add.py,sha256=XGkZMfab01huk9EaI6JUfzkd2STbV1C_-TyC2guKE8I,190
|
|
41
43
|
duckdb_kernel/parser/elements/binary/And.py,sha256=0jgetTG8yo5TJSeK70Kj-PI9ERyek1eyMQXX5HBxa4Y,274
|
|
42
44
|
duckdb_kernel/parser/elements/binary/ArrowLeft.py,sha256=u4fZSoyT9lfvWXBwuhUl4DdjVZAOqyVIKmMVbpElLD4,203
|
|
43
|
-
duckdb_kernel/parser/elements/binary/ConditionalSet.py,sha256=
|
|
45
|
+
duckdb_kernel/parser/elements/binary/ConditionalSet.py,sha256=sZ3qrxPux7pb3fMrlyBg4Hw7n4-Ln-AeN70_Jp5dAPo,17652
|
|
44
46
|
duckdb_kernel/parser/elements/binary/Cross.py,sha256=jVY3cvD6qDWZkJ7q74lFUPO2VdDt4aAjdk2YAfg-ZC4,687
|
|
45
47
|
duckdb_kernel/parser/elements/binary/Difference.py,sha256=ZVRgJHYVMOFwnc97oPvGtKvLvHsjSCsn2Aao6ymxY8Y,742
|
|
46
48
|
duckdb_kernel/parser/elements/binary/Divide.py,sha256=d7mzaOeRYSRO1F-2IHsv_C939TuYtLppbf4-5GSRJXs,265
|
|
@@ -77,7 +79,7 @@ duckdb_kernel/visualization/Drawer.py,sha256=D0LkiGMvuJ2v6cQSg_axLTGaM4VXAJEQJAy
|
|
|
77
79
|
duckdb_kernel/visualization/RATreeDrawer.py,sha256=j-Vy1zpYMzwZ3CsphyfPW-J7ou9a9tM6aXXgAlQTgDI,2128
|
|
78
80
|
duckdb_kernel/visualization/SchemaDrawer.py,sha256=9K-TUUmyeGdMYMTFQJ7evIU3p8p2KyMKeizUc7-y8co,3015
|
|
79
81
|
duckdb_kernel/visualization/__init__.py,sha256=5eMJmxJ01XAXcgWDn3t70eSZF2PGaXdNo6GK-x-0H3s,78
|
|
80
|
-
jupyter_duckdb-1.2.0.
|
|
81
|
-
jupyter_duckdb-1.2.0.
|
|
82
|
-
jupyter_duckdb-1.2.0.
|
|
83
|
-
jupyter_duckdb-1.2.0.
|
|
82
|
+
jupyter_duckdb-1.2.0.5.dist-info/METADATA,sha256=7twgLgE-u5--Y9lV-JCtqPoUFSL8GAwdPyVcmu1NzjA,9115
|
|
83
|
+
jupyter_duckdb-1.2.0.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
84
|
+
jupyter_duckdb-1.2.0.5.dist-info/top_level.txt,sha256=KvRRPMnmkQNuhyBsXoPmwyt26LRDp0O-0HN6u0Dm5jA,14
|
|
85
|
+
jupyter_duckdb-1.2.0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|