jupyter-duckdb 1.2.101__py3-none-any.whl → 1.2.103__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/kernel.py CHANGED
@@ -39,8 +39,11 @@ class DuckDBKernel(Kernel):
39
39
  self._magics: MagicCommandHandler = MagicCommandHandler()
40
40
 
41
41
  self._magics.add(
42
- MagicCommand('create').arg('database').opt('of').opt('with_tests').on(self._create_magic),
43
- MagicCommand('load').arg('database').opt('with_tests').on(self._load_magic),
42
+ MagicCommand('create').arg('database').opt('of').opt('name').on(self._create_magic),
43
+ MagicCommand('load').arg('database').opt('name').on(self._load_magic),
44
+ MagicCommand('copy').arg('source').arg('target').on(self._copy_magic),
45
+ MagicCommand('use').arg('name').on(self._use_magic),
46
+ MagicCommand('load_tests').arg('tests').on(self._load_tests_magic),
44
47
  MagicCommand('test').arg('name').result(True).on(self._test_magic),
45
48
  MagicCommand('all', 'all_rows').on(self._all_magic),
46
49
  MagicCommand('max_rows').arg('count').on(self._max_rows_magic),
@@ -59,8 +62,8 @@ class DuckDBKernel(Kernel):
59
62
  )
60
63
 
61
64
  # create placeholders for database and tests
62
- self._db: Optional[Connection] = None
63
- self._tests: Optional[Dict] = None
65
+ self._db: Dict[str, Connection] = {}
66
+ self._tests: Dict = {}
64
67
 
65
68
  # output related functions
66
69
  def print(self, text: str, name: str = 'stdout'):
@@ -97,131 +100,143 @@ class DuckDBKernel(Kernel):
97
100
  })
98
101
 
99
102
  # database related functions
100
- def _load_database(self, path: str):
101
- if self._db is None:
102
- # If the provided path looks like a postgres url,
103
- # we want to use the postgres driver.
104
- if path.startswith(('postgresql://', 'postgres://', 'pgsql://', 'psql://', 'pg://')):
105
- # pull data from connection string
106
- re_expr = r'(postgresql|postgres|pgsql|psql|pg)://((.*?)(:(.*?))?@)?(.*?)(:(\d+))?(/(.*))?'
107
- match = re.fullmatch(re_expr, path)
108
-
109
- host = match.group(6)
110
- port = int(match.group(8)) if match.group(8) is not None else 5432
111
- username = match.group(3)
112
- password = match.group(5)
113
- database_name = match.group(10)
114
-
115
- # load and create instance
116
- try:
117
- from .db.implementation.postgres import Connection as Postgres
118
- self._db = Postgres(host, port, username, password, database_name)
119
- except ImportError:
120
- self.print('psycopg could not be found', name='stderr')
121
-
122
- # Otherwise the provided path is used to create an
123
- # in-process instance.
124
- else:
125
- # By default, we try to load DuckDB.
126
- try:
127
- from .db.implementation.duckdb import Connection as DuckDB
128
- self._db = DuckDB(path)
103
+ def _load_database(self, path: str, name: str) -> Connection:
104
+ if name in self._db:
105
+ raise ValueError(f'duplicate database name {name}')
106
+
107
+ # If the provided path looks like a postgres url,
108
+ # we want to use the postgres driver.
109
+ if path.startswith(('postgresql://', 'postgres://', 'pgsql://', 'psql://', 'pg://')):
110
+ # pull data from connection string
111
+ re_expr = r'(postgresql|postgres|pgsql|psql|pg)://((.*?)(:(.*?))?@)?(.*?)(:(\d+))?(/(.*))?'
112
+ match = re.fullmatch(re_expr, path)
113
+
114
+ host = match.group(6)
115
+ port = int(match.group(8)) if match.group(8) is not None else 5432
116
+ username = match.group(3)
117
+ password = match.group(5)
118
+ database_name = match.group(10)
119
+
120
+ # load and create instance
121
+ from .db.implementation.postgres import Connection as Postgres
122
+ self._db[name] = Postgres(host, port, username, password, database_name)
123
+
124
+ # Otherwise the provided path is used to create an
125
+ # in-process instance.
126
+ else:
127
+ # By default, we try to load DuckDB.
128
+ try:
129
+ from .db.implementation.duckdb import Connection as DuckDB
130
+ self._db[name] = DuckDB(path)
129
131
 
