jupyter-duckdb 0.9.2.1.dev202311200802__tar.gz → 0.9.2.2__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 (84) hide show
  1. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/PKG-INFO +1 -1
  2. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/setup.py +3 -4
  3. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/RAOperand.py +5 -2
  4. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +29 -8
  5. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/jupyter_duckdb.egg-info/PKG-INFO +1 -1
  6. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/test/test_dc.py +35 -0
  7. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/test/test_ra.py +25 -0
  8. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/README.md +0 -0
  9. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/setup.cfg +0 -0
  10. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/__init__.py +0 -0
  11. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/__main__.py +0 -0
  12. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/Column.py +0 -0
  13. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/Connection.py +0 -0
  14. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/Constraint.py +0 -0
  15. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/DatabaseError.py +0 -0
  16. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/ForeignKey.py +0 -0
  17. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/Table.py +0 -0
  18. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/__init__.py +0 -0
  19. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/duckdb/Connection.py +0 -0
  20. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/duckdb/__init__.py +0 -0
  21. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/sqlite/Connection.py +0 -0
  22. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/db/sqlite/__init__.py +0 -0
  23. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/kernel.json +0 -0
  24. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/kernel.py +0 -0
  25. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/magics/MagicCommand.py +0 -0
  26. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/magics/MagicCommandCallback.py +0 -0
  27. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
  28. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/magics/MagicCommandHandler.py +0 -0
  29. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/magics/__init__.py +0 -0
  30. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/DCParser.py +0 -0
  31. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/LogicParser.py +0 -0
  32. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/RAParser.py +0 -0
  33. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/__init__.py +0 -0
  34. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/DCOperand.py +0 -0
  35. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -0
  36. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
  37. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
  38. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
  39. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/RAElement.py +0 -0
  40. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
  41. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +0 -0
  42. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/__init__.py +0 -0
  43. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
  44. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
  45. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
  46. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Cross.py +0 -0
  47. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
  48. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
  49. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
  50. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
  51. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
  52. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
  53. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Join.py +0 -0
  54. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
  55. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
  56. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
  57. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
  58. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
  59. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
  60. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
  61. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/binary/__init__.py +0 -0
  62. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
  63. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/unary/Projection.py +0 -0
  64. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/unary/Rename.py +0 -0
  65. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
  66. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/elements/unary/__init__.py +0 -0
  67. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/tokenizer/Token.py +0 -0
  68. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
  69. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
  70. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
  71. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/util/RenamableColumnList.py +0 -0
  72. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/parser/util/__init__.py +0 -0
  73. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/util/ResultSetComparator.py +0 -0
  74. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/util/__init__.py +0 -0
  75. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/util/formatting.py +0 -0
  76. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/visualization/Drawer.py +0 -0
  77. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
  78. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
  79. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/duckdb_kernel/visualization/__init__.py +0 -0
  80. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/jupyter_duckdb.egg-info/SOURCES.txt +0 -0
  81. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
  82. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
  83. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/src/jupyter_duckdb.egg-info/top_level.txt +0 -0
  84. {jupyter-duckdb-0.9.2.1.dev202311200802 → jupyter-duckdb-0.9.2.2}/test/test_result_comparison.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jupyter-duckdb
3
- Version: 0.9.2.1.dev202311200802
3
+ Version: 0.9.2.2
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,13 +1,12 @@
1
1
  import os
2
+ import re
2
3
  from datetime import datetime
3
4
 
4
5
  from setuptools import setup, find_packages
5
6
 
6
7
  # version
7
- version = '0.9.2.1'
8
-
9
- if os.getenv('DEV') == '1':
10
- version += '.dev' + datetime.now().strftime('%Y%m%d%H%M')
8
+ version = os.getenv('PACKAGE_VERSION', '1.0')
9
+ version = re.sub(r'[^dev0-9._]', '', version)
11
10
 
12
11
  # install requires
