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.
- duckdb_kernel/db/Connection.py +3 -0
- duckdb_kernel/db/Table.py +8 -0
- duckdb_kernel/db/implementation/duckdb/Connection.py +27 -13
- duckdb_kernel/db/implementation/postgres/Connection.py +27 -12
- duckdb_kernel/db/implementation/sqlite/Connection.py +9 -3
- duckdb_kernel/kernel.py +407 -200
- duckdb_kernel/magics/MagicCommand.py +34 -10
- duckdb_kernel/magics/MagicCommandCallback.py +11 -7
- duckdb_kernel/magics/MagicCommandHandler.py +58 -9
- duckdb_kernel/magics/MagicState.py +11 -0
- duckdb_kernel/magics/__init__.py +1 -0
- duckdb_kernel/parser/DCParser.py +17 -7
- duckdb_kernel/parser/LogicParser.py +6 -6
- duckdb_kernel/parser/ParserError.py +18 -0
- duckdb_kernel/parser/RAParser.py +29 -21
- duckdb_kernel/parser/__init__.py +1 -0
- duckdb_kernel/parser/elements/DCOperand.py +7 -4
- duckdb_kernel/parser/elements/LogicElement.py +0 -2
- duckdb_kernel/parser/elements/RAElement.py +4 -1
- duckdb_kernel/parser/elements/RARelationReference.py +86 -0
- duckdb_kernel/parser/elements/RAUnaryOperator.py +6 -0
- duckdb_kernel/parser/elements/__init__.py +2 -1
- duckdb_kernel/parser/elements/binary/And.py +1 -1
- duckdb_kernel/parser/elements/binary/ConditionalSet.py +37 -10
- duckdb_kernel/parser/elements/binary/Cross.py +2 -2
- duckdb_kernel/parser/elements/binary/Difference.py +1 -1
- duckdb_kernel/parser/elements/binary/Divide.py +1 -1
- duckdb_kernel/parser/elements/binary/Division.py +0 -4
- duckdb_kernel/parser/elements/binary/FullOuterJoin.py +40 -0
- duckdb_kernel/parser/elements/binary/Join.py +4 -1
- duckdb_kernel/parser/elements/binary/LeftOuterJoin.py +27 -0
- duckdb_kernel/parser/elements/binary/LeftSemiJoin.py +27 -0
- duckdb_kernel/parser/elements/binary/RightOuterJoin.py +27 -0
- duckdb_kernel/parser/elements/binary/RightSemiJoin.py +27 -0
- duckdb_kernel/parser/elements/binary/__init__.py +21 -6
- duckdb_kernel/parser/elements/unary/AttributeRename.py +39 -0
- duckdb_kernel/parser/elements/unary/Projection.py +1 -1
- duckdb_kernel/parser/elements/unary/Rename.py +68 -14
- duckdb_kernel/parser/elements/unary/__init__.py +2 -0
- duckdb_kernel/parser/tokenizer/Token.py +24 -3
- duckdb_kernel/parser/util/QuerySplitter.py +87 -0
- duckdb_kernel/parser/util/RenamableColumnList.py +10 -2
- duckdb_kernel/tests/__init__.py +76 -0
- duckdb_kernel/tests/test_dc.py +483 -0
- duckdb_kernel/tests/test_ra.py +1966 -0
- duckdb_kernel/tests/test_result_comparison.py +173 -0
- duckdb_kernel/tests/test_sql.py +48 -0
- duckdb_kernel/util/ResultSetComparator.py +22 -4
- duckdb_kernel/util/SQL.py +6 -0
- duckdb_kernel/util/TestError.py +4 -0
- duckdb_kernel/visualization/Plotly.py +144 -0
- duckdb_kernel/visualization/RATreeDrawer.py +34 -2
- duckdb_kernel/visualization/__init__.py +1 -0
- duckdb_kernel/visualization/lib/__init__.py +53 -0
- duckdb_kernel/visualization/lib/plotly-3.0.1.min.js +3879 -0
- duckdb_kernel/visualization/lib/ra.css +3 -0
- duckdb_kernel/visualization/lib/ra.js +55 -0
- {jupyter_duckdb-1.2.0.1.dist-info → jupyter_duckdb-1.4.111.dist-info}/METADATA +53 -19
- jupyter_duckdb-1.4.111.dist-info/RECORD +104 -0
- {jupyter_duckdb-1.2.0.1.dist-info → jupyter_duckdb-1.4.111.dist-info}/WHEEL +1 -1
- jupyter_duckdb-1.2.0.1.dist-info/RECORD +0 -82
- {jupyter_duckdb-1.2.0.1.dist-info → jupyter_duckdb-1.4.111.dist-info}/top_level.txt +0 -0
duckdb_kernel/db/Connection.py
CHANGED
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
|
-
|
|
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[
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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[
|
|
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[(
|
|
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
|
-
|
|
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[
|
|
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[(
|
|
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
|
-
|
|
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[
|
|
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
|
|
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[
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
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[
|
|
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
|
|
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[
|
|
223
|
+
source_table = tables[normalized_source_table_name]
|
|
209
224
|
|
|
210
|
-
if
|
|
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[
|
|
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
|
-
|
|
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[
|
|
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[
|
|
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()
|