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