jupyter-duckdb 1.2.0.6__tar.gz → 1.2.7__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 (98) hide show
  1. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/PKG-INFO +1 -1
  2. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/RAParser.py +6 -2
  3. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/__init__.py +1 -1
  4. jupyter_duckdb-1.2.7/src/duckdb_kernel/parser/elements/binary/FullOuterJoin.py +30 -0
  5. jupyter_duckdb-1.2.7/src/duckdb_kernel/parser/elements/binary/LeftOuterJoin.py +24 -0
  6. jupyter_duckdb-1.2.7/src/duckdb_kernel/parser/elements/binary/RightOuterJoin.py +24 -0
  7. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/__init__.py +19 -6
  8. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/util/RenamableColumnList.py +10 -2
  9. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/jupyter_duckdb.egg-info/PKG-INFO +1 -1
  10. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/jupyter_duckdb.egg-info/SOURCES.txt +3 -0
  11. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/test/test_ra.py +454 -7
  12. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/README.md +0 -0
  13. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/setup.cfg +0 -0
  14. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/setup.py +0 -0
  15. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/__init__.py +0 -0
  16. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/__main__.py +0 -0
  17. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/Column.py +0 -0
  18. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/Connection.py +0 -0
  19. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/Constraint.py +0 -0
  20. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/DatabaseError.py +0 -0
  21. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/ForeignKey.py +0 -0
  22. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/Table.py +0 -0
  23. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/__init__.py +0 -0
  24. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/error/EmptyResultError.py +0 -0
  25. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/error/__init__.py +0 -0
  26. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/implementation/duckdb/Connection.py +0 -0
  27. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/implementation/duckdb/__init__.py +0 -0
  28. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/implementation/postgres/Connection.py +0 -0
  29. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/implementation/postgres/__init__.py +0 -0
  30. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/implementation/postgres/util.py +0 -0
  31. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/implementation/sqlite/Connection.py +0 -0
  32. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/db/implementation/sqlite/__init__.py +0 -0
  33. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/kernel.json +0 -0
  34. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/kernel.py +0 -0
  35. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/magics/MagicCommand.py +0 -0
  36. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/magics/MagicCommandCallback.py +0 -0
  37. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
  38. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/magics/MagicCommandHandler.py +0 -0
  39. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/magics/StringWrapper.py +0 -0
  40. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/magics/__init__.py +0 -0
  41. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/DCParser.py +0 -0
  42. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/LogicParser.py +0 -0
  43. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/ParserError.py +0 -0
  44. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/__init__.py +0 -0
  45. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/DCOperand.py +0 -0
  46. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -0
  47. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
  48. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
  49. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
  50. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/RAElement.py +0 -0
  51. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/RAOperand.py +0 -0
  52. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
  53. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +0 -0
  54. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
  55. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
  56. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
  57. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +0 -0
  58. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Cross.py +0 -0
  59. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
  60. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
  61. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Division.py +0 -0
  62. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
  63. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
  64. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
  65. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
  66. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Join.py +0 -0
  67. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
  68. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
  69. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
  70. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
  71. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
  72. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
  73. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
  74. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
  75. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/unary/Projection.py +0 -0
  76. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/unary/Rename.py +0 -0
  77. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
  78. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/elements/unary/__init__.py +0 -0
  79. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/tokenizer/Token.py +0 -0
  80. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
  81. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
  82. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
  83. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/parser/util/__init__.py +0 -0
  84. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/util/ResultSetComparator.py +0 -0
  85. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/util/SQL.py +0 -0
  86. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/util/TestError.py +0 -0
  87. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/util/__init__.py +0 -0
  88. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/util/formatting.py +0 -0
  89. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/visualization/Drawer.py +0 -0
  90. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
  91. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
  92. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/duckdb_kernel/visualization/__init__.py +0 -0
  93. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
  94. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
  95. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/src/jupyter_duckdb.egg-info/top_level.txt +0 -0
  96. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/test/test_dc.py +0 -0
  97. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/test/test_result_comparison.py +0 -0
  98. {jupyter_duckdb-1.2.0.6 → jupyter_duckdb-1.2.7}/test/test_sql.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jupyter-duckdb
3
- Version: 1.2.0.6
3
+ Version: 1.2.7
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
@@ -19,10 +19,14 @@ class RAParser:
19
19
  tokens = tuple(Tokenizer.tokenize(tokens[0]))
20
20
 
21
21
  # binary operators
22
- for operator in RA_BINARY_OPERATORS:
22
+ for operator_symbols in RA_BINARY_SYMBOLS:
23
23
  # iterate tokens and match symbol
24
24
  for i in range(1, len(tokens) + 1):
25
- if tokens[-i].lower() in operator.symbols():
25
+ lower_token = tokens[-i].lower()
26
+
27
+ if lower_token in operator_symbols:
28
+ operator = operator_symbols[lower_token]
29
+
26
30
  # raise error if left or right operand missing
