]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-153658: Fix sqlite3 iterdump() for table names containing a single quote...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 14 Jul 2026 07:49:02 +0000 (09:49 +0200)
committerGitHub <noreply@github.com>
Tue, 14 Jul 2026 07:49:02 +0000 (07:49 +0000)
(cherry picked from commit 2fc3c4d47dff722758c76686f657d5ecdd922551)

Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com>
Lib/sqlite3/dump.py
Lib/test/test_sqlite3/test_dump.py
Misc/NEWS.d/next/Library/2026-07-13-10-27-08.gh-issue-153658.A6o4n5.rst [new file with mode: 0644]

index 57e6a3b4f1e6eba0d399d09dbe0df157f36d93b9..95e14a6655ef17282e6e7fee8c7e36221d05e621 100644 (file)
@@ -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:
index e18a207e9f649494c337493ea75e862f5669448e..7841b610cd222a7f337c6665d2080633b1f600e7 100644 (file)
@@ -56,6 +56,24 @@ class DumpTests(MemoryDatabaseMixin, unittest.TestCase):
         [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 (file)
index 0000000..06385bb
--- /dev/null
@@ -0,0 +1,2 @@
+Fix :meth:`sqlite3.Connection.iterdump` raising :exc:`sqlite3.OperationalError`
+when a table name contains a single quote. Patch by tonghuaroot.