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.
Files changed (107) hide show
  1. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/PKG-INFO +1 -1
  2. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/setup.py +1 -1
  3. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/kernel.py +4 -0
  4. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/DCParser.py +7 -0
  5. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/RAParser.py +7 -5
  6. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Cross.py +1 -1
  7. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/FullOuterJoin.py +4 -1
  8. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Join.py +4 -1
  9. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/LeftOuterJoin.py +4 -1
  10. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/LeftSemiJoin.py +4 -1
  11. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/RightOuterJoin.py +4 -1
  12. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/RightSemiJoin.py +4 -1
  13. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_dc.py +29 -0
  14. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_ra.py +55 -0
  15. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/ResultSetComparator.py +18 -3
  16. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/PKG-INFO +1 -1
  17. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/README.md +0 -0
  18. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/setup.cfg +0 -0
  19. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/__init__.py +0 -0
  20. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/__main__.py +0 -0
  21. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/Column.py +0 -0
  22. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/Connection.py +0 -0
  23. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/Constraint.py +0 -0
  24. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/DatabaseError.py +0 -0
  25. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/ForeignKey.py +0 -0
  26. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/Table.py +0 -0
  27. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/__init__.py +0 -0
  28. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/error/EmptyResultError.py +0 -0
  29. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/error/__init__.py +0 -0
  30. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/duckdb/Connection.py +0 -0
  31. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/duckdb/__init__.py +0 -0
  32. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/postgres/Connection.py +0 -0
  33. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/postgres/__init__.py +0 -0
  34. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/postgres/util.py +0 -0
  35. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/sqlite/Connection.py +0 -0
  36. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/db/implementation/sqlite/__init__.py +0 -0
  37. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/kernel.json +0 -0
  38. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommand.py +0 -0
  39. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandCallback.py +0 -0
  40. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
  41. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicCommandHandler.py +0 -0
  42. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/MagicState.py +0 -0
  43. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/magics/__init__.py +0 -0
  44. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/LogicParser.py +0 -0
  45. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/ParserError.py +0 -0
  46. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/__init__.py +0 -0
  47. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/DCOperand.py +0 -0
  48. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -0
  49. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
  50. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
  51. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
  52. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAElement.py +0 -0
  53. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAOperand.py +0 -0
  54. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
  55. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +0 -0
  56. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/__init__.py +0 -0
  57. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
  58. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
  59. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
  60. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +0 -0
  61. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
  62. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
  63. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Division.py +0 -0
  64. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
  65. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
  66. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
  67. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
  68. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
  69. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
  70. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
  71. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
  72. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
  73. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
  74. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
  75. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/binary/__init__.py +0 -0
  76. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
  77. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Projection.py +0 -0
  78. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Rename.py +0 -0
  79. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
  80. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/elements/unary/__init__.py +0 -0
  81. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/Token.py +0 -0
  82. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
  83. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
  84. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/QuerySplitter.py +0 -0
  85. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
  86. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/RenamableColumnList.py +0 -0
  87. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/parser/util/__init__.py +0 -0
  88. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/__init__.py +0 -0
  89. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_result_comparison.py +0 -0
  90. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/tests/test_sql.py +0 -0
  91. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/SQL.py +0 -0
  92. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/TestError.py +0 -0
  93. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/__init__.py +0 -0
  94. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/util/formatting.py +0 -0
  95. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/Drawer.py +0 -0
  96. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/Plotly.py +0 -0
  97. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
  98. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
  99. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/__init__.py +0 -0
  100. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/__init__.py +0 -0
  101. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/plotly-3.0.1.min.js +0 -0
  102. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/ra.css +0 -0
  103. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/duckdb_kernel/visualization/lib/ra.js +0 -0
  104. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/SOURCES.txt +0 -0
  105. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
  106. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
  107. {jupyter_duckdb-1.4.106 → jupyter_duckdb-1.4.107}/src/jupyter_duckdb.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jupyter-duckdb
3
- Version: 1.4.106
3
+ Version: 1.4.107
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
@@ -1,7 +1,7 @@
1
1
  import os
2
2
 
3
3
  # configuration
4
- PACKAGE_VERSION = '1.4.106'
4
+ PACKAGE_VERSION = '1.4.107'
5
5
  DUCKDB_VERSION = '1.4.1'
6
6
 
7
7
  DEPENDENCIES = [
@@ -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) > 1:
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])
@@ -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
@@ -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 (
@@ -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]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jupyter-duckdb
3
- Version: 1.4.106
3
+ Version: 1.4.107
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