27
31
  if i == 1:
28
32
  raise RAParserError(f'right operand missing after {tokens[-i]}', depth)
@@ -9,7 +9,7 @@ from .RAOperator import RAOperator
9
9
  from .RABinaryOperator import RABinaryOperator
10
10
  from .RAUnaryOperator import RAUnaryOperator
11
11
  from .RAOperand import RAOperand
12
- from .binary import RA_BINARY_OPERATORS
12
+ from .binary import RA_BINARY_OPERATORS, RA_BINARY_SYMBOLS
13
13
  from .unary import RA_UNARY_OPERATORS
14
14
 
15
15
  from .DCOperand import DCOperand
@@ -0,0 +1,30 @@
1
+ from typing import Tuple, Dict
2
+
3
+ from duckdb_kernel.db import Table
4
+ from ..RABinaryOperator import RABinaryOperator
5
+ from ...util.RenamableColumnList import RenamableColumnList
6
+
7
+
8
+ class FullOuterJoin(RABinaryOperator):
9
+ @staticmethod
10
+ def symbols() -> Tuple[str, ...]:
11
+ return chr(10199), 'fjoin', 'ojoin'
12
+
13
+ def to_sql(self, tables: Dict[str, Table]) -> Tuple[str, RenamableColumnList]:
14
+ # execute subqueries
15
+ lq, lcols = self.left.to_sql(tables)
16
+ rq, rcols = self.right.to_sql(tables)
17
+
18
+ # find matching columns
19
+ join_cols, all_cols = lcols.intersect(rcols)
20
+
21
+ replacements = {c1: c2 for c1, c2 in join_cols}
22
+ select_cols = [
23
+ f'COALESCE({c.current_name}, {replacements.get(c).current_name})' if c in replacements else c.current_name
24
+ for c in all_cols]
25
+ select_clause = ', '.join(select_cols)
26
+
27
+ on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
28
+
29
+ # create sql
30
+ return f'SELECT {select_clause} FROM ({lq}) {self._name()} FULL OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
@@ -0,0 +1,24 @@
1
+ from typing import Tuple, Dict
2
+
3
+ from duckdb_kernel.db import Table
4
+ from ..RABinaryOperator import RABinaryOperator
5
+ from ...util.RenamableColumnList import RenamableColumnList
6
+
7
+
8
+ class LeftOuterJoin(RABinaryOperator):
9
+ @staticmethod
10
+ def symbols() -> Tuple[str, ...]:
11
+ return chr(10197), 'ljoin'
12
+
13
+ def to_sql(self, tables: Dict[str, Table]) -> Tuple[str, RenamableColumnList]:
14
+ # execute subqueries
15
+ lq, lcols = self.left.to_sql(tables)
16
+ rq, rcols = self.right.to_sql(tables)
17
+
18
+ # find matching columns
19
+ join_cols, all_cols = lcols.intersect(rcols)
20
+
21
+ on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
22
+
23
+ # create sql
24
+ return f'SELECT {all_cols.list} FROM ({lq}) {self._name()} LEFT OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
@@ -0,0 +1,24 @@
1
+ from typing import Tuple, Dict
2
+
3
+ from duckdb_kernel.db import Table
4
+ from ..RABinaryOperator import RABinaryOperator
5
+ from ...util.RenamableColumnList import RenamableColumnList
6
+
7
+
8
+ class RightOuterJoin(RABinaryOperator):
9
+ @staticmethod
10
+ def symbols() -> Tuple[str, ...]:
11
+ return chr(10198), 'rjoin'
12
+
13
+ def to_sql(self, tables: Dict[str, Table]) -> Tuple[str, RenamableColumnList]:
14
+ # execute subqueries
15
+ lq, lcols = self.left.to_sql(tables)
16
+ rq, rcols = self.right.to_sql(tables)
17
+
18
+ # find matching columns
19
+ join_cols, all_cols = lcols.intersect(rcols, prefer_right=True)
20
+
21
+ on_clause = ' AND '.join(f'{l.current_name} = {r.current_name}' for l, r in join_cols)
22
+
23
+ # create sql
24
+ return f'SELECT {all_cols.list} FROM ({lq}) {self._name()} RIGHT OUTER JOIN ({rq}) {self._name()} ON {on_clause}', all_cols
@@ -2,6 +2,9 @@ from .Cross import Cross
2
2
  from .Difference import Difference
3
3
  from .Intersection import Intersection
4
4
  from .Join import Join
5
+ from .LeftOuterJoin import LeftOuterJoin
6
+ from .RightOuterJoin import RightOuterJoin
7
+ from .FullOuterJoin import FullOuterJoin
5
8
  from .Union import Union
6
9
 
7
10
  from .Add import Add
@@ -30,12 +33,22 @@ LOGIC_BINARY_OPERATORS = sorted([
30
33
  ], key=lambda x: x.order, reverse=True)
