jupyter-duckdb 1.4.106__tar.gz → 1.4.108__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.4.106 → jupyter_duckdb-1.4.108}/PKG-INFO +1 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/setup.py +1 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/kernel.py +4 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/DCParser.py +7 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/RAParser.py +9 -8
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -2
- jupyter_duckdb-1.4.108/src/duckdb_kernel/parser/elements/RARelationReference.py +86 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +6 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/__init__.py +1 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Cross.py +1 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/FullOuterJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Join.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/LeftOuterJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/LeftSemiJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/RightOuterJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/RightSemiJoin.py +4 -1
- jupyter_duckdb-1.4.106/src/duckdb_kernel/parser/elements/unary/Rename.py → jupyter_duckdb-1.4.108/src/duckdb_kernel/parser/elements/unary/AttributeRename.py +4 -4
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/unary/Projection.py +1 -1
- jupyter_duckdb-1.4.108/src/duckdb_kernel/parser/elements/unary/Rename.py +93 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/unary/__init__.py +2 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/tests/test_dc.py +29 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/tests/test_ra.py +374 -38
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/util/ResultSetComparator.py +18 -3
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/jupyter_duckdb.egg-info/PKG-INFO +1 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/jupyter_duckdb.egg-info/SOURCES.txt +2 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/README.md +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/setup.cfg +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/__main__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/Column.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/Connection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/Constraint.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/DatabaseError.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/ForeignKey.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/Table.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/error/EmptyResultError.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/error/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/implementation/duckdb/Connection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/implementation/duckdb/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/implementation/postgres/Connection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/implementation/postgres/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/implementation/postgres/util.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/implementation/sqlite/Connection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/db/implementation/sqlite/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/kernel.json +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/magics/MagicCommand.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/magics/MagicCommandCallback.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/magics/MagicCommandHandler.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/magics/MagicState.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/magics/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/LogicParser.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/ParserError.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/DCOperand.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/RAElement.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/RAOperand.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Division.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/tokenizer/Token.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/util/QuerySplitter.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/util/RenamableColumnList.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/util/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/tests/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/tests/test_result_comparison.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/tests/test_sql.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/util/SQL.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/util/TestError.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/util/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/util/formatting.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/Drawer.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/Plotly.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/lib/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/lib/plotly-3.0.1.min.js +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/lib/ra.css +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/visualization/lib/ra.js +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/jupyter_duckdb.egg-info/top_level.txt +0 -0
|
@@ -535,6 +535,8 @@ class DuckDBKernel(Kernel):
|
|
|
535
535
|
|
|
536
536
|
# parse ra input
|
|
537
537
|
root_node = RAParser.parse_query(state.code)
|
|
538
|
+
if root_node is None:
|
|
539
|
+
return
|
|
538
540
|
|
|
539
541
|
# create and show visualization
|
|
540
542
|
if analyze:
|
|
@@ -575,6 +577,8 @@ class DuckDBKernel(Kernel):
|
|
|
575
577
|
|
|
576
578
|
# parse dc input
|
|
577
579
|
root_node = DCParser.parse_query(state.code)
|
|
580
|
+
if root_node is None:
|
|
581
|
+
return
|
|
578
582
|
|
|
579
583
|
# generate sql
|
|
580
584
|
sql, cnm = root_node.to_sql_with_renamed_columns(tables)
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
from .ParserError import DCParserError
|
|
2
2
|
from .elements import *
|
|
3
3
|
from .tokenizer import *
|
|
4
|
+
from .util.QuerySplitter import get_last_query
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class DCParser:
|
|
7
8
|
@staticmethod
|
|
8
9
|
def parse_query(query: str) -> DC_SET:
|
|
10
|
+
# remove comments from query
|
|
11
|
+
query = get_last_query(query, split_at=None, remove_comments=True)
|
|
12
|
+
|
|
9
13
|
# create initial token set
|
|
10
14
|
initial_token = Token(query)
|
|
11
15
|
tokens = tuple(Tokenizer.tokenize(initial_token))
|
|
12
16
|
|
|
17
|
+
if len(tokens) == 0:
|
|
18
|
+
return None
|
|
19
|
+
|
|
13
20
|
# split at |
|
|
14
21
|
for i, token in enumerate(tokens):
|
|
15
22
|
if token in DC_SET.symbols():
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from .LogicParser import LogicParser
|
|
2
1
|
from .ParserError import RAParserError
|
|
3
2
|
from .elements import *
|
|
4
3
|
from .tokenizer import *
|
|
@@ -10,7 +9,7 @@ from .util.QuerySplitter import get_last_query
|
|
|
10
9
|
|
|
11
10
|
class RAParser:
|
|
12
11
|
@staticmethod
|
|
13
|
-
def parse_query(query: str) -> RAElement:
|
|
12
|
+
def parse_query(query: str) -> RAElement | None:
|
|
14
13
|
# remove comments from query
|
|
15
14
|
query = get_last_query(query, split_at=None, remove_comments=True)
|
|
16
15
|
|
|
@@ -19,7 +18,7 @@ class RAParser:
|
|
|
19
18
|
return RAParser.parse_tokens(initial_token, depth=0)
|
|
20
19
|
|
|
21
20
|
@staticmethod
|
|
22
|
-
def parse_tokens(*tokens: Token, target: RAOperator | RAOperand = None, depth: int = 0) -> RAElement:
|
|
21
|
+
def parse_tokens(*tokens: Token, target: RAOperator | RAOperand = None, depth: int = 0) -> RAElement | None:
|
|
23
22
|
if len(tokens) == 1:
|
|
24
23
|
tokens = tuple(Tokenizer.tokenize(tokens[0]))
|
|
25
24
|
|
|
@@ -55,7 +54,7 @@ class RAParser:
|
|
|
55
54
|
if target is None:
|
|
56
55
|
op = operator(
|
|
57
56
|
RAParser.parse_tokens(tokens[-1], depth=depth + 1),
|
|
58
|
-
|
|
57
|
+
operator.parse_args(*tokens[-i + 1:-1], depth=depth + 1)
|
|
59
58
|
)
|
|
60
59
|
|
|
61
60
|
# Otherwise the handed target is this operator's
|
|
@@ -63,7 +62,7 @@ class RAParser:
|
|
|
63
62
|
else:
|
|
64
63
|
op = operator(
|
|
65
64
|
target,
|
|
66
|
-
|
|
65
|
+
operator.parse_args(*tokens[-i + 1:], depth=depth + 1)
|
|
67
66
|
)
|
|
68
67
|
|
|
69
68
|
# If there are any more tokens the operator is
|
|
@@ -76,7 +75,9 @@ class RAParser:
|
|
|
76
75
|
return op
|
|
77
76
|
|
|
78
77
|
# return as name
|
|
79
|
-
if len(tokens)
|
|
78
|
+
if len(tokens) == 0:
|
|
79
|
+
return None
|
|
80
|
+
elif len(tokens) == 1:
|
|
81
|
+
return RAOperand(tokens[0])
|
|
82
|
+
else:
|
|
80
83
|
raise RAParserError(f'{tokens=}', depth)
|
|
81
|
-
|
|
82
|
-
return RAOperand(tokens[0])
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from . import RAUnaryOperator
|
|
4
|
+
from .LogicElement import LogicElement
|
|
5
|
+
from ..ParserError import RAParserError
|
|
6
|
+
from ..tokenizer import Token
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RARelationReference(LogicElement):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def parse_tokens(operator: type[RAUnaryOperator], *tokens: Token, depth: int = 0) -> 'RARelationReference':
|
|
12
|
+
try:
|
|
13
|
+
# If we get one single token, it should be like
|
|
14
|
+
# R -> "R"
|
|
15
|
+
# [ R ] -> "R"
|
|
16
|
+
# [ R(A, B, C) ] -> "R(A, B, C)"
|
|
17
|
+
# (A, B, C) -> "(A, B, C")
|
|
18
|
+
# [ (A, B, C) ] -> "(A, B, C)"
|
|
19
|
+
if len(tokens) == 1:
|
|
20
|
+
return RARelationReference._parse_one_token(*tokens)
|
|
21
|
+
|
|
22
|
+
# If we get two tokens, it should be like
|
|
23
|
+
# R(A, B, C) -> "R", "A, B, C"
|
|
24
|
+
# R A -> "R", "A"
|
|
25
|
+
# (The latter equals R(A), but we should think about rejecting this type.)
|
|
26
|
+
elif len(tokens) == 2:
|
|
27
|
+
return RARelationReference._parse_two_tokens(*tokens)
|
|
28
|
+
|
|
29
|
+
# Otherwise, the input is malformed.
|
|
30
|
+
else:
|
|
31
|
+
raise AssertionError()
|
|
32
|
+
|
|
33
|
+
except AssertionError:
|
|
34
|
+
raise RAParserError(f'malformed input for operator {operator.symbols()[0]} {tokens=}', depth=depth)
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _parse_one_token(token: Token) -> 'RARelationReference':
|
|
38
|
+
match = re.fullmatch(r'^\s*([A-Za-z0-9]+)?\s*(\(?((\s*[A-Za-z0-9]+\s*,\s*)*(\s*[A-Za-z0-9]+\s*,?\s*))\)?)?\s*$', token)
|
|
39
|
+
if match is None:
|
|
40
|
+
raise AssertionError()
|
|
41
|
+
|
|
42
|
+
if match.group(1) is not None:
|
|
43
|
+
relation = match.group(1).strip()
|
|
44
|
+
else:
|
|
45
|
+
relation = None
|
|
46
|
+
|
|
47
|
+
if match.group(3) is not None:
|
|
48
|
+
attributes = [b for b in (a.strip() for a in match.group(3).split(',')) if b != '']
|
|
49
|
+
else:
|
|
50
|
+
attributes = None
|
|
51
|
+
|
|
52
|
+
if relation is None and attributes is None:
|
|
53
|
+
raise AssertionError()
|
|
54
|
+
|
|
55
|
+
return RARelationReference(relation, attributes)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _parse_two_tokens(token1: Token, token2: Token) -> 'RARelationReference':
|
|
59
|
+
# We expect the first token to be a relation name and the second one
|
|
60
|
+
# to be a list of column names separated by commas.
|
|
61
|
+
relation = token1.strip()
|
|
62
|
+
attributes = [b for b in (a.strip() for a in token2.split(',')) if b != '']
|
|
63
|
+
|
|
64
|
+
return RARelationReference(relation, attributes)
|
|
65
|
+
|
|
66
|
+
def __init__(self, relation: str | None, attributes: list[str] | None):
|
|
67
|
+
# check duplicated attributes
|
|
68
|
+
if attributes is not None:
|
|
69
|
+
for i in range(len(attributes)):
|
|
70
|
+
for k in range(i + 1, len(attributes)):
|
|
71
|
+
if attributes[i] == attributes[k]:
|
|
72
|
+
raise RAParserError(f'duplicate attribute {attributes[i]}', 0)
|
|
73
|
+
if attributes[i].lower() == attributes[k].lower():
|
|
74
|
+
raise RAParserError(f'duplicate attribute {attributes[i]}={attributes[k]}', 0)
|
|
75
|
+
|
|
76
|
+
# store
|
|
77
|
+
self.relation: str | None = relation
|
|
78
|
+
self.attributes: list[str] | None = attributes
|
|
79
|
+
|
|
80
|
+
def __str__(self) -> str:
|
|
81
|
+
if self.relation is not None and self.attributes is None:
|
|
82
|
+
return self.relation
|
|
83
|
+
elif self.relation is None and self.attributes is not None:
|
|
84
|
+
return f'({", ".join(self.attributes)})'
|
|
85
|
+
else:
|
|
86
|
+
return f'{self.relation}({", ".join(self.attributes)})'
|
|
@@ -3,9 +3,15 @@ from typing import Iterator
|
|
|
3
3
|
from .LogicElement import LogicElement
|
|
4
4
|
from .RAElement import RAElement
|
|
5
5
|
from .RAOperator import RAOperator
|
|
6
|
+
from ..tokenizer import Token
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class RAUnaryOperator(RAOperator):
|
|
10
|
+
@classmethod
|
|
11
|
+
def parse_args(cls: type['RAUnaryOperator'], *tokens: Token, depth: int):
|
|
12
|
+
from .. import LogicParser
|
|
13
|
+
return LogicParser.parse_tokens(*tokens, depth=depth)
|
|
14
|
+
|
|
9
15
|
def __init__(self, target: RAElement):
|
|
10
16
|
self.target: RAElement = target
|
|
11
17
|
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/__init__.py
RENAMED
|
@@ -11,6 +11,7 @@ from .RAUnaryOperator import RAUnaryOperator
|
|
|
11
11
|
from .RAOperand import RAOperand
|
|
12
12
|
from .binary import RA_BINARY_OPERATORS, RA_BINARY_SYMBOLS
|
|
13
13
|
from .unary import RA_UNARY_OPERATORS
|
|
14
|
+
from .RARelationReference import RARelationReference
|
|
14
15
|
|
|
15
16
|
from .DCOperand import DCOperand
|
|
16
17
|
from .binary import DC_SET
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Cross.py
RENAMED
|
@@ -19,4 +19,4 @@ class Cross(RABinaryOperator):
|
|
|
19
19
|
cols = lcols.merge(rcols)
|
|
20
20
|
|
|
21
21
|
# create statement
|
|
22
|
-
return f'SELECT {cols.list} FROM ({lq}) {self._name()} CROSS JOIN ({rq}) {self._name()}', cols
|
|
22
|
+
return f'SELECT DISTINCT {cols.list} FROM ({lq}) {self._name()} CROSS JOIN ({rq}) {self._name()}', cols
|
|
@@ -3,6 +3,7 @@ from typing import Tuple, Dict
|
|
|
3
3
|
|
|
4
4
|
from duckdb_kernel.db import Table
|
|
5
5
|
from ..RABinaryOperator import RABinaryOperator
|
|
6
|
+
from ...ParserError import RAParserError
|
|
6
7
|
from ...util.RenamableColumn import RenamableColumn
|
|
7
8
|
from ...util.RenamableColumnList import RenamableColumnList
|
|
8
9
|
|
|
@@ -26,6 +27,8 @@ class FullOuterJoin(RABinaryOperator):
|
|
|
26
27
|
|
|
27
28
|
# find matching columns
|
|
28
29
|
join_cols, all_cols = lcols.intersect(rcols)
|
|
30
|
+
if len(join_cols) == 0:
|
|
31
|
+
raise RAParserError('no common attributes found for full outer join', 0)
|
|
29
32
|
|
|
30
33
|
replacements = {c1: c2 for c1, c2 in join_cols}
|
|
31
34
|
select_cols = [self._coalesce(c, replacements.get(c)) for c in all_cols]
|
|
@@ -34,4 +37,4 @@ class FullOuterJoin(RABinaryOperator):
|
|
|
34
37
|
on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
|
|
35
38
|
|
|
36
39
|
# create sql
|
|
37
|
-
return f'SELECT {select_clause} FROM ({lq}) {self._name()} FULL OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
|
|
40
|
+
return f'SELECT DISTINCT {select_clause} FROM ({lq}) {self._name()} FULL OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.108}/src/duckdb_kernel/parser/elements/binary/Join.py
RENAMED
|
@@ -2,6 +2,7 @@ from typing import Tuple, Dict
|
|
|
2
2
|
|
|
3
3
|
from duckdb_kernel.db import Table
|
|
4
4
|
from ..RABinaryOperator import RABinaryOperator
|
|
5
|
+
from ...ParserError import RAParserError
|
|
5
6
|
from ...util.RenamableColumnList import RenamableColumnList
|
|
6
7
|
|
|
7
8
|
|
|
@@ -17,8 +18,10 @@ class Join(RABinaryOperator):
|
|
|
17
18
|
|
|
18
19
|
# find matching columns
|
|
19
20
|
join_cols, all_cols = lcols.intersect(rcols)
|
|
21
|
+
if len(join_cols) == 0:
|
|
22
|
+
raise RAParserError('no common attributes found for join', 0)
|
|
20
23
|
|
|
21
24
|
on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
|
|
22
25
|
|
|
23
26
|
# create sql
|
|
24
|
-
return f'SELECT {all_cols.list} FROM ({lq}) {self._name()} JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
|
|
27
|
+
return f'SELECT DISTINCT {all_cols.list} FROM ({lq}) {self._name()} JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
|
|
@@ -2,6 +2,7 @@ from typing import Tuple, Dict
|
|
|
2
2
|
|
|
3
3
|
from duckdb_kernel.db import Table
|
|
4
4
|
from ..RABinaryOperator import RABinaryOperator
|
|
5
|
+
from ...ParserError import RAParserError
|
|
5
6
|
from ...util.RenamableColumnList import RenamableColumnList
|
|
6
7
|
|
|
7
8
|
|
|
@@ -17,8 +18,10 @@ class LeftOuterJoin(RABinaryOperator):
|
|
|
17
18
|
|
|
18
19
|
# find matching columns
|
|
19
20
|
join_cols, all_cols = lcols.intersect(rcols)
|
|
21
|
+
if len(join_cols) == 0:
|
|
22
|
+
raise RAParserError('no common attributes found for left outer join', 0)
|
|
20
23
|
|
|
21
24
|
on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
|
|
22
25
|
|
|
23
26
|
# create sql
|
|
24
|
-
return f'SELECT {all_cols.list} FROM ({lq}) {self._name()} LEFT OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
|
|
27
|
+
return f'SELECT DISTINCT {all_cols.list} FROM ({lq}) {self._name()} LEFT OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
|
|
@@ -2,6 +2,7 @@ from typing import Tuple, Dict
|
|
|
2
2
|
|
|
3
3
|
from duckdb_kernel.db import Table
|
|
4
4
|
from ..RABinaryOperator import RABinaryOperator
|
|
5
|
+
from ...ParserError import RAParserError
|
|
5
6
|
from ...util.RenamableColumnList import RenamableColumnList
|
|
6
7
|
|
|
7
8
|
|
|
@@ -17,8 +18,10 @@ class LeftSemiJoin(RABinaryOperator):
|
|
|
17
18
|
|
|
18
19
|
# find matching columns
|
|
19
20
|
join_cols, all_cols = lcols.intersect(rcols)
|
|
21
|
+
if len(join_cols) == 0:
|
|
22
|
+
raise RAParserError('no common attributes found for left semi join', 0)
|
|
20
23
|
|
|
21
24
|
on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
|
|
22
25
|
|
|
23
26
|
# create sql
|
|
24
|
-
return f'SELECT {lcols.list} FROM ({lq}) {self._name()} JOIN ({rq}) {self._name()} ON {on_clause}', lcols
|
|
27
|
+
return f'SELECT DISTINCT {lcols.list} FROM ({lq}) {self._name()} JOIN ({rq}) {self._name()} ON {on_clause}', lcols
|
|
@@ -2,6 +2,7 @@ from typing import Tuple, Dict
|
|
|
2
2
|
|
|
3
3
|
from duckdb_kernel.db import Table
|
|
4
4
|
from ..RABinaryOperator import RABinaryOperator
|
|
5
|
+
from ...ParserError import RAParserError
|
|
5
6
|
from ...util.RenamableColumnList import RenamableColumnList
|
|
6
7
|
|
|
7
8
|
|
|
@@ -17,8 +18,10 @@ class RightOuterJoin(RABinaryOperator):
|
|
|
17
18
|
|
|
18
19
|
# find matching columns
|
|
19
20
|
join_cols, all_cols = lcols.intersect(rcols, prefer_right=True)
|
|
21
|
+
if len(join_cols) == 0:
|
|
22
|
+
raise RAParserError('no common attributes found for right outer join', 0)
|
|
20
23
|
|
|
21
24
|
on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
|
|
22
25
|
|
|
23
26
|
# create sql
|
|
24
|
-
return f'SELECT {all_cols.list} FROM ({lq}) {self._name()} RIGHT OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
|
|
27
|
+
return f'SELECT DISTINCT {all_cols.list} FROM ({lq}) {self._name()} RIGHT OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
|
|
@@ -2,6 +2,7 @@ from typing import Tuple, Dict
|
|
|
2
2
|
|
|
3
3
|
from duckdb_kernel.db import Table
|
|
4
4
|
from ..RABinaryOperator import RABinaryOperator
|
|
5
|
+
from ...ParserError import RAParserError
|
|
5
6
|
from ...util.RenamableColumnList import RenamableColumnList
|
|
6
7
|
|
|
7
8
|
|
|
@@ -17,8 +18,10 @@ class RightSemiJoin(RABinaryOperator):
|
|
|
17
18
|
|
|
18
19
|
# find matching columns
|
|
19
20
|
join_cols, all_cols = lcols.intersect(rcols, prefer_right=True)
|
|
21
|
+
if len(join_cols) == 0:
|
|
22
|
+
raise RAParserError('no common attributes found for right semi join', 0)
|
|
20
23
|
|
|
21
24
|
on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
|
|
22
25
|
|
|
23
26
|
# create sql
|
|
24
|
-
return f'SELECT {rcols.list} FROM ({lq}) {self._name()} JOIN ({rq}) {self._name()} ON {on_clause}', rcols
|
|
27
|
+
return f'SELECT DISTINCT {rcols.list} FROM ({lq}) {self._name()} JOIN ({rq}) {self._name()} ON {on_clause}', rcols
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Dict, Tuple
|
|
2
2
|
|
|
3
|
-
from duckdb_kernel.db import Table
|
|
4
3
|
from ..LogicElement import LogicElement
|
|
5
4
|
from ..RAElement import RAElement
|
|
6
5
|
from ..RAUnaryOperator import RAUnaryOperator
|
|
7
6
|
from ..binary import ArrowLeft
|
|
8
7
|
from ...util.RenamableColumnList import RenamableColumnList
|
|
8
|
+
from ....db import Table
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class AttributeRename(RAUnaryOperator):
|
|
12
12
|
@staticmethod
|
|
13
13
|
def symbols() -> Tuple[str, ...]:
|
|
14
14
|
return 'β', 'beta'
|
|
15
15
|
|
|
16
|
-
def __init__(self, target: RAElement, arg:
|
|
16
|
+
def __init__(self, target: RAElement, arg: LogicElement):
|
|
17
17
|
if not isinstance(arg, ArrowLeft):
|
|
18
18
|
raise AssertionError('only arrow statements allowed as parameter')
|
|
19
19
|
|
|
@@ -13,7 +13,7 @@ class Projection(RAUnaryOperator):
|
|
|
13
13
|
def symbols() -> Tuple[str, ...]:
|
|
14
14
|
return 'Π', 'π', 'pi'
|
|
15
15
|
|
|
16
|
-
def __init__(self, target: RAElement, arg:
|
|
16
|
+
def __init__(self, target: RAElement, arg: LogicElement):
|
|
17
17
|
if not isinstance(arg, LogicOperand):
|
|
18
18
|
raise AssertionError('only argument lists allowed as parameter')
|
|
19
19
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from typing import Tuple, Dict
|
|
2
|
+
|
|
3
|
+
from .. import RARelationReference
|
|
4
|
+
from ..LogicElement import LogicElement
|
|
5
|
+
from ..RAElement import RAElement
|
|
6
|
+
from ..RAUnaryOperator import RAUnaryOperator
|
|
7
|
+
from ...ParserError import RAParserError
|
|
8
|
+
from ...tokenizer import Token
|
|
9
|
+
from ...util.RenamableColumnList import RenamableColumnList
|
|
10
|
+
from ....db import Table
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Rename(RAUnaryOperator):
|
|
14
|
+
@staticmethod
|
|
15
|
+
def symbols() -> Tuple[str, ...]:
|
|
16
|
+
return 'ρ', 'ϱ', 'rho'
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def parse_args(cls: type[RAUnaryOperator], *tokens: Token, depth: int):
|
|
20
|
+
from .. import RARelationReference
|
|
21
|
+
return RARelationReference.parse_tokens(cls, *tokens, depth=depth)
|
|
22
|
+
|
|
23
|
+
def __init__(self, target: RAElement, arg: RARelationReference):
|
|
24
|
+
super().__init__(target)
|
|
25
|
+
self.reference: RARelationReference = arg
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def arg(self) -> LogicElement:
|
|
29
|
+
return self.reference
|
|
30
|
+
|
|
31
|
+
def to_sql(self, tables: Dict[str, Table]) -> Tuple[str, RenamableColumnList]:
|
|
32
|
+
# execute subquery
|
|
33
|
+
subquery, subcols = self.target.to_sql(tables)
|
|
34
|
+
|
|
35
|
+
# rename attributes
|
|
36
|
+
if self.reference.relation is None and self.reference.attributes is not None:
|
|
37
|
+
return self._to_sql_with_renamed_attributes(tables, subquery, subcols)
|
|
38
|
+
|
|
39
|
+
# rename relation
|
|
40
|
+
elif self.reference.relation is not None and self.reference.attributes is None:
|
|
41
|
+
return self._to_sql_with_renamed_relation(tables, subquery, subcols)
|
|
42
|
+
|
|
43
|
+
# rename relation and attributes
|
|
44
|
+
else:
|
|
45
|
+
return self._to_sql_with_renamed_relation_and_attributes(tables, subquery, subcols)
|
|
46
|
+
|
|
47
|
+
def _to_sql_with_renamed_relation(self,
|
|
48
|
+
tables: Dict[str, Table],
|
|
49
|
+
subquery: str,
|
|
50
|
+
subcols: RenamableColumnList) -> Tuple[str, RenamableColumnList]:
|
|
51
|
+
# check if there are two columns with the same name
|
|
52
|
+
for i in range(len(subcols)):
|
|
53
|
+
for k in range(i + 1, len(subcols)):
|
|
54
|
+
if subcols[i].name == subcols[k].name:
|
|
55
|
+
raise RAParserError(
|
|
56
|
+
f'attribute {subcols[i].name} is present in both {subcols[i].table.name} and {subcols[k].table.name}',
|
|
57
|
+
depth=0
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# add new table
|
|
61
|
+
table = Table(self.reference.relation)
|
|
62
|
+
tables[self.reference.relation] = table
|
|
63
|
+
|
|
64
|
+
# set table for all attributes
|
|
65
|
+
for col in subcols:
|
|
66
|
+
col.table = table
|
|
67
|
+
|
|
68
|
+
# return
|
|
69
|
+
return subquery, subcols
|
|
70
|
+
|
|
71
|
+
def _to_sql_with_renamed_attributes(self,
|
|
72
|
+
tables: Dict[str, Table],
|
|
73
|
+
subquery: str,
|
|
74
|
+
subcols: RenamableColumnList) -> Tuple[str, RenamableColumnList]:
|
|
75
|
+
# check if there are more names than subcols
|
|
76
|
+
if len(self.reference.attributes) > len(subcols):
|
|
77
|
+
raise RAParserError('more names than attributes', 0)
|
|
78
|
+
|
|
79
|
+
# rename columns
|
|
80
|
+
for col, new_name in zip(subcols, self.reference.attributes):
|
|
81
|
+
col.name = new_name
|
|
82
|
+
|
|
83
|
+
# return
|
|
84
|
+
return subquery, subcols
|
|
85
|
+
|
|
86
|
+
def _to_sql_with_renamed_relation_and_attributes(self,
|
|
87
|
+
tables: Dict[str, Table],
|
|
88
|
+
subquery: str,
|
|
89
|
+
subcols: RenamableColumnList) -> Tuple[str, RenamableColumnList]:
|
|
90
|
+
subquery, subcols = self._to_sql_with_renamed_attributes(tables, subquery, subcols)
|
|
91
|
+
subquery, subcols = self._to_sql_with_renamed_relation(tables, subquery, subcols)
|
|
92
|
+
|
|
93
|
+
return subquery, subcols
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from .AttributeRename import AttributeRename
|
|
1
2
|
from .Not import Not
|
|
2
3
|
from .Projection import Projection
|
|
3
4
|
from .Rename import Rename
|
|
@@ -10,6 +11,7 @@ LOGIC_UNARY_OPERATORS = [
|
|
|
10
11
|
]
|
|
11
12
|
|
|
12
13
|
RA_UNARY_OPERATORS = [
|
|
14
|
+
AttributeRename,
|
|
13
15
|
Projection,
|
|
14
16
|
Rename,
|
|
15
17
|
Selection
|
|
@@ -46,6 +46,35 @@ def test_case_insensitivity():
|
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
def test_comments():
|
|
50
|
+
for query in (
|
|
51
|
+
'{ username | users(id, username) } -- cmmnt',
|
|
52
|
+
'{ username | users(id, username) } /* cmmnt */',
|
|
53
|
+
'{ username | /* cmmnt */ users(id, username) }',
|
|
54
|
+
):
|
|
55
|
+
root = DCParser.parse_query(query)
|
|
56
|
+
|
|
57
|
+
# execute to test case insensitivity
|
|
58
|
+
with Connection() as con:
|
|
59
|
+
cols, rows = con.execute_dc_return_cols(root)
|
|
60
|
+
|
|
61
|
+
assert [c.lower() for c in cols] == [
|
|
62
|
+
'username'
|
|
63
|
+
]
|
|
64
|
+
assert rows == [
|
|
65
|
+
('Alice',),
|
|
66
|
+
('Bob',),
|
|
67
|
+
('Charlie',)
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
for query in (
|
|
71
|
+
'-- comment',
|
|
72
|
+
'/* comment */'
|
|
73
|
+
):
|
|
74
|
+
root = DCParser.parse_query(query)
|
|
75
|
+
assert root is None
|
|
76
|
+
|
|
77
|
+
|
|
49
78
|
def test_simple_queries():
|
|
50
79
|
with Connection() as con:
|
|
51
80
|
for query in [
|