Skip to content

BUG: Save and restore user SQLite adapters in to_sql() (GH#64337)#66297

Open
q121212 wants to merge 3 commits into
pandas-dev:mainfrom
q121212:fix/64337-sqlite-adapters-full
Open

BUG: Save and restore user SQLite adapters in to_sql() (GH#64337)#66297
q121212 wants to merge 3 commits into
pandas-dev:mainfrom
q121212:fix/64337-sqlite-adapters-full

Conversation

@q121212

@q121212 q121212 commented Jul 11, 2026

Copy link
Copy Markdown

Closes #64337

This is a more comprehensive fix that preserves user-registered adapters and
converters even when they were registered before the first to_sql() call.

How it works

  1. In SQLiteTable.__init__(), save the currently registered adapters
    (for time, date, datetime) and converters (for "date", "timestamp")
    that pandas will override.

  2. Register pandas' adapters/converters as before (needed for the insert
    operation to work on Python 3.12+).

  3. After _execute_insert() or _execute_insert_multi() returns, restore
    the user's original adapters/converters via a finally block.

Changes

File: pandas/io/sql.py

  • SQLiteTable.__init__() — save existing adapters/converters before
    registering pandas' own
  • New _restore_date_adapters() method — restores saved handlers
  • _execute_insert() — wraps super() call with try/finally that
    calls _restore_date_adapters()
  • _execute_insert_multi() — same pattern

Added tests

File: pandas/tests/io/test_sql.py

  • test_to_sql_restores_user_adapters_after_insert — custom adapter
    registered BEFORE to_sql() is restored after the operation
  • test_to_sql_restores_user_converters_after_insert — same for converters
  • test_to_sql_restores_no_crash_when_no_prior_registration — edge case:
    no prior user registration, should not crash on restore

Comparison with simple version

Scenario Simple (once) Full (save/restore)
User registers adapter AFTER first to_sql() ✅ preserved ✅ preserved
User registers adapter BEFORE first to_sql() ❌ overwritten ✅ preserved
Multiple to_sql() calls, adapter preserved
Code complexity +5 lines +40 lines
Risk Zero Low

Checklist

  • Bug fix
  • Tests pass
  • No new dependencies
  • Backward compatible

q121212 added 3 commits July 12, 2026 02:06
Every call to DataFrame.to_sql() with a SQLite connection created a new
SQLiteTable instance whose __init__ unconditionally called
sqlite3.register_adapter() and sqlite3.register_converter(). These are
global mutations, overwriting any custom adapters/converters the user
may have registered.

Fix: use a module-level _SQLITE_ADAPTERS_REGISTERED flag so adapters
and converters are registered only once per process. After the first
to_sql() call, subsequent calls are no-ops.

This preserves pandas' required adapters (needed because Python 3.12+
removed default datetime adapters) while not stomping on user handlers.
Every to_sql() call unconditionally registered pandas' own adapters
and converters via sqlite3.register_adapter()/register_converter(),
overwriting any custom handlers the user may have set up.

Fix: save the currently registered adapters (for time, date, datetime)
and converters (for 'date', 'timestamp') before registering pandas'
own, then restore them after the insert operation completes.

This handles both cases:
- User registered adapters BEFORE the first to_sql() — restored
- User registered adapters AFTER a to_sql() — never overwritten
  on subsequent calls

Restore happens in a finally block in _execute_insert() and
_execute_insert_multi() to ensure cleanup even on error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: Dataframe.to_sql() overrides registered date and time converters in SQLite

1 participant