31
34
 
32
35
  RA_BINARY_OPERATORS = [
33
- Difference,
34
- Union,
35
- Intersection,
36
- Join,
37
- Cross,
38
- Division
36
+ [Difference],
37
+ [Union],
38
+ [Intersection],
39
+ [Join],
40
+ [LeftOuterJoin, RightOuterJoin, FullOuterJoin],
41
+ [Cross],
42
+ [Division]
43
+ ]
44
+
45
+ RA_BINARY_SYMBOLS = [
46
+ {
47
+ symbol: operator
48
+ for operator in level
49
+ for symbol in operator.symbols()
50
+ }
51
+ for level in RA_BINARY_OPERATORS
39
52
  ]
40
53
 
41
54
  DC_SET = ConditionalSet
@@ -73,18 +73,26 @@ class RenamableColumnList(list[RenamableColumn]):
73
73
 
74
74
  return RenamableColumnList(cols.values())
75
75
 
76
- def intersect(self, other: 'RenamableColumnList') \
76
+ def intersect(self, other: 'RenamableColumnList', prefer_right: bool = False) \
77
77
  -> Tuple[List[Tuple[RenamableColumn, RenamableColumn]], 'RenamableColumnList']:
78
78
  self_cols: Dict[str, RenamableColumn] = {col.name: col for col in self}
79
79
  other_cols: Dict[str, RenamableColumn] = {col.name: col for col in other}
80
80
 
81
+ replacements: Dict[RenamableColumn, RenamableColumn] = {}
81
82
  intersection: List[Tuple[RenamableColumn, RenamableColumn]] = []
83
+
82
84
  for name in tuple(self_cols.keys()):
83
85
  if name in other_cols:
86
+ if prefer_right:
87
+ replacements[self_cols[name]] = other_cols[name]
88
+
84
89
  intersection.append((self_cols[name], other_cols[name]))
85
90
  del other_cols[name]
86
91
 
87
92
  # if len(intersection) == 0:
88
93
  # raise AssertionError('no common attributes found for join')
89
94
 
90
- return intersection, RenamableColumnList(self_cols.values(), other_cols.values())
95
+ return intersection, RenamableColumnList(
96
+ (replacements.get(x, x) for x in self_cols.values()),
97
+ other_cols.values()
98
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jupyter-duckdb
3
- Version: 1.2.0.6
3
+ Version: 1.2.7
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
@@ -50,15 +50,18 @@ src/duckdb_kernel/parser/elements/binary/Difference.py
50
50
  src/duckdb_kernel/parser/elements/binary/Divide.py
51
51
  src/duckdb_kernel/parser/elements/binary/Division.py
52
52
  src/duckdb_kernel/parser/elements/binary/Equal.py
53
+ src/duckdb_kernel/parser/elements/binary/FullOuterJoin.py
53
54
  src/duckdb_kernel/parser/elements/binary/GreaterThan.py
54
55
  src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py
55
56
  src/duckdb_kernel/parser/elements/binary/Intersection.py
56
57
  src/duckdb_kernel/parser/elements/binary/Join.py
58
+ src/duckdb_kernel/parser/elements/binary/LeftOuterJoin.py
57
59
  src/duckdb_kernel/parser/elements/binary/LessThan.py
58
60
  src/duckdb_kernel/parser/elements/binary/LessThanEqual.py
59
61
  src/duckdb_kernel/parser/elements/binary/Minus.py
60
62
  src/duckdb_kernel/parser/elements/binary/Multiply.py
61
63
  src/duckdb_kernel/parser/elements/binary/Or.py
64
+ src/duckdb_kernel/parser/elements/binary/RightOuterJoin.py
62
65
  src/duckdb_kernel/parser/elements/binary/Unequal.py
63
66
  src/duckdb_kernel/parser/elements/binary/Union.py
64
67
  src/duckdb_kernel/parser/elements/binary/__init__.py
@@ -32,9 +32,9 @@ def test_case_insensitivity():
32
32
  ]
33
33
 
34
34
  for query in (
35
- 'π [ Username ] ( Users )',
36
- 'π [ username ] ( Users )',
37
- 'π [ userName ] ( Users )'
35
+ 'π [ Username ] ( Users )',
36
+ 'π [ username ] ( Users )',
37
+ 'π [ userName ] ( Users )'
38
38
  ):
39
39
  root = RAParser.parse_query(query)
40
40
 
@@ -47,9 +47,9 @@ def test_case_insensitivity():
47
47
  ]
48
48
 
49
49
  for query in (
50
- 'π [ Id, Username ] ( Users )',
51
- 'π [ id, username ] ( Users )',
52
- 'π [ iD, userName ] ( Users )'
50
+ 'π [ Id, Username ] ( Users )',
51
+ 'π [ id, username ] ( Users )',
52
+ 'π [ iD, userName ] ( Users )'
53
53
  ):