13
12
  install_requires = [
@@ -22,10 +22,13 @@ class RAOperand(RAElement):
22
22
  yield
23
23
 
24
24
  def to_sql(self, tables: Dict[str, Table]) -> Tuple[str, RenamableColumnList]:
25
- if self.relation not in tables:
25
+ table_keys = {key.lower(): key for key in tables}
26
+ relation_lower = self.relation.lower()
27
+
28
+ if relation_lower not in table_keys:
26
29
  raise AssertionError(f'unknown relation {self.relation}')
27
30
 
28
- cols = RenamableColumnList.from_iter(tables[self.name].columns)
31
+ cols = RenamableColumnList.from_iter(tables[table_keys[relation_lower]].columns)
29
32
  column_names = ', '.join(c.rename() for c in cols)
30
33
 
31
34
  return f'SELECT DISTINCT {column_names} FROM {self.relation}', cols
@@ -149,9 +149,9 @@ class ConditionalSet:
149
149
  # names for the attributes. We store the related operands and the join conditions.
150
150
  # Furthermore, `select_columns` contains a mapping from column names to a part of
151
151
  # a "select as" statement and joined_columns is a list of all columns that were
152
- # used for joins, so the resulting sql statement do not become ambiguous if they
152
+ # used for joins, so the resulting sql statements do not become ambiguous if they
153
153
  # are used for filtering.
154
- positive_joins: List[Tuple[str, str, List[str]]] = []
154
+ positive_joins: List[Tuple[str, str, Optional[List[str]]]] = []
155
155
  select_columns: Dict[str, str] = {}
156
156
  joined_columns: RenamableColumnList = RenamableColumnList()
157
157
 
@@ -206,9 +206,22 @@ class ConditionalSet:
206
206
  already_joined.add(join_tuple)
207
207
  discovered_joins += 1
208
208
 
209
- # If no joins were discovered using this table, an exception is raised.
209
+ # If no common attributes were discovered using this table,
210
+ # a cross join is used instead.
210
211
  if discovered_joins == 0:
211
- raise AssertionError('no common attributes found for join')
212
+ # Find any other table for the cross join, so the joins
213
+ # can later be constructed.
214
+ for right_name, _, _ in relevant_positive:
215
+ if left_name != right_name:
216
+ break
217
+ else:
218
+ raise AssertionError(f'could not build join for relation {left_name}')
219
+
220
+ join_tuple = min(left_name, right_name), max(left_name, right_name)
221
+
222
+ # Store the join with a join condition that is None.
223
+ positive_joins.append((left_name, right_name, None))
224
+ already_joined.add(join_tuple)
212
225
 
213
226
  # Last but not least we need to include the "negative" joins. They only
214
227
  # remove tuples and never add any attributes, so we only track the
@@ -249,12 +262,17 @@ class ConditionalSet:
249
262
  for _, target_name, join_condition in positive_joins:
250
263
  if target_name not in all_positive_conditions:
251
264
  all_positive_conditions[target_name] = []
252
- all_positive_conditions[target_name].extend(join_condition)
265
+ if join_condition is not None:
266
+ all_positive_conditions[target_name].extend(join_condition)
253
267
 
254
- sorted_positive_joins: List[Tuple[str, str, str]] = []
268
+ sorted_positive_joins: List[Tuple[str, str, Optional[str]]] = []
255
269
  while len(used_relations) < len(relevant_positive):
256
270
  for source_name, target_name, _ in positive_joins:
257
- join_condition = ' AND '.join(all_positive_conditions[target_name])
271
+ apc = all_positive_conditions[target_name]
272
+ if len(apc) == 0:
273
+ join_condition = None
274
+ else:
275
+ join_condition = ' AND '.join(apc)
258
276
 
259
277
  if source_name in used_relations and target_name not in used_relations:
260
278
  sorted_positive_joins.append((source_name, target_name, join_condition))
@@ -308,7 +326,10 @@ class ConditionalSet:
308
326
  sql_tables = table_statements[relevant_positive[0][0]]
309
327
  for _, target_name, join_condition in sorted_positive_joins:
310
328
  target_table_stmt = table_statements[target_name]
311
- sql_tables += f' JOIN {target_table_stmt} ON {join_condition}'
329
+ if join_condition is None:
330
+ sql_tables += f' CROSS JOIN {target_table_stmt}'
331
+ else:
332
+ sql_tables += f' JOIN {target_table_stmt} ON {join_condition}'
312
333
  for _, target_name, join_condition, _ in sorted_negative_joins:
313
334
  target_table_stmt = table_statements[target_name]
314
335
  sql_tables += f' LEFT JOIN {target_table_stmt} ON {join_condition}'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jupyter-duckdb
3
- Version: 0.9.2.1.dev202311200802
3
+ Version: 0.9.2.2
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
@@ -30,6 +30,23 @@ def test_simple_queries():
30
30
  ]
