https://github.com/python/cpython/commit/2fc3c4d47dff722758c76686f657d5ecdd922551
commit: 2fc3c4d47dff722758c76686f657d5ecdd922551
branch: main
author: tonghuaroot (童话) <[email protected]>
committer: erlend-aasland <[email protected]>
date: 2026-07-14T09:16:43+02:00
summary:
gh-153658: Fix sqlite3 iterdump() for table names containing a single quote
(#153659)
files:
A Misc/NEWS.d/next/Library/2026-07-13-10-27-08.gh-issue-153658.A6o4n5.rst
M Lib/sqlite3/dump.py
M Lib/test/test_sqlite3/test_dump.py
diff --git a/Lib/sqlite3/dump.py b/Lib/sqlite3/dump.py
index 57e6a3b4f1e6eba..95e14a6655ef172 100644
--- a/Lib/sqlite3/dump.py
+++ b/Lib/sqlite3/dump.py
@@ -11,8 +11,12 @@ def _quote_name(name):
return '"{0}"'.format(name.replace('"', '""'))
+def _escape_single_quotes(value):
+ return value.replace("'", "''")
+
+
def _quote_value(value):
- return "'{0}'".format(value.replace("'", "''"))
+ return "'{0}'".format(_escape_single_quotes(value))
def _iterdump(connection, *, filter=None):
@@ -80,11 +84,12 @@ def _iterdump(connection, *, filter=None):
table_name_ident = _quote_name(table_name)
res = cu.execute(f'PRAGMA table_info({table_name_ident})')
column_names = [str(table_info[1]) for table_info in res.fetchall()]
- q = "SELECT 'INSERT INTO {0} VALUES('{1}')' FROM {0};".format(
- table_name_ident,
+ q = "SELECT 'INSERT INTO {0} VALUES('{1}')' FROM {2};".format(
+ _escape_single_quotes(table_name_ident),
"','".join(
"||quote({0})||".format(_quote_name(col)) for col in
column_names
- )
+ ),
+ table_name_ident,
)
query_res = cu.execute(q)
for row in query_res:
diff --git a/Lib/test/test_sqlite3/test_dump.py
b/Lib/test/test_sqlite3/test_dump.py
index e18a207e9f64949..7841b610cd222a7 100644
--- a/Lib/test/test_sqlite3/test_dump.py
+++ b/Lib/test/test_sqlite3/test_dump.py
@@ -56,6 +56,24 @@ def test_table_dump(self):
[self.assertEqual(expected_sqls[i], actual_sqls[i])
for i in range(len(expected_sqls))]
+ def test_dump_single_quote_in_identifier(self):
+ # A single quote in a table or column name must not break the dump.
+ self.cu.execute("""CREATE TABLE "a'b" ("c'd" text);""")
+ self.cu.execute("""INSERT INTO "a'b" VALUES('x''y');""")
+ expected = [
+ "BEGIN TRANSACTION;",
+ """CREATE TABLE "a'b" ("c'd" text);""",
+ """INSERT INTO "a'b" VALUES('x''y');""",
+ "COMMIT;",
+ ]
+ actual = list(self.cx.iterdump())
+ self.assertEqual(expected, actual)
+ # The dump restores into a fresh database.
+ with memory_database() as cx2:
+ cx2.executescript("".join(actual))
+ row = cx2.execute("""SELECT "c'd" FROM "a'b";""").fetchone()
+ self.assertEqual(row[0], "x'y")
+
def test_table_dump_filter(self):
all_table_sqls = [
"""CREATE TABLE "some_table_2" ("id_1" INTEGER);""",
diff --git
a/Misc/NEWS.d/next/Library/2026-07-13-10-27-08.gh-issue-153658.A6o4n5.rst
b/Misc/NEWS.d/next/Library/2026-07-13-10-27-08.gh-issue-153658.A6o4n5.rst
new file mode 100644
index 000000000000000..06385bb7754b127
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-13-10-27-08.gh-issue-153658.A6o4n5.rst
@@ -0,0 +1,2 @@
+Fix :meth:`sqlite3.Connection.iterdump` raising :exc:`sqlite3.OperationalError`
+when a table name contains a single quote. Patch by tonghuaroot.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]