54
54
  root = RAParser.parse_query(query)
55
55
 
@@ -160,6 +160,64 @@ def test_binary_operator_join():
160
160
  ]
161
161
 
162
162
 
163
+ def test_binary_operator_ljoin():
164
+ for query in (
165
+ r'Users ⟕ BannedUsers',
166
+ r'Users ljoin BannedUsers'
167
+ ):
168
+ root = RAParser.parse_query(query)
169
+
170
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
171
+ assert isinstance(root.left, RAOperand) and root.left.name == 'Users'
172
+ assert isinstance(root.right, RAOperand) and root.right.name == 'BannedUsers'
173
+
174
+ with Connection() as con:
175
+ assert con.execute_ra(root) == [
176
+ (1, 'Alice', None),
177
+ (2, 'Bob', 'Bob'),
178
+ (3, 'Charlie', None),
179
+ ]
180
+
181
+
182
+ def test_binary_operator_rjoin():
183
+ for query in (
184
+ r'Users ⟖ BannedUsers',
185
+ r'Users rjoin BannedUsers'
186
+ ):
187
+ root = RAParser.parse_query(query)
188
+
189
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
190
+ assert isinstance(root.left, RAOperand) and root.left.name == 'Users'
191
+ assert isinstance(root.right, RAOperand) and root.right.name == 'BannedUsers'
192
+
193
+ with Connection() as con:
194
+ assert con.execute_ra(root) == [
195
+ (2, 'Bob', 'Bob'),
196
+ (4, None, 'David')
197
+ ]
198
+
199
+
200
+ def test_binary_operator_fjoin():
201
+ for query in (
202
+ r'Users ⟗ BannedUsers',
203
+ r'Users fjoin BannedUsers',
204
+ r'Users ojoin BannedUsers'
205
+ ):
206
+ root = RAParser.parse_query(query)
207
+
208
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
209
+ assert isinstance(root.left, RAOperand) and root.left.name == 'Users'
210
+ assert isinstance(root.right, RAOperand) and root.right.name == 'BannedUsers'
211
+
212
+ with Connection() as con:
213
+ assert con.execute_ra(root) == [
214
+ (1, 'Alice', None),
215
+ (2, 'Bob', 'Bob'),
216
+ (3, 'Charlie', None),
217
+ (4, None, 'David')
218
+ ]
219
+
220
+
163
221
  def test_binary_operator_union():
164
222
  for query in (
165
223
  r'Users ∪ BannedUsers',
@@ -313,6 +371,178 @@ def test_unary_operator_selection():
313
371
  ]
314
372
 
315
373
 