130
- # If DuckDB is not installed or fails to load,
131
- # we use SQLite instead.
132
- except ImportError:
133
- self.print('DuckDB is not available\n')
132
+ # If DuckDB is not installed or fails to load,
133
+ # we use SQLite instead.
134
+ except ImportError:
135
+ self.print('DuckDB is not available\n')
134
136
 
135
- from .db.implementation.sqlite import Connection as SQLite
136
- self._db = SQLite(path)
137
+ from .db.implementation.sqlite import Connection as SQLite
138
+ self._db[name] = SQLite(path)
137
139
 
138
- return True
139
- else:
140
- return False
140
+ return self._db[name]
141
+
142
+ def _unload_database(self, name: str):
143
+ if name in self._db:
144
+ self._db[name].close()
145
+ del self._db[name]
141
146
 
142
- def _unload_database(self):
143
- if self._db is not None:
144
- self._db.close()
145
- self._db = None
146
147
  return True
147
148
  else:
148
149
  return False
149
150
 
150
- def _execute_stmt(self, query: str, silent: bool,
151
- column_name_mapping: Dict[str, str],
152
- max_rows: Optional[int]) -> Tuple[Optional[List[str]], Optional[List[List]]]:
153
- if self._db is None:
151
+ def _execute_stmt(self, silent: bool, state: MagicState, name: str, query: str) \
152
+ -> Tuple[Optional[List[str]], Optional[List[List]]]:
153
+ if state.db is None:
154
154
  raise AssertionError('load a database first')
155
155
 
156
156
  # execute query and store start and end timestamp
157
157
  st = time.time()
158
158
 
159
159
  try:
160
- columns, rows = self._db.execute(query)
160
+ columns, rows = state.db.execute(query)
161
161
  except EmptyResultError:
162
162
  columns, rows = None, None
163
163
 
164
164
  et = time.time()
165
165
 
166
- # return result if silent
167
- if silent:
168
- return columns, rows
169
-
170
- # print EXPLAIN queries as raw text if using DuckDB
171
- if query.strip().startswith('EXPLAIN') and self._db.plain_explain():
172
- for ekey, evalue in rows:
173
- self.print_data(f'<b>{ekey}</b><br><pre>{evalue}</pre>')
174
-
175
- return None, None
176
-
177
- # print every other query as a table
178
- else:
179
- if columns is not None:
180
- # table header
181
- mapped_columns = (column_name_mapping.get(c, c) for c in columns)
182
- table_header = ''.join(f'<th>{c}</th>' for c in mapped_columns)
183
-
184
- # table data
185
- if max_rows is not None and len(rows) > max_rows:
186
- table_data = f'''
187
- {rows_table(rows[:math.ceil(max_rows / 2)])}
188
- <tr>
189
- <td colspan="{len(columns)}"
190
- style="text-align: center"
191
- title="{row_count(len(rows) - max_rows)} omitted">
192
- ...
193
- </td>
194
- </tr>
195
- {rows_table(rows[-math.floor(max_rows // 2):])}
196
- '''
166
+ # print result if not silent
167
+ if not silent:
168
+ # print EXPLAIN queries as raw text if using DuckDB
169
+ if query.strip().startswith('EXPLAIN') and state.db.plain_explain():
170
+ for ekey, evalue in rows:
171
+ html = f'<b>{ekey}</b><br><pre>{evalue}</pre>'
172
+ break
197
173
  else:
198
- table_data = rows_table(rows)
174
+ html = ''
199
175
 
200
- # send to client
201
- self.print_data(f'''
202
- <table class="duckdb-query-result">
203
- {table_header}
204
- {table_data}
205
- </table>
206
- ''')
176
+ # print every other query as a table
177
+ else:
178
+ if columns is not None:
179
+ # table header
180
+ mapped_columns = (state.column_name_mapping.get(c, c) for c in columns)
181
+ table_header = ''.join(f'<th>{c}</th>' for c in mapped_columns)
182
+
183
+ # table data
184
+ if state.max_rows is not None and len(rows) > state.max_rows:
185
+ table_data = f'''
186
+ {rows_table(rows[:math.ceil(state.max_rows / 2)])}
187
+ <tr>
188
+ <td colspan="{len(columns)}"
189
+ style="text-align: center"
190
+ title="{row_count(len(rows) - state.max_rows)} omitted">
191
+ ...
192
+ </td>
193
+ </tr>
194
+ {rows_table(rows[-math.floor(state.max_rows // 2):])}
195
+ '''
196
+ else:
197
+ table_data = rows_table(rows)
198
+
199
+ # send to client
200
+ html = (f'''
201
+ <table class="duckdb-query-result-table">
202
+ {table_header}
203
+ {table_data}
204
+ </table>
205
+
206
+ {row_count(len(rows))} in {et - st:.3f}s
207
+ ''')
207
208
 
