jupyter-duckdb 1.2.0.1__py3-none-any.whl → 1.4.111__py3-none-any.whl

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 (62) hide show
  1. duckdb_kernel/db/Connection.py +3 -0
  2. duckdb_kernel/db/Table.py +8 -0
  3. duckdb_kernel/db/implementation/duckdb/Connection.py +27 -13
  4. duckdb_kernel/db/implementation/postgres/Connection.py +27 -12
  5. duckdb_kernel/db/implementation/sqlite/Connection.py +9 -3
  6. duckdb_kernel/kernel.py +407 -200
  7. duckdb_kernel/magics/MagicCommand.py +34 -10
  8. duckdb_kernel/magics/MagicCommandCallback.py +11 -7
  9. duckdb_kernel/magics/MagicCommandHandler.py +58 -9
  10. duckdb_kernel/magics/MagicState.py +11 -0
  11. duckdb_kernel/magics/__init__.py +1 -0
  12. duckdb_kernel/parser/DCParser.py +17 -7
  13. duckdb_kernel/parser/LogicParser.py +6 -6
  14. duckdb_kernel/parser/ParserError.py +18 -0
  15. duckdb_kernel/parser/RAParser.py +29 -21
  16. duckdb_kernel/parser/__init__.py +1 -0
  17. duckdb_kernel/parser/elements/DCOperand.py +7 -4
  18. duckdb_kernel/parser/elements/LogicElement.py +0 -2
  19. duckdb_kernel/parser/elements/RAElement.py +4 -1
  20. duckdb_kernel/parser/elements/RARelationReference.py +86 -0
  21. duckdb_kernel/parser/elements/RAUnaryOperator.py +6 -0
  22. duckdb_kernel/parser/elements/__init__.py +2 -1
  23. duckdb_kernel/parser/elements/binary/And.py +1 -1
  24. duckdb_kernel/parser/elements/binary/ConditionalSet.py +37 -10
  25. duckdb_kernel/parser/elements/binary/Cross.py +2 -2
  26. duckdb_kernel/parser/elements/binary/Difference.py +1 -1
  27. duckdb_kernel/parser/elements/binary/Divide.py +1 -1
  28. duckdb_kernel/parser/elements/binary/Division.py +0 -4
  29. duckdb_kernel/parser/elements/binary/FullOuterJoin.py +40 -0
  30. duckdb_kernel/parser/elements/binary/Join.py +4 -1
  31. duckdb_kernel/parser/elements/binary/LeftOuterJoin.py +27 -0
  32. duckdb_kernel/parser/elements/binary/LeftSemiJoin.py +27 -0
  33. duckdb_kernel/parser/elements/binary/RightOuterJoin.py +27 -0
  34. duckdb_kernel/parser/elements/binary/RightSemiJoin.py +27 -0
  35. duckdb_kernel/parser/elements/binary/__init__.py +21 -6
  36. duckdb_kernel/parser/elements/unary/AttributeRename.py +39 -0
  37. duckdb_kernel/parser/elements/unary/Projection.py +1 -1
  38. duckdb_kernel/parser/elements/unary/Rename.py +68 -14
  39. duckdb_kernel/parser/elements/unary/__init__.py +2 -0
  40. duckdb_kernel/parser/tokenizer/Token.py +24 -3
  41. duckdb_kernel/parser/util/QuerySplitter.py +87 -0
  42. duckdb_kernel/parser/util/RenamableColumnList.py +10 -2
  43. duckdb_kernel/tests/__init__.py +76 -0
  44. duckdb_kernel/tests/test_dc.py +483 -0
  45. duckdb_kernel/tests/test_ra.py +1966 -0
  46. duckdb_kernel/tests/test_result_comparison.py +173 -0
  47. duckdb_kernel/tests/test_sql.py +48 -0
  48. duckdb_kernel/util/ResultSetComparator.py +22 -4
  49. duckdb_kernel/util/SQL.py +6 -0
  50. duckdb_kernel/util/TestError.py +4 -0
  51. duckdb_kernel/visualization/Plotly.py +144 -0
  52. duckdb_kernel/visualization/RATreeDrawer.py +34 -2
  53. duckdb_kernel/visualization/__init__.py +1 -0
  54. duckdb_kernel/visualization/lib/__init__.py +53 -0
  55. duckdb_kernel/visualization/lib/plotly-3.0.1.min.js +3879 -0
  56. duckdb_kernel/visualization/lib/ra.css +3 -0
  57. duckdb_kernel/visualization/lib/ra.js +55 -0
  58. {jupyter_duckdb-1.2.0.1.dist-info → jupyter_duckdb-1.4.111.dist-info}/METADATA +53 -19
  59. jupyter_duckdb-1.4.111.dist-info/RECORD +104 -0
  60. {jupyter_duckdb-1.2.0.1.dist-info → jupyter_duckdb-1.4.111.dist-info}/WHEEL +1 -1
  61. jupyter_duckdb-1.2.0.1.dist-info/RECORD +0 -82
  62. {jupyter_duckdb-1.2.0.1.dist-info → jupyter_duckdb-1.4.111.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,9 @@ class Connection:
10
10
  def close(self):
11
11
  pass
12
12
 
13
+ def copy(self) -> 'Connection':
14
+ raise NotImplementedError
15
+
13
16
  @staticmethod
14
17
  def plain_explain() -> bool:
15
18
  return False
duckdb_kernel/db/Table.py CHANGED
@@ -14,11 +14,19 @@ class Table:
14
14
  self.unique_keys: List[Constraint] = []
15
15
  self.foreign_keys: List[ForeignKey] = []
16
16
 
17
+ @staticmethod
18
+ def normalize_name(name: str) -> str:
19
+ return name.lower()
20
+
17
21
  @property
18
22
  def id(self) -> str:
19
23
  name = re.sub(r'[^A-Za-z]', '_', self.name)
20
24
  return f'table_{name}'
21
25
 
26
+ @property
27
+ def normalized_name(self) -> str:
28
+ return self.normalize_name(self.name)
29
+
22
30
  def get_column(self, name: str) -> "Column":
23
31
  for column in self.columns:
24
32
  if column.name == name:
@@ -4,6 +4,7 @@ import duckdb
4
4
 
5
5
  from ... import DatabaseError, Column, Constraint, ForeignKey, Table
6
6
  from ...Connection import Connection as Base
7
+ from ...error import EmptyResultError
7
8
 
8
9
 
9
10
  class Connection(Base):
@@ -14,6 +15,9 @@ class Connection(Base):
14
15
  def close(self):
15
16
  self.con.close()
16
17
 
18
+ def copy(self) -> 'Connection':
19
+ return Connection(self.path)
20
+
17
21
  @staticmethod
18
22
  def plain_explain() -> bool:
19
23
  return True
@@ -36,7 +40,10 @@ class Connection(Base):
36
40
  raise e
37
41
 
38
42
  # get rows
39
- rows = cursor.fetchall()
43
+ try:
44
+ rows = cursor.fetchall()
45
+ except duckdb.InvalidInputException as e:
46
+ raise EmptyResultError(str(e))
40
47
 
41
48
  # get columns
42
49
  if cursor.description is None:
@@ -59,7 +66,7 @@ class Connection(Base):
59
66
  WHERE table_type == 'BASE TABLE'
60
67
  ''').fetchall():
61
68
  table = Table(table_name)
62
- tables[table_name] = table
69
+ tables[table.normalized_name] = table
63
70
 
64
71
  # Get column names and data types for each table.
65
72
  for table_name, column_name, data_type, is_nullable in self.con.execute('''
@@ -71,8 +78,9 @@ class Connection(Base):
71
78
  FROM information_schema.columns
72
79
  ORDER BY ordinal_position ASC
73
80
  ''').fetchall():
74
- if table_name in tables:
75
- table = tables[table_name]
81
+ normalized_table_name = Table.normalize_name(table_name)
82
+ if normalized_table_name in tables:
83
+ table = tables[normalized_table_name]
76
84
 
77
85
  column = Column(table, column_name, data_type, is_nullable == 'YES')
78
86
  table.columns.append(column)
@@ -88,10 +96,12 @@ class Connection(Base):
88
96
  ORDER BY constraint_index ASC
89
97
  ''').fetchall():