31
31
 
32
32
 
33
+ def test_asterisk_projection():
34
+ with Connection() as con:
35
+ root = DCParser.parse_query('{ * | users(id, _) }')
36
+ assert con.execute_dc(root) == [
37
+ (1,),
38
+ (2,),
39
+ (3,)
40
+ ]
41
+
42
+ root = DCParser.parse_query('{ * | users(id, name) }')
43
+ assert con.execute_dc(root) == [
44
+ (1, 'Alice'),
45
+ (2, 'Bob'),
46
+ (3, 'Charlie')
47
+ ]
48
+
49
+
33
50
  def test_conditions():
34
51
  with Connection() as con:
35
52
  for query in [
@@ -135,6 +152,24 @@ def test_joins():
135
152
  ('Show 2', 'Actor F')
136
153
  ]
137
154
 
155
+ # cross join
156
+ root = DCParser.parse_query('{ sename | shows(shid1, shname) ∧ seasons(senum, shid2, sename) ∧ shid1 = shid2 }')
157
+ assert con.execute_dc(root) == [
158
+ ('Show 1 / Season 1',),
159
+ ('Show 1 / Season 2',),
160
+ ('Show 2 / Season 1',),
161
+ ('Show 2 / Season 2',)
162
+ ]
163
+
164
+ for query in [
165
+ '{ s2,c5 | shows(sa1,s2) ∧ episodes(e1,e2,sb1,e4) ∧ characters(c1,e1,c3,sb1,c5) ∧ sa1=2 ∧ sa1 = sb1 ∧ e4="Show 2 / Season 1 / Episode 2" }',
166
+ '{ s2,c5 | shows(sa1,s2) ∧ episodes(e1,e2,sb1,e4) ∧ characters(c1,e1,c3,sc1,c5) ∧ sa1=2 ∧ sa1 = sb1 ∧ sb1 = sc1 ∧ e4="Show 2 / Season 1 / Episode 2" }'
167
+ ]:
168
+ root = DCParser.parse_query(query)
169
+ assert con.execute_dc(root) == [
170
+ ('Show 2', 'Actor F')
171
+ ]
172
+
138
173
 
139
174
  def test_underscores():
140
175
  with Connection() as con:
@@ -7,6 +7,31 @@ from duckdb_kernel.parser.elements import RAOperand, LogicElement
7
7
  from . import Connection
8
8
 
9
9
 
10
+ def test_case_insensitivity():
11
+ for query in (
12
+ 'users',
13
+ 'Users',
14
+ 'USERS',
15
+ 'userS'
16
+ ):
17
+ root = RAParser.parse_query(query)
18
+
19
+ # root is an RAOperand
20
+ assert isinstance(root, RAOperand)
21
+
22
+ # Root's name is the relation name in whatever case
23
+ # it has been written.
24
+ assert root.name == query
25
+
26
+ # execute to test case insensitivity
27
+ with Connection() as con:
28
+ assert con.execute_ra(root) == [
29
+ (1, 'Alice'),
30
+ (2, 'Bob'),
31
+ (3, 'Charlie')
32
+ ]
33
+
34
+
10
35
  def test_binary_operator_cross():
11
36
  for query in (
12
37
  r'shows x seasons',