208
- self.print_data(f'{row_count(len(rows))} in {et - st:.3f}s')
209
+ else:
210
+ html = f'statement executed without result in {et - st:.3f}s'
209
211
 
210
- else:
211
- self.print_data(f'statement executed without result in {et - st:.3f}s')
212
+ self.print_data(f'''
213
+ <div class="duckdb-query-result {name}">
214
+ {html}
215
+ </div>
216
+ ''')
212
217
 
213
218
  return columns, rows
214
219
 
215
220
  # magic command related functions
216
- def _create_magic(self, silent: bool, path: str, of: Optional[str], with_tests: Optional[str]):
217
- self._load(silent, path, True, of, with_tests)
221
+ def _create_magic(self, silent: bool, state: MagicState,
222
+ path: str, of: Optional[str], name: Optional[str]):
223
+ self._load(silent, state, path, True, of, name)
224
+
225
+ def _load_magic(self, silent: bool, state: MagicState,
226
+ path: str, name: Optional[str]):
227
+ self._load(silent, state, path, False, None, name)
228
+
229
+ def _load(self, silent: bool, state: MagicState,
230
+ path: str, create: bool, of: Optional[str], name: Optional[str]):
231
+ # use default name if non provided
232
+ if name is None:
233
+ name = 'default'
218
234
 
219
- def _load_magic(self, silent: bool, path: str, with_tests: Optional[str]):
220
- self._load(silent, path, False, None, with_tests)
235
+ if not silent:
236
+ self.print(f'--- connection {name} ---\n')
221
237
 
222
- def _load(self, silent: bool, path: str, create: bool, of: Optional[str], with_tests: Optional[str]):
223
238
  # unload current database if necessary
224
- if self._unload_database():
239
+ if self._unload_database(name):
225
240
  if not silent:
226
241
  self.print('unloaded database\n')
227
242
 
@@ -238,10 +253,10 @@ class DuckDBKernel(Kernel):
238
253
  if create and os.path.exists(path):
239
254
  os.remove(path)
240
255
 
241
- if self._load_database(path):
242
- if not silent:
243
- # self.print(f'loaded database "{path}"\n')
244
- self.print(str(self._db) + '\n')
256
+ state.db = self._load_database(path, name)
257
+ if not silent:
258
+ # self.print(f'loaded database "{path}"\n')
259
+ self.print(str(state.db) + '\n')
245
260
 
246
261
  # copy data from source database
247
262
  if of is not None:
@@ -257,18 +272,18 @@ class DuckDBKernel(Kernel):
257
272
  content = file.read()
258
273
 
259
274
  # You can only execute one statement at a time using SQLite.
260
- if not self._db.multiple_statements_per_query():
275
+ if not state.db.multiple_statements_per_query():
261
276
  statements = re.split(r';\r?\n', content)
262
277
  for statement in statements:
263
278
  try:
264
- self._db.execute(statement)
279
+ state.db.execute(statement)
265
280
  except EmptyResultError:
266
281
  pass
267
282
 
268
283
  # Other DBMS can execute multiple statements at a time.
269
284
  else:
270
285
  try:
271
- self._db.execute(content)
286
+ state.db.execute(content)
272
287
  except EmptyResultError:
273
288
  pass
274
289
 
@@ -282,29 +297,51 @@ class DuckDBKernel(Kernel):
282
297
  of_db.execute('SHOW TABLES')
283
298
  for table, in of_db.fetchall():
284
299
  transfer_df = of_db.query(f'SELECT * FROM {table}').to_df()
285
- self._db.execute(f'CREATE TABLE {table} AS SELECT * FROM transfer_df')
300
+ state.db.execute(f'CREATE TABLE {table} AS SELECT * FROM transfer_df')
286
301
 
287
302
  if not silent:
288
303
  self.print(f'transferred table {table}\n')
289
304
 