90
98
  # get table
91
- if table_name not in tables:
99
+ normalized_table_name = Table.normalize_name(table_name)
100
+
101
+ if normalized_table_name not in tables:
92
102
  raise AssertionError(f'unknown table {table_name} for constraint {constraint_index}')
93
103
 
94
- table = tables[table_name]
104
+ table = tables[normalized_table_name]
95
105
 
96
106
  # store constraint
97
107
  if constraint_index in constraints:
@@ -102,7 +112,7 @@ class Connection(Base):
102
112
  table,
103
113
  tuple(table.get_column(c) for c in constraint_columns)
104
114
  )
105
- constraints[(table_name, *constraint_columns)] = constraint
115
+ constraints[(normalized_table_name, *constraint_columns)] = constraint
106
116
 
107
117
  # store key
108
118
  if table.primary_key is not None:
@@ -121,10 +131,12 @@ class Connection(Base):
121
131
  ORDER BY constraint_index ASC
122
132
  ''').fetchall():
123
133
  # get table
124
- if table_name not in tables:
134
+ normalized_table_name = Table.normalize_name(table_name)
135
+
136
+ if normalized_table_name not in tables:
125
137
  raise AssertionError(f'unknown table {table_name} for constraint {constraint_index}')
126
138
 
127
- table = tables[table_name]
139
+ table = tables[normalized_table_name]
128
140
 
129
141
  # store constraint
130
142
  if constraint_index in constraints:
@@ -135,7 +147,7 @@ class Connection(Base):
135
147
  table,
136
148
  tuple(table.get_column(c) for c in constraint_columns)
137
149
  )
138
- constraints[(table_name, *constraint_columns)] = constraint
150
+ constraints[(normalized_table_name, *constraint_columns)] = constraint
139
151
 
140
152
  # store key
141
153
  table.unique_keys.append(constraint)
@@ -153,13 +165,15 @@ class Connection(Base):
153
165
  ORDER BY constraint_index ASC
154
166
  ''').fetchall():
