jupyter-duckdb 1.4.106__tar.gz → 1.4.107__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.107}/PKG-INFO +1 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/setup.py +1 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/kernel.py +4 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/DCParser.py +7 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/RAParser.py +7 -5
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Cross.py +1 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/FullOuterJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Join.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/LeftOuterJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/LeftSemiJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/RightOuterJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/RightSemiJoin.py +4 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_dc.py +29 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_ra.py +55 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/ResultSetComparator.py +18 -3
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/PKG-INFO +1 -1
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/README.md +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/setup.cfg +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/__main__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/Column.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/Connection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/Constraint.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/DatabaseError.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/ForeignKey.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/Table.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/error/EmptyResultError.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/error/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/duckdb/Connection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/duckdb/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/postgres/Connection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/postgres/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/postgres/util.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/sqlite/Connection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/sqlite/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/kernel.json +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommand.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandCallback.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandHandler.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicState.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/LogicParser.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/ParserError.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/DCOperand.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAElement.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAOperand.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Division.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Projection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Rename.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/Token.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/QuerySplitter.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/RenamableColumnList.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_result_comparison.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_sql.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/SQL.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/TestError.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/formatting.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/Drawer.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/Plotly.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/__init__.py +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/plotly-3.0.1.min.js +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/ra.css +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/ra.js +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/SOURCES.txt +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
- {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/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():
|
|
@@ -10,7 +10,7 @@ from .util.QuerySplitter import get_last_query
|
|
|
10
10
|
|
|
11
11
|
class RAParser:
|
|
12
12
|
@staticmethod
|
|
13
|
-
def parse_query(query: str) -> RAElement:
|
|
13
|
+
def parse_query(query: str) -> RAElement | None:
|
|
14
14
|
# remove comments from query
|
|
15
15
|
query = get_last_query(query, split_at=None, remove_comments=True)
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ class RAParser:
|
|
|
19
19
|
return RAParser.parse_tokens(initial_token, depth=0)
|
|
20
20
|
|
|
21
21
|
@staticmethod
|
|
22
|
-
def parse_tokens(*tokens: Token, target: RAOperator | RAOperand = None, depth: int = 0) -> RAElement:
|
|
22
|
+
def parse_tokens(*tokens: Token, target: RAOperator | RAOperand = None, depth: int = 0) -> RAElement | None:
|
|
23
23
|
if len(tokens) == 1:
|
|
24
24
|
tokens = tuple(Tokenizer.tokenize(tokens[0]))
|
|
25
25
|
|
|
@@ -76,7 +76,9 @@ class RAParser:
|
|
|
76
76
|
return op
|
|
77
77
|
|
|
78
78
|
# return as name
|
|
79
|
-
if len(tokens)
|
|
79
|
+
if len(tokens) == 0:
|
|
80
|
+
return None
|
|
81
|
+
elif len(tokens) == 1:
|
|
82
|
+
return RAOperand(tokens[0])
|
|
83
|
+
else:
|
|
80
84
|
raise RAParserError(f'{tokens=}', depth)
|
|
81
|
-
|
|
82
|
-
return RAOperand(tokens[0])
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/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.107}/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
|
|
@@ -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 [
|
|
@@ -101,6 +101,13 @@ def test_comments():
|
|
|
101
101
|
assert isinstance(root.left, RAOperand) and root.left.name == 'Shows'
|
|
102
102
|
assert isinstance(root.right, RAOperand) and root.right.name == 'Seasons'
|
|
103
103
|
|
|
104
|
+
for query in (
|
|
105
|
+
'-- comment',
|
|
106
|
+
'/* comment */'
|
|
107
|
+
):
|
|
108
|
+
root = RAParser.parse_query(query)
|
|
109
|
+
assert root is None
|
|
110
|
+
|
|
104
111
|
|
|
105
112
|
def test_binary_operator_cross():
|
|
106
113
|
for query in (
|
|
@@ -236,6 +243,14 @@ def test_binary_operator_join():
|
|
|
236
243
|
(2, 'Show 2', 2, 'Show 2 / Season 2')
|
|
237
244
|
]
|
|
238
245
|
|
|
246
|
+
for query in (
|
|
247
|
+
r'Shows ⋈ Users',
|
|
248
|
+
):
|
|
249
|
+
with pytest.raises(RAParserError):
|
|
250
|
+
with Connection() as con:
|
|
251
|
+
root = RAParser.parse_query(query)
|
|
252
|
+
con.execute_ra_return_cols(root)
|
|
253
|
+
|
|
239
254
|
|
|
240
255
|
def test_binary_operator_ljoin():
|
|
241
256
|
for query in (
|
|
@@ -262,6 +277,14 @@ def test_binary_operator_ljoin():
|
|
|
262
277
|
(3, 'Charlie', None),
|
|
263
278
|
]
|
|
264
279
|
|
|
280
|
+
for query in (
|
|
281
|
+
r'Shows ⟕ Users',
|
|
282
|
+
):
|
|
283
|
+
with pytest.raises(RAParserError):
|
|
284
|
+
with Connection() as con:
|
|
285
|
+
root = RAParser.parse_query(query)
|
|
286
|
+
con.execute_ra_return_cols(root)
|
|
287
|
+
|
|
265
288
|
|
|
266
289
|
def test_binary_operator_rjoin():
|
|
267
290
|
for query in (
|
|
@@ -287,6 +310,14 @@ def test_binary_operator_rjoin():
|
|
|
287
310
|
(4, None, 'David')
|
|
288
311
|
]
|
|
289
312
|
|
|
313
|
+
for query in (
|
|
314
|
+
r'Shows ⟖ Users',
|
|
315
|
+
):
|
|
316
|
+
with pytest.raises(RAParserError):
|
|
317
|
+
with Connection() as con:
|
|
318
|
+
root = RAParser.parse_query(query)
|
|
319
|
+
con.execute_ra_return_cols(root)
|
|
320
|
+
|
|
290
321
|
|
|
291
322
|
def test_binary_operator_fjoin():
|
|
292
323
|
for query in (
|
|
@@ -315,6 +346,14 @@ def test_binary_operator_fjoin():
|
|
|
315
346
|
(4, None, 'David')
|
|
316
347
|
]
|
|
317
348
|
|
|
349
|
+
for query in (
|
|
350
|
+
r'Shows ⟗ Users',
|
|
351
|
+
):
|
|
352
|
+
with pytest.raises(RAParserError):
|
|
353
|
+
with Connection() as con:
|
|
354
|
+
root = RAParser.parse_query(query)
|
|
355
|
+
con.execute_ra_return_cols(root)
|
|
356
|
+
|
|
318
357
|
|
|
319
358
|
def test_binary_operator_lsjoin():
|
|
320
359
|
for query in (
|
|
@@ -338,6 +377,14 @@ def test_binary_operator_lsjoin():
|
|
|
338
377
|
(2, 'Bob')
|
|
339
378
|
]
|
|
340
379
|
|
|
380
|
+
for query in (
|
|
381
|
+
r'Shows ⋉ Users',
|
|
382
|
+
):
|
|
383
|
+
with pytest.raises(RAParserError):
|
|
384
|
+
with Connection() as con:
|
|
385
|
+
root = RAParser.parse_query(query)
|
|
386
|
+
con.execute_ra_return_cols(root)
|
|
387
|
+
|
|
341
388
|
|
|
342
389
|
def test_binary_operator_rsjoin():
|
|
343
390
|
for query in (
|
|
@@ -361,6 +408,14 @@ def test_binary_operator_rsjoin():
|
|
|
361
408
|
(2, 'Bob')
|
|
362
409
|
]
|
|
363
410
|
|
|
411
|
+
for query in (
|
|
412
|
+
r'Shows ⋊ Users',
|
|
413
|
+
):
|
|
414
|
+
with pytest.raises(RAParserError):
|
|
415
|
+
with Connection() as con:
|
|
416
|
+
root = RAParser.parse_query(query)
|
|
417
|
+
con.execute_ra_return_cols(root)
|
|
418
|
+
|
|
364
419
|
|
|
365
420
|
def test_binary_operator_union():
|
|
366
421
|
for query in (
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/ResultSetComparator.py
RENAMED
|
@@ -1,11 +1,26 @@
|
|
|
1
|
+
import datetime
|
|
1
2
|
from datetime import date
|
|
2
|
-
from typing import Tuple, List, Optional
|
|
3
|
+
from typing import Tuple, List, Optional, Any
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class ResultSetComparator:
|
|
6
7
|
def __init__(self, left: List[Tuple | List], right: List[Tuple | List]):
|
|
7
|
-
self._left: List[Tuple] = [tuple(t) for t in left]
|
|
8
|
-
self._right: List[Tuple] = [tuple(t) for t in right]
|
|
8
|
+
self._left: List[Tuple] = [tuple(self.format_value(t)) for t in left]
|
|
9
|
+
self._right: List[Tuple] = [tuple(self.format_value(t)) for t in right]
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def format_value(value: Any) -> Any:
|
|
13
|
+
if isinstance(value, tuple) or isinstance(value, list):
|
|
14
|
+
return tuple(ResultSetComparator.format_value(t) for t in value)
|
|
15
|
+
|
|
16
|
+
if isinstance(value, datetime.datetime):
|
|
17
|
+
return value.strftime("%Y-%m-%d %H:%M:%S")
|
|
18
|
+
if isinstance(value, datetime.date):
|
|
19
|
+
return value.strftime("%Y-%m-%d")
|
|
20
|
+
if isinstance(value, datetime.time):
|
|
21
|
+
return value.strftime("%H:%M:%S")
|
|
22
|
+
|
|
23
|
+
return value
|
|
9
24
|
|
|
10
25
|
@property
|
|
11
26
|
def left_only(self) -> List[Tuple]:
|
|
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.4.106 → jupyter_duckdb-1.4.107}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandCallback.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandException.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandHandler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/DCOperand.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicElement.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicOperand.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicOperator.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAElement.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAOperand.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAOperator.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/__init__.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Add.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/And.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Divide.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Equal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Minus.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Or.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Union.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Not.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Rename.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/Token.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/__init__.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/QuerySplitter.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/RenamableColumn.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_result_comparison.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.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/RATreeDrawer.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/SchemaDrawer.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/__init__.py
RENAMED
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/ra.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|