290
- # load tests
291
- if with_tests is None:
292
- self._tests = {}
293
- else:
294
- with open(with_tests, 'r', encoding='utf-8') as tests_file:
295
- self._tests = json.load(tests_file)
296
- for test in self._tests.values():
297
- if 'attributes' in test:
298
- rows = {k: [] for k in test['attributes']}
299
- for row in test['equals']:
300
- for k, v in zip(test['attributes'], row):
301
- rows[k].append(v)
305
+ def _copy_magic(self, silent: bool, state: MagicState, source: str, target: str):
306
+ if source not in self._db:
307
+ raise ValueError(f'unknown connection {source}')
308
+
309
+ if not silent:
310
+ self.print(f'--- connection {target} ---\n')
311
+
312
+ # unload current database if necessary
313
+ if self._unload_database(target):
314
+ if not silent:
315
+ self.print('unloaded database\n')
316
+
317
+ # copy connection
318
+ self._db[target] = self._db[source].copy()
319
+ state.db = self._db[target]
320
+
321
+ if not silent:
322
+ self.print(str(state.db) + '\n')
323
+
324
+ def _use_magic(self, silent: bool, state: MagicState, name: str):
325
+ if name not in self._db:
326
+ raise ValueError(f'unknown connection {name}')
302
327
 
303
- test['equals'] = rows
328
+ state.db = self._db[name]
304
329
 
305
- self.print(f'loaded tests from {with_tests}\n')
330
+ def _load_tests_magic(self, silent: bool, state: MagicState, tests: str):
331
+ with open(tests, 'r', encoding='utf-8') as tests_file:
332
+ self._tests = json.load(tests_file)
333
+ for test in self._tests.values():
334
+ if 'attributes' in test:
335
+ rows = {k: [] for k in test['attributes']}
336
+ for row in test['equals']:
337
+ for k, v in zip(test['attributes'], row):
338
+ rows[k].append(v)
306
339
 
307
- def _test_magic(self, silent: bool, result_columns: List[str], result: List[List], name: str):
340
+ test['equals'] = rows
341
+
342
+ self.print(f'loaded tests from {tests}\n')
343
+
344
+ def _test_magic(self, silent: bool, state: MagicState, result_columns: List[str], result: List[List], name: str):
308
345
  # If the query was empty, result_columns and result may be None.
309
346
  if result_columns is None or result is None:
310
347
  self.print_data(wrap_image(False, 'Statement did not return data.'))
@@ -393,31 +430,29 @@ class DuckDBKernel(Kernel):
393
430
  elif above > 0:
394
431
  raise TestError(f'{row_count(above)} unnecessary')
395
432
 
396
- def _all_magic(self, silent: bool):
397
- return {
398
- 'max_rows': None
399
- }
433
+ def _all_magic(self, silent: bool, state: MagicState):
434
+ state.max_rows = None
400
435
 
401
- def _max_rows_magic(self, silent: bool, count: str):
436
+ def _max_rows_magic(self, silent: bool, state: MagicState, count: str):
402
437
  if count.lower() != 'none':
403
438
  DuckDBKernel.DEFAULT_MAX_ROWS = int(count)
404
439
  else:
405
440
  DuckDBKernel.DEFAULT_MAX_ROWS = None
406
441
 
407
- def _query_max_rows_magic(self, silent: bool, count: str):
408
- return {
409
- 'max_rows': int(count) if count.lower() != 'none' else None
410
- }
442
+ state.max_rows = DuckDBKernel.DEFAULT_MAX_ROWS
411
443
 
412
- def _schema_magic(self, silent: bool, td: bool, only: Optional[str]):
413
- if self._db is None:
414
- raise AssertionError('load a database first')
444
+ def _query_max_rows_magic(self, silent: bool, state: MagicState, count: str):
445
+ state.max_rows = int(count) if count.lower() != 'none' else None
415
446
 
447
+ def _schema_magic(self, silent: bool, state: MagicState, td: bool, only: Optional[str]):
416
448
  if silent:
417
449
  return
418
450
 
451
+ if state.db is None:
452
+ raise AssertionError('load a database first')
453
+
419
454
  # analyze tables
420
- tables = self._db.analyze()
455
+ tables = state.db.analyze()
421
456
 
422
457
  # apply filter
423
458
  if only is None:
@@ -459,7 +494,9 @@ class DuckDBKernel(Kernel):
459
494
 
460
495
  self.print_data(svg)
461
496
 
462
- def _store_magic(self, silent: bool, result_columns: List[str], result: List[List], file: str, noheader: bool):
497
+ def _store_magic(self, silent: bool, state: MagicState,
498
+ result_columns: List[str], result: List[List],
499
+ file: str, noheader: bool):
463
500
  _, ext = file.rsplit('.', 1)
464
501
 