155
167
  # get table
156
- if table_name not in tables:
168
+ normalized_table_name = Table.normalize_name(table_name)
169
+
170
+ if normalized_table_name not in tables:
157
171
  raise AssertionError(f'unknown table {table_name} for constraint {constraint_index}')
158
172
 
159
- table = tables[table_name]
173
+ table = tables[normalized_table_name]
160
174
 
161
175
  # lookup constraint
162
- constraint_key = (referenced_table, *referenced_column_names)
176
+ constraint_key = (Table.normalize_name(referenced_table), *referenced_column_names)
163
177
  if constraint_key not in constraints:
164
178
  raise AssertionError(f'constraint with key {constraint_key} not discovered previously')
165
179
 
@@ -19,6 +19,7 @@ class Connection(Base):
19
19
  self.host: str = host
20
20
  self.port: int = port
21
21
  self.username: Optional[str] = username
22
+ self.password: Optional[str] = password
22
23
 
23
24
  options = {
24
25
  'host': host,
@@ -43,6 +44,9 @@ class Connection(Base):
43
44
  def close(self):
44
45
  self.con.close()
45
46
 
47
+ def copy(self) -> 'Connection':
48
+ return Connection(self.host, self.port, self.username, self.password, self.database_name)
49
+
46
50
  def __str__(self) -> str:
47
51
  user = f'{self.username}@' if self.username is not None else ''
48
52
  return f'PostgreSQL: {user}{self.host}:{self.port}/{self.database_name}'
@@ -63,7 +67,10 @@ class Connection(Base):
63
67
  rows = cursor.fetchall()
64
68
  except psycopg.ProgrammingError as e:
65
69
  text = str(e)
66
- if text == "the last operation didn't produce a result":
70
+ if text.startswith((
71
+ "the last operation didn't produce a result",
72
+ "the last operation didn't produce records"
73
+ )):
67
74
  raise EmptyResultError()
68
75
  else:
69
76
  raise e
@@ -88,7 +95,7 @@ class Connection(Base):
88
95
  WHERE table_schema='public' AND table_type='BASE TABLE'
89
96
  ''').fetchall():
90
97
  table = Table(table_name)
91
- tables[table_name] = table
98
+ tables[table.normalized_name] = table
92
99
 
93
100
  # Get column names and data types for each table.
94
101
  for table_name, column_name, data_type, is_nullable in self.con.execute('''
@@ -100,8 +107,9 @@ class Connection(Base):
100
107
  FROM information_schema.columns
101
108
  ORDER BY ordinal_position ASC
102
109
  ''').fetchall():
103
- if table_name in tables:
104
- table = tables[table_name]
110
+ normalized_table_name = Table.normalize_name(table_name)
111
+ if normalized_table_name in tables:
112
+ table = tables[normalized_table_name]
105
113
 
106
114
  column = Column(table, column_name, data_type, is_nullable == 'YES')
107
115
  table.columns.append(column)
@@ -126,10 +134,12 @@ class Connection(Base):
126
134
 
127
135
  for constraint_name, (table_name, column_names) in constraints_dict.items():
128
136
  # get table
129
- if table_name not in tables:
137
+ normalized_table_name = Table.normalize_name(table_name)
138
+
139
+ if normalized_table_name not in tables:
130
140
  raise AssertionError(f'unknown table {table_name} for constraint {constraint_index}')
131
141
 
132
- table = tables[table_name]
142
+ table = tables[normalized_table_name]
133
143
 
134
144
  # store constraint
135
145
  constraint = Constraint(
@@ -166,10 +176,12 @@ class Connection(Base):
166
176
 
167
177
  for constraint_name, (table_name, column_names) in constraints_dict.items():
168
178
  # get table
169
- if table_name not in tables:
179
+ normalized_table_name = Table.normalize_name(table_name)
180
+
181
+ if normalized_table_name not in tables:
170
182
  raise AssertionError(f'unknown table {table_name} for constraint {constraint_index}')
171
183
 
172
- table = tables[table_name]
184
+ table = tables[normalized_table_name]
173
185
 
174
186
  # store constraint
175
187
  constraint = Constraint(
@@ -197,20 +209,23 @@ class Connection(Base):
197
209
  raise AssertionError(f'could not parse foreign key definitions for table {source_table_name}')
198
210
 
199
211
  source_table_name = strip_delimiters(source_table_name)
212
+ normalized_source_table_name = Table.normalize_name(source_table_name)
200
213
  source_table_column_names = [strip_delimiters(c) for c in match.group(1).split(',')]
214
+
201
215
  target_table_name = strip_delimiters(match.group(2))
216
+ normalized_target_table_name = Table.normalize_name(target_table_name)
202
217
  target_table_column_names = [strip_delimiters(c) for c in match.group(3).split(',')]
203
218
 
204
219
  # get tables
205
- if source_table_name not in tables:
220
+ if normalized_source_table_name not in tables:
206
221
  raise AssertionError(f'unknown table {source_table_name} for foreign key {fk_name}')
207
222
 
208
- source_table = tables[source_table_name]
223
+ source_table = tables[normalized_source_table_name]
209
224
 
210
- if target_table_name not in tables:
225
+ if normalized_target_table_name not in tables:
211
226
  raise AssertionError(f'unknown table {target_table_name} for foreign key {fk_name}')
212
227
 
213
- target_table = tables[target_table_name]
228
+ target_table = tables[normalized_target_table_name]
214
229
 
215
230
  # store constraint
216
231
  constraint = Constraint(
@@ -4,6 +4,7 @@ from typing import Dict, List, Tuple, Any
4
4
 
5
5
  from ... import DatabaseError, Column, Constraint, ForeignKey, Table
6
6
  from ...Connection import Connection as Base
7
+ from ...error import EmptyResultError
7
8
 
8
9
 
9
10
  class Connection(Base):
@@ -15,6 +16,9 @@ class Connection(Base):
15
16
  def close(self):
16
17
  self.con.close()
17
18
 
19
+ def copy(self) -> 'Connection':
20
+ return Connection(self.path)
21
+
18
22
  @staticmethod
19
23
  def multiple_statements_per_query() -> bool:
20
24
  return False
@@ -41,7 +45,7 @@ class Connection(Base):
41
45
 
42
46
  # get columns
43
47
  if cursor.description is None:
44
- columns = []
48
+ raise EmptyResultError()
45
49
  else:
46
50
  columns = [e[0] for e in cursor.description]
47
51
 
@@ -61,7 +65,7 @@ class Connection(Base):
61
65
  WHERE type ='table' AND name NOT LIKE 'sqlite_%';
62
66
  ''').fetchall():
63
67
  table = Table(table_name)
64
- tables[table_name] = table
68
+ tables[table.normalized_name] = table
65
69
 
66
70
  # Get column names and data types for each table.
67
71
  for table_name, table in tables.items():
@@ -174,8 +178,10 @@ class Connection(Base):
174
178
  current_targets = []
175
179
 
176
180
  # add columns to parse later
181
+ normalized_to_table_name = Table.normalize_name(to_table_name)
182
+
177
183
  current_sources.append(table.get_column(from_col))
178
- current_targets.append(tables[to_table_name].get_column(to_col))
184
+ current_targets.append(tables[normalized_to_table_name].get_column(to_col))
179
185
 
180
186
  if len(current_sources) > 0:
181
187
  store()