374
+ def test_unary_inner_to_outer_evaluation_order():
375
+ root = RAParser.parse_query(r'π [ Id ] π [ Id, Username ] (Users)')
376
+ assert isinstance(root, UnaryOperators.Projection) and root.columns == ('Id',)
377
+ assert isinstance(root.target, UnaryOperators.Projection) and root.target.columns == ('Id', 'Username')
378
+
379
+ root = RAParser.parse_query(r'σ [ Id > 2 ] σ [ Id > 1 ] (Users)')
380
+ assert isinstance(root, UnaryOperators.Selection)
381
+ assert isinstance(root.condition, BinaryOperators.GreaterThan)
382
+ assert root.condition.left == ('Id',) and root.condition.right == ('2',)
383
+ assert isinstance(root.target, UnaryOperators.Selection)
384
+ assert isinstance(root.target.condition, BinaryOperators.GreaterThan)
385
+ assert root.target.condition.left == ('Id',) and root.target.condition.right == ('1',)
386
+
387
+ root = RAParser.parse_query(r'β [ Id3 ← Id2 ] β [ Id2 ← Id ] (Users)')
388
+ assert isinstance(root, UnaryOperators.Rename)
389
+ assert isinstance(root.arrow, BinaryOperators.ArrowLeft)
390
+ assert root.arrow.left == ('Id3',) and root.arrow.right == ('Id2',)
391
+ assert isinstance(root.target, UnaryOperators.Rename)
392
+ assert isinstance(root.target.arrow, BinaryOperators.ArrowLeft)
393
+ assert root.target.arrow.left == ('Id2',) and root.target.arrow.right == ('Id',)
394
+
395
+
396
+ def test_binary_left_to_right_evaluation_order():
397
+ # difference
398
+ root = RAParser.parse_query(r'a \ b \ c')
399
+ assert isinstance(root, BinaryOperators.Difference)
400
+ assert isinstance(root.left, BinaryOperators.Difference)
401
+ assert isinstance(root.left.left, RAOperand)
402
+ assert root.left.left.name == 'a'
403
+ assert isinstance(root.left.right, RAOperand)
404
+ assert root.left.right.name == 'b'
405
+ assert isinstance(root.right, RAOperand)
406
+ assert root.right.name == 'c'
407
+
408
+ # union
409
+ root = RAParser.parse_query(r'a ∪ b ∪ c')
410
+ assert isinstance(root, BinaryOperators.Union)
411
+ assert isinstance(root.left, BinaryOperators.Union)
412
+ assert isinstance(root.left.left, RAOperand)
413
+ assert root.left.left.name == 'a'
414
+ assert isinstance(root.left.right, RAOperand)
415
+ assert root.left.right.name == 'b'
416
+ assert isinstance(root.right, RAOperand)
417
+ assert root.right.name == 'c'
418
+
419
+ # intersection
420
+ root = RAParser.parse_query(r'a ∩ b ∩ c')
421
+ assert isinstance(root, BinaryOperators.Intersection)
422
+ assert isinstance(root.left, BinaryOperators.Intersection)
423
+ assert isinstance(root.left.left, RAOperand)
424
+ assert root.left.left.name == 'a'
425
+ assert isinstance(root.left.right, RAOperand)
426
+ assert root.left.right.name == 'b'
427
+ assert isinstance(root.right, RAOperand)
428
+ assert root.right.name == 'c'
429
+
430
+ # natural join
431
+ root = RAParser.parse_query(r'a ⋈ b ⋈ c')
432
+ assert isinstance(root, BinaryOperators.Join)
433
+ assert isinstance(root.left, BinaryOperators.Join)
434
+ assert isinstance(root.left.left, RAOperand)
435
+ assert root.left.left.name == 'a'
436
+ assert isinstance(root.left.right, RAOperand)
437
+ assert root.left.right.name == 'b'
438
+ assert isinstance(root.right, RAOperand)
439
+ assert root.right.name == 'c'
440
+
441
+ # outer join
442
+ root = RAParser.parse_query(r'a ⟕ b ⟕ c')
443
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
444
+ assert isinstance(root.left, BinaryOperators.LeftOuterJoin)
445
+ assert isinstance(root.left.left, RAOperand)
446
+ assert root.left.left.name == 'a'
447
+ assert isinstance(root.left.right, RAOperand)
448
+ assert root.left.right.name == 'b'
449
+ assert isinstance(root.right, RAOperand)
450
+ assert root.right.name == 'c'
451
+
452
+ root = RAParser.parse_query(r'a ⟖ b ⟖ c')
453
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
454
+ assert isinstance(root.left, BinaryOperators.RightOuterJoin)
455
+ assert isinstance(root.left.left, RAOperand)
456
+ assert root.left.left.name == 'a'
457
+ assert isinstance(root.left.right, RAOperand)
458
+ assert root.left.right.name == 'b'
459
+ assert isinstance(root.right, RAOperand)
460
+ assert root.right.name == 'c'
461
+
462
+ root = RAParser.parse_query(r'a ⟗ b ⟗ c')
463
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
464
+ assert isinstance(root.left, BinaryOperators.FullOuterJoin)
465
+ assert isinstance(root.left.left, RAOperand)
466
+ assert root.left.left.name == 'a'
467
+ assert isinstance(root.left.right, RAOperand)
468
+ assert root.left.right.name == 'b'
469
+ assert isinstance(root.right, RAOperand)
470
+ assert root.right.name == 'c'
471
+
472
+ # mixed outer joins
473
+ root = RAParser.parse_query(r'a ⟕ b ⟖ c')
474
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
475
+ assert isinstance(root.left, BinaryOperators.LeftOuterJoin)
476
+ assert isinstance(root.left.left, RAOperand)
477
+ assert root.left.left.name == 'a'
478
+ assert isinstance(root.left.right, RAOperand)
479
+ assert root.left.right.name == 'b'
480
+ assert isinstance(root.right, RAOperand)
481
+ assert root.right.name == 'c'
482
+
483
+ root = RAParser.parse_query(r'a ⟕ b ⟗ c')
484
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
485
+ assert isinstance(root.left, BinaryOperators.LeftOuterJoin)
486
+ assert isinstance(root.left.left, RAOperand)
487
+ assert root.left.left.name == 'a'
488
+ assert isinstance(root.left.right, RAOperand)
489
+ assert root.left.right.name == 'b'
490
+ assert isinstance(root.right, RAOperand)
491
+ assert root.right.name == 'c'
492
+
493
+ root = RAParser.parse_query(r'a ⟖ b ⟕ c')
494
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
495
+ assert isinstance(root.left, BinaryOperators.RightOuterJoin)
496
+ assert isinstance(root.left.left, RAOperand)
497
+ assert root.left.left.name == 'a'
498
+ assert isinstance(root.left.right, RAOperand)
499
+ assert root.left.right.name == 'b'
500
+ assert isinstance(root.right, RAOperand)
501
+ assert root.right.name == 'c'
502
+
503
+ root = RAParser.parse_query(r'a ⟖ b ⟗ c')
504
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
505
+ assert isinstance(root.left, BinaryOperators.RightOuterJoin)
506
+ assert isinstance(root.left.left, RAOperand)
507
+ assert root.left.left.name == 'a'
508
+ assert isinstance(root.left.right, RAOperand)
509
+ assert root.left.right.name == 'b'
510
+ assert isinstance(root.right, RAOperand)
511
+ assert root.right.name == 'c'
512
+
513
+ root = RAParser.parse_query(r'a ⟗ b ⟕ c')
514
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
515
+ assert isinstance(root.left, BinaryOperators.FullOuterJoin)
516
+ assert isinstance(root.left.left, RAOperand)
517
+ assert root.left.left.name == 'a'
518
+ assert isinstance(root.left.right, RAOperand)
519
+ assert root.left.right.name == 'b'
520
+ assert isinstance(root.right, RAOperand)
521
+ assert root.right.name == 'c'
522
+
523
+ root = RAParser.parse_query(r'a ⟗ b ⟖ c')
524
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
525
+ assert isinstance(root.left, BinaryOperators.FullOuterJoin)
526
+ assert isinstance(root.left.left, RAOperand)
527
+ assert root.left.left.name == 'a'
528
+ assert isinstance(root.left.right, RAOperand)
529
+ assert root.left.right.name == 'b'
530
+ assert isinstance(root.right, RAOperand)
531
+ assert root.right.name == 'c'
532
+
533
+
534
+ # cross join
535
+ root = RAParser.parse_query(r'a x b x c')
536
+ assert isinstance(root, BinaryOperators.Cross)
537
+ assert isinstance(root.left, BinaryOperators.Cross)
538
+ assert isinstance(root.left.left, RAOperand)
539
+ assert root.left.left.name == 'a'
540
+ assert isinstance(root.left.right, RAOperand)
541
+ assert root.left.right.name == 'b'
542
+ assert isinstance(root.right, RAOperand)
543
+ assert root.right.name == 'c'
544
+
545
+
316
546
  def test_unary_evaluation_order():