465
502
  # csv
@@ -479,37 +516,38 @@ class DuckDBKernel(Kernel):
479
516
  else:
480
517
  raise ValueError(f'extension {ext} not supported')
481
518
 
482
- def _ra_magic(self, silent: bool, code: str, analyze: bool):
483
- if self._db is None:
484
- raise AssertionError('load a database first')
485
-
519
+ def _ra_magic(self, silent: bool, state: MagicState, analyze: bool):
486
520
  if silent:
487
521
  return
488
522
 
489
- if not code.strip():
523
+ if not state.code.strip():
490
524
  return
491
525
 
526
+ if state.db is None:
527
+ raise AssertionError('load a database first')
528
+
492
529
  # analyze tables
493
- tables = self._db.analyze()
530
+ tables = state.db.analyze()
494
531
 
495
532
  # parse ra input
496
- root_node = RAParser.parse_query(code)
533
+ root_node = RAParser.parse_query(state.code)
497
534
 
498
535
  # create and show visualization
499
536
  if analyze:
500
- vd = RATreeDrawer(self._db, root_node, tables)
501
- svg = vd.to_svg(True)
537
+ vd = RATreeDrawer(state.db, root_node, tables)
502
538
 
539
+ svg = vd.to_interactive_svg()
503
540
  self.print_data(svg)
504
541
 
505
- # generate sql
506
- sql = root_node.to_sql_with_renamed_columns(tables)
542
+ state.code = {
543
+ node_id: node.to_sql_with_renamed_columns(tables)
544
+ for node_id, node in vd.nodes.items()
545
+ }
507
546
 
508
- return {
509
- 'generated_code': sql
510
- }
547
+ else:
548
+ state.code = root_node.to_sql_with_renamed_columns(tables)
511
549
 
512
- def _all_ra_magic(self, silent: bool, value: str):
550
+ def _all_ra_magic(self, silent: bool, state: MagicState, value: str):
513
551
  if value.lower() in ('1', 'on', 'true'):
514
552
  self._magics['ra'].default(True)
515
553
  self._magics['dc'].default(False)
@@ -518,31 +556,29 @@ class DuckDBKernel(Kernel):
518
556
  else:
519
557
  self._magics['ra'].default(False)
520
558
 
521
- def _dc_magic(self, silent: bool, code: str):
522
- if self._db is None:
523
- raise AssertionError('load a database first')
524
-
559
+ def _dc_magic(self, silent: bool, state: MagicState):
525
560
  if silent:
526
561
  return
527
562
 
528
- if not code.strip():
563
+ if not state.code.strip():
529
564
  return
530
565
 
566
+ if state.db is None:
567
+ raise AssertionError('load a database first')
568
+
531
569
  # analyze tables
532
- tables = self._db.analyze()
570
+ tables = state.db.analyze()
533
571
 
534
572
  # parse dc input
535
- root_node = DCParser.parse_query(code)
573
+ root_node = DCParser.parse_query(state.code)
536
574
 
537
575
  # generate sql
538
576
  sql, cnm = root_node.to_sql_with_renamed_columns(tables)
539
577
 
540
- return {
541
- 'generated_code': sql,
542
- 'column_name_mapping': cnm
543
- }
578
+ state.code = sql
579
+ state.column_name_mapping.update(cnm)
544
580
 
545
- def _all_dc_magic(self, silent: bool, value: str):
581
+ def _all_dc_magic(self, silent: bool, state: MagicState, value: str):
546
582
  if value.lower() in ('1', 'on', 'true'):
547
583
  self._magics['dc'].default(True)
548
584
  self._magics['ra'].default(False)
@@ -551,41 +587,43 @@ class DuckDBKernel(Kernel):
551
587
  else:
552
588
  self._magics['dc'].default(False)
553
589
 
554
- def _guess_parser_magic(self, silent: bool, value: str):
590
+ def _guess_parser_magic(self, silent: bool, state: MagicState, value: str):
555
591
  if value.lower() in ('1', 'on', 'true'):
556
592
  self._magics['auto_parser'].default(True)
557
593
  self.print('The correct parser is guessed for each subsequently executed cell.\n')
558
594
  else:
559
595
  self._magics['auto_parser'].default(False)
560
596
 
561
- def _auto_parser_magic(self, silent: bool, code: str):
597
+ def _auto_parser_magic(self, silent: bool, state: MagicState):
562
598
  # do not handle statements starting with SQL keywords
