diff --git a/Lib/sqlite3/dump.py b/Lib/sqlite3/dump.py index 95e14a6655ef17..5cc0d9a1a9d124 100644 --- a/Lib/sqlite3/dump.py +++ b/Lib/sqlite3/dump.py @@ -77,6 +77,8 @@ def _iterdump(connection, *, filter=None): _quote_value(table_name), _quote_value(sql), )) + # Rows live in the shadow tables, dumped separately. + continue else: yield('{0};'.format(sql)) diff --git a/Lib/test/test_sqlite3/test_dump.py b/Lib/test/test_sqlite3/test_dump.py index 7841b610cd222a..4cde5f891ad316 100644 --- a/Lib/test/test_sqlite3/test_dump.py +++ b/Lib/test/test_sqlite3/test_dump.py @@ -1,6 +1,10 @@ # Author: Paul Kippes +import sqlite3 import unittest +from contextlib import closing + +from test.support.os_helper import TESTFN, unlink from .util import memory_database from .util import MemoryDatabaseMixin @@ -246,6 +250,34 @@ def test_dump_virtual_tables(self): actual = list(self.cx.iterdump()) self.assertEqual(expected, actual) + @requires_virtual_table("fts4") + def test_dump_virtual_table_data_roundtrip(self): + # gh-153729: a populated virtual table must round-trip through iterdump(). + self.addCleanup(unlink, TESTFN) + with closing(sqlite3.connect(TESTFN)) as src: + src.execute("CREATE VIRTUAL TABLE test USING fts4(example)") + src.execute("INSERT INTO test(example) VALUES('hello world')") + src.execute("INSERT INTO test(example) VALUES('second row')") + src.commit() + script = "".join(src.iterdump()) + + # The virtual table's own rows are not dumped as INSERT statements + # (the data is preserved via the shadow tables instead). + self.assertNotIn('INSERT INTO "test"', script) + + restored_path = f"{TESTFN}.restored" + self.addCleanup(unlink, restored_path) + with closing(sqlite3.connect(restored_path)) as dst: + # Defensive mode blocks writable_schema writes to sqlite_master. + dst.setconfig(sqlite3.SQLITE_DBCONFIG_DEFENSIVE, False) + dst.setconfig(sqlite3.SQLITE_DBCONFIG_WRITABLE_SCHEMA, True) + dst.executescript(script) + with closing(sqlite3.connect(restored_path)) as restored: + rows = restored.execute( + "SELECT example FROM test ORDER BY docid" + ).fetchall() + self.assertEqual(rows, [("hello world",), ("second row",)]) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-07-14-10-00-00.gh-issue-153729.p3yD9r.rst b/Misc/NEWS.d/next/Library/2026-07-14-10-00-00.gh-issue-153729.p3yD9r.rst new file mode 100644 index 00000000000000..88004303345fca --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-14-10-00-00.gh-issue-153729.p3yD9r.rst @@ -0,0 +1,3 @@ +:meth:`sqlite3.Connection.iterdump` no longer emits ``INSERT`` statements for +a virtual table's own rows, which previously made restoring the dump raise +:exc:`sqlite3.OperationalError`. Patch by tonghuaroot.