317
547
  root = RAParser.parse_query(r'π [ Id2 ] β [ Id2 ← Id ] (Users)')
318
548
  assert isinstance(root, UnaryOperators.Projection)
@@ -367,6 +597,30 @@ def test_binary_evaluation_order():
367
597
  assert isinstance(root, BinaryOperators.Difference)
368
598
  assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Join)
369
599
 
600
+ root = RAParser.parse_query(r'a \ b ⟕ c')
601
+ assert isinstance(root, BinaryOperators.Difference)
602
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.LeftOuterJoin)
603
+
604
+ root = RAParser.parse_query(r'a ⟕ b \ c')
605
+ assert isinstance(root, BinaryOperators.Difference)
606
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.LeftOuterJoin)
607
+
608
+ root = RAParser.parse_query(r'a \ b ⟖ c')
609
+ assert isinstance(root, BinaryOperators.Difference)
610
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.RightOuterJoin)
611
+
612
+ root = RAParser.parse_query(r'a ⟖ b \ c')
613
+ assert isinstance(root, BinaryOperators.Difference)
614
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.RightOuterJoin)
615
+
616
+ root = RAParser.parse_query(r'a \ b ⟗ c')
617
+ assert isinstance(root, BinaryOperators.Difference)
618
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.FullOuterJoin)
619
+
620
+ root = RAParser.parse_query(r'a ⟗ b \ c')
621
+ assert isinstance(root, BinaryOperators.Difference)
622
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.FullOuterJoin)
623
+
370
624
  # difference <-> cross
371
625
  root = RAParser.parse_query(r'a \ b x c')
372
626
  assert isinstance(root, BinaryOperators.Difference)
@@ -403,6 +657,30 @@ def test_binary_evaluation_order():
403
657
  assert isinstance(root, BinaryOperators.Union)
404
658
  assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Join)
405
659
 
660
+ root = RAParser.parse_query(r'a ∪ b ⟕ c')
661
+ assert isinstance(root, BinaryOperators.Union)
662
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.LeftOuterJoin)
663
+
664
+ root = RAParser.parse_query(r'a ⟕ b ∪ c')
665
+ assert isinstance(root, BinaryOperators.Union)
666
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.LeftOuterJoin)
667
+
668
+ root = RAParser.parse_query(r'a ∪ b ⟖ c')
669
+ assert isinstance(root, BinaryOperators.Union)
670
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.RightOuterJoin)
671
+
672
+ root = RAParser.parse_query(r'a ⟖ b ∪ c')
673
+ assert isinstance(root, BinaryOperators.Union)
674
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.RightOuterJoin)
675
+
676
+ root = RAParser.parse_query(r'a ∪ b ⟗ c')
677
+ assert isinstance(root, BinaryOperators.Union)
678
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.FullOuterJoin)
679
+
680
+ root = RAParser.parse_query(r'a ⟗ b ∪ c')
681
+ assert isinstance(root, BinaryOperators.Union)
682
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.FullOuterJoin)
683
+
406
684
  # union <-> cross