563
- first_word = code.strip().split(maxsplit=1)
599
+ first_word = state.code.strip().split(maxsplit=1)
564
600
  if len(first_word) > 0:
565
601
  if first_word[0].upper() in SQL_KEYWORDS:
566
602
  return
567
603
 
568
604
  # try to parse DC
569
605
  try:
570
- return self._dc_magic(silent, code)
606
+ self._dc_magic(silent, state)
571
607
  except ParserError as e:
572
608
  if e.depth > 0:
573
609
  raise e
574
610
 
575
611
  # try to parse RA
576
612
  try:
577
- return self._ra_magic(silent, code, analyze=False)
613
+ self._ra_magic(silent, state, analyze=False)
578
614
  except ParserError as e:
579
615
  if e.depth > 0:
580
616
  raise e
581
617
 
582
- def _plotly_magic(self, silent: bool, cols: List, rows: List[Tuple], type: str, mapping: str, title: str = None):
618
+ def _plotly_magic(self, silent: bool, state: MagicState,
619
+ cols: List, rows: List[Tuple],
620
+ type: str, mapping: str, title: str = None):
583
621
  # split mapping and handle asterisks
584
622
  mapping = [m.strip() for m in mapping.split(',')]
585
623
 
586
624
  for i in range(len(mapping)):
587
625
  if mapping[i] == '*':
588
- mapping = mapping[:i] + cols + mapping[i+1:]
626
+ mapping = mapping[:i] + cols + mapping[i + 1:]
589
627
 
590
628
  # convert all column names to lower case
591
629
  lower_cols = [c.lower() for c in cols]
@@ -654,7 +692,9 @@ class DuckDBKernel(Kernel):
654
692
  # finally print the code
655
693
  self.print_data(html, mime='text/html')
656
694
 
657
- def _plotly_raw_magic(self, silent: bool, cols: List, rows: List[Tuple], title: str = None):
695
+ def _plotly_raw_magic(self, silent: bool, state: MagicState,
696
+ cols: List, rows: List[Tuple],
697
+ title: str = None):
658
698
  if len(cols) != 1 and len(rows) != 1:
659
699
  raise ValueError(f'expected exactly one column and one row')
660
700
 
@@ -669,30 +709,27 @@ class DuckDBKernel(Kernel):
669
709
  **kwargs):
670
710
  try:
671
711
  # get magic command
672
- clean_code, pre_query_callbacks, post_query_callbacks = self._magics(silent, code)
712
+ if len(self._db) > 0:
713
+ init_db = self._db[list(self._db.keys())[0]]
714
+ else:
715
+ init_db = None
673
716
 
674
- # execute magic commands here if it does not depend on query results
675
- execution_args = {
676
- 'max_rows': DuckDBKernel.DEFAULT_MAX_ROWS
677
- }
717
+ magic_state = MagicState(init_db, code, DuckDBKernel.DEFAULT_MAX_ROWS)
718
+ pre_query_callbacks, post_query_callbacks = self._magics(silent, magic_state)
678
719
 
720
+ # execute magic commands here if it does not depend on query results
679
721
  for callback in pre_query_callbacks:
680
- execution_args.update(callback())
722
+ callback()
681
723
 
682
- # overwrite clean_code with generated code
683
- if 'generated_code' in execution_args:
684
- clean_code = execution_args['generated_code']
685
- del execution_args['generated_code']
724
+ # execute statement if needed
725
+ cols, rows = None, None
686
726
 
687
- # set default column name mapping if none provided
688
- if 'column_name_mapping' not in execution_args:
689
- execution_args['column_name_mapping'] = {}
727
+ if not isinstance(magic_state.code, dict):
728
+ magic_state.code = {'default': magic_state.code}
690
729
 
691
- # execute statement if needed
692
- if clean_code.strip():
693
- cols, rows = self._execute_stmt(clean_code, silent, **execution_args)
694
- else:
695
- cols, rows = None, None
730
+ for name, code in magic_state.code.items():
731
+ if code.strip():
732
+ cols, rows = self._execute_stmt(silent, magic_state, name, code)
696
733
 
697
734
  # execute magic command here if it does depend on query results
698
735
  for callback in post_query_callbacks:
@@ -716,5 +753,7 @@ class DuckDBKernel(Kernel):
716
753
  }
717
754
 
718
755
  def do_shutdown(self, restart):
719
- self._unload_database()
756
+ for name in list(self._db.keys()):
757
+ self._unload_database(name)
758
+
720
759
  return super().do_shutdown(restart)