407
685
  root = RAParser.parse_query(r'a ∪ b x c')
408
686
  assert isinstance(root, BinaryOperators.Union)
@@ -430,6 +708,30 @@ def test_binary_evaluation_order():
430
708
  assert isinstance(root, BinaryOperators.Intersection)
431
709
  assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Join)
432
710
 
711
+ root = RAParser.parse_query(r'a ∩ b ⟕ c')
712
+ assert isinstance(root, BinaryOperators.Intersection)
713
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.LeftOuterJoin)
714
+
715
+ root = RAParser.parse_query(r'a ⟕ b ∩ c')
716
+ assert isinstance(root, BinaryOperators.Intersection)
717
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.LeftOuterJoin)
718
+
719
+ root = RAParser.parse_query(r'a ∩ b ⟖ c')
720
+ assert isinstance(root, BinaryOperators.Intersection)
721
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.RightOuterJoin)
722
+
723
+ root = RAParser.parse_query(r'a ⟖ b ∩ c')
724
+ assert isinstance(root, BinaryOperators.Intersection)
725
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.RightOuterJoin)
726
+
727
+ root = RAParser.parse_query(r'a ∩ b ⟗ c')
728
+ assert isinstance(root, BinaryOperators.Intersection)
729
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.FullOuterJoin)
730
+
731
+ root = RAParser.parse_query(r'a ⟗ b ∩ c')
732
+ assert isinstance(root, BinaryOperators.Intersection)
733
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.FullOuterJoin)
734
+
433
735
  # intersection <-> cross
434
736
  root = RAParser.parse_query(r'a ∩ b x c')
435
737
  assert isinstance(root, BinaryOperators.Intersection)
@@ -457,6 +759,30 @@ def test_binary_evaluation_order():
457
759
  assert isinstance(root, BinaryOperators.Join)
458
760
  assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Cross)
459
761
 
762
+ root = RAParser.parse_query(r'a ⟕ b x c')
763
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
764
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.Cross)
765
+
766
+ root = RAParser.parse_query(r'a x b ⟕ c')
767
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
768
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Cross)
769
+
770
+ root = RAParser.parse_query(r'a ⟖ b x c')
771
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
772
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.Cross)
773
+
774
+ root = RAParser.parse_query(r'a x b ⟖ c')
775
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
776
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Cross)
777
+
778
+ root = RAParser.parse_query(r'a ⟗ b x c')
779
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
780
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.Cross)
781
+
782
+ root = RAParser.parse_query(r'a x b ⟗ c')
783
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
784
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Cross)
785
+
460
786
  # join <-> division
461
787
  root = RAParser.parse_query(r'a ⋈ b ÷ c')
462
788
  assert isinstance(root, BinaryOperators.Join)
@@ -466,6 +792,55 @@ def test_binary_evaluation_order():
466
792
  assert isinstance(root, BinaryOperators.Join)
467
793
  assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Division)
468
794
 
795
+ root = RAParser.parse_query(r'a ⟕ b ÷ c')
796
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
797
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.Division)
798
+
799
+ root = RAParser.parse_query(r'a ÷ b ⟕ c')
800
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
801
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Division)
802
+
803
+ root = RAParser.parse_query(r'a ⟖ b ÷ c')
804
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
805
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.Division)
806
+
807
+ root = RAParser.parse_query(r'a ÷ b ⟖ c')
808
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
809
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Division)
810
+
811
+ root = RAParser.parse_query(r'a ⟗ b ÷ c')
812
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
813
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.Division)
814
+
815
+ root = RAParser.parse_query(r'a ÷ b ⟗ c')
816
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
817
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.Division)
818
+
819
+ # natural join <-> outer join
820
+ root = RAParser.parse_query(r'a ⋈ b ⟕ c')
821
+ assert isinstance(root, BinaryOperators.Join)
822
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.LeftOuterJoin)
823
+
824
+ root = RAParser.parse_query(r'a ⟕ b ⋈ c')
825
+ assert isinstance(root, BinaryOperators.Join)
826
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.LeftOuterJoin)
827
+
828
+ root = RAParser.parse_query(r'a ⋈ b ⟖ c')
829
+ assert isinstance(root, BinaryOperators.Join)
830
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.RightOuterJoin)
831
+
832
+ root = RAParser.parse_query(r'a ⟖ b ⋈ c')
833
+ assert isinstance(root, BinaryOperators.Join)
834
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.RightOuterJoin)
835
+
836
+ root = RAParser.parse_query(r'a ⋈ b ⟗ c')
837
+ assert isinstance(root, BinaryOperators.Join)
838
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, BinaryOperators.FullOuterJoin)
839
+
840
+ root = RAParser.parse_query(r'a ⟗ b ⋈ c')
841
+ assert isinstance(root, BinaryOperators.Join)
842
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, BinaryOperators.FullOuterJoin)
843
+
469
844
  # cross <-> division
470
845
  root = RAParser.parse_query(r'a x b ÷ c')
471
846
  assert isinstance(root, BinaryOperators.Cross)
@@ -567,6 +942,30 @@ def test_mixed_evaluation_order():
567
942
  assert isinstance(root, BinaryOperators.Join)
568
943
  assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Projection)
569
944
 
945
+ root = RAParser.parse_query(r'a ⟕ π [ Id ] b')
946
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
947
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Projection)
948
+
949
+ root = RAParser.parse_query(r'π [ Id ] a ⟕ b')
950
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
951
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Projection)
952
+
953
+ root = RAParser.parse_query(r'a ⟖ π [ Id ] b')
954
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
955
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Projection)
956
+
957
+ root = RAParser.parse_query(r'π [ Id ] a ⟖ b')
958
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
959
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Projection)
960
+
961
+ root = RAParser.parse_query(r'a ⟗ π [ Id ] b')
962
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
963
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Projection)
964
+
965
+ root = RAParser.parse_query(r'π [ Id ] a ⟗ b')
966
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
967
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Projection)
968
+
570
969
  # join <-> rename
571
970
  root = RAParser.parse_query(r'a ⋈ β [ Id2 ← Id ] b')
572
971
  assert isinstance(root, BinaryOperators.Join)
@@ -576,6 +975,30 @@ def test_mixed_evaluation_order():
576
975
  assert isinstance(root, BinaryOperators.Join)
577
976
  assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Rename)
578
977
 
978
+ root = RAParser.parse_query(r'a ⟕ β [ Id2 ← Id ] b')
979
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
980
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Rename)
981
+
982
+ root = RAParser.parse_query(r'β [ Id2 ← Id ] a ⟕ b')
983
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
984
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Rename)
985
+
986
+ root = RAParser.parse_query(r'a ⟖ β [ Id2 ← Id ] b')
987
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
988
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Rename)
989
+
990
+ root = RAParser.parse_query(r'β [ Id2 ← Id ] a ⟖ b')
991
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
992
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Rename)
993
+
994
+ root = RAParser.parse_query(r'a ⟗ β [ Id2 ← Id ] b')
995
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
996
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Rename)
997
+
998
+ root = RAParser.parse_query(r'β [ Id2 ← Id ] a ⟗ b')
999
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
1000
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Rename)
1001
+
579
1002
  # join <-> selection
580
1003
  root = RAParser.parse_query(r'a ⋈ σ [ Id > 1 ] b')
581
1004
  assert isinstance(root, BinaryOperators.Join)
@@ -585,6 +1008,30 @@ def test_mixed_evaluation_order():
585
1008
  assert isinstance(root, BinaryOperators.Join)
586
1009
  assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Selection)
587
1010
 
1011
+ root = RAParser.parse_query(r'a ⟕ σ [ Id > 1 ] b')
1012
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
1013
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Selection)
1014
+
1015
+ root = RAParser.parse_query(r'σ [ Id > 1 ] a ⟕ b')
1016
+ assert isinstance(root, BinaryOperators.LeftOuterJoin)
1017
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Selection)
1018
+
1019
+ root = RAParser.parse_query(r'a ⟖ σ [ Id > 1 ] b')
1020
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
1021
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Selection)
1022
+
1023
+ root = RAParser.parse_query(r'σ [ Id > 1 ] a ⟖ b')
1024
+ assert isinstance(root, BinaryOperators.RightOuterJoin)
1025
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Selection)
1026
+
1027
+ root = RAParser.parse_query(r'a ⟗ σ [ Id > 1 ] b')
1028
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
1029
+ assert isinstance(root.left, RAOperand) and isinstance(root.right, UnaryOperators.Selection)
1030
+
1031
+ root = RAParser.parse_query(r'σ [ Id > 1 ] a ⟗ b')
1032
+ assert isinstance(root, BinaryOperators.FullOuterJoin)
1033
+ assert isinstance(root.right, RAOperand) and isinstance(root.left, UnaryOperators.Selection)
1034
+
588
1035
  # cross <-> projection
589
1036
  root = RAParser.parse_query(r'a x π [ Id ] b')
590
1037
  assert isinstance(root, BinaryOperators.Cross)
@@ -657,7 +1104,7 @@ def test_special_queries():
657
1104
  (
658
1105
  Sigma [ Id > 1 ] Pi [ Username, Id ] (Users)
659
1106
  ) x (
660
- Beta [ Username2 <- Username ] Beta [ Id2 <- Id ] (BannedUsers)
1107
+ Beta [ Username2 <- BannedUsername ] Beta [ Id2 <- Id ] (BannedUsers)
661
1108
  )
662
1109
  ''')
663
1110