From 2b64759e2a81f5bcce6b0289cdf0cae3375087ed Mon Sep 17 00:00:00 2001 From: lkk7 Date: Wed, 15 Jul 2026 16:31:44 +0200 Subject: [PATCH] gh-153768: Fix sqlite3 CLI completion for database names with quotes --- Lib/sqlite3/_completer.py | 14 ++++++++--- Lib/test/test_sqlite3/test_cli.py | 24 +++++++++++++++++++ ...-07-15-16-29-05.gh-issue-153768.Q-M37F.rst | 1 + 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-15-16-29-05.gh-issue-153768.Q-M37F.rst diff --git a/Lib/sqlite3/_completer.py b/Lib/sqlite3/_completer.py index ba580f968bf92d..bbd8f4a3798c41 100644 --- a/Lib/sqlite3/_completer.py +++ b/Lib/sqlite3/_completer.py @@ -11,6 +11,14 @@ _completion_matches = [] +def _quote_identifier(value): + return '"' + value.replace('"', '""') + '"' + + +def _quote_string_literal(value): + return "'" + value.replace("'", "''") + "'" + + def _complete(con, text, state): global _completion_matches @@ -32,7 +40,7 @@ def _complete(con, text, state): # escape '_' which can appear in attached database names select_clauses = ( f"""\ - SELECT name || ' ' FROM \"{schema}\".sqlite_master + SELECT name || ' ' FROM {_quote_identifier(schema)}.sqlite_master WHERE name LIKE REPLACE(:text, '_', '^_') || '%' ESCAPE '^'""" for schema in schemata ) @@ -46,8 +54,8 @@ def _complete(con, text, state): try: select_clauses = ( f"""\ - SELECT pti.name || ' ' FROM "{schema}".sqlite_master AS sm - JOIN pragma_table_xinfo(sm.name,'{schema}') AS pti + SELECT pti.name || ' ' FROM {_quote_identifier(schema)}.sqlite_master AS sm + JOIN pragma_table_xinfo(sm.name,{_quote_string_literal(schema)}) AS pti WHERE sm.type='table' AND pti.name LIKE REPLACE(:text, '_', '^_') || '%' ESCAPE '^'""" for schema in schemata diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py index 1fc0236780fa8b..d31aaa800d1d3c 100644 --- a/Lib/test/test_sqlite3/test_cli.py +++ b/Lib/test/test_sqlite3/test_cli.py @@ -209,6 +209,30 @@ def test_color(self): '\x1b[35mnear "sel": syntax error\x1b[0m', err) +class Completer(unittest.TestCase): + + def test_schema_name_with_double_quote(self): + from sqlite3._completer import _complete + + con = sqlite3.connect(":memory:") + self.addCleanup(con.close) + con.execute("ATTACH ? AS ?", (":memory:", 'double"quote')) + con.execute('CREATE TABLE [double"quote].table_match(value)') + self.assertEqual(_complete(con, "table_", 0), "table_match ") + + @unittest.skipIf(sqlite3.sqlite_version_info < (3, 16, 0), + "PRAGMA table-valued function is not available until " + "SQLite 3.16.0") + def test_schema_name_with_single_quote(self): + from sqlite3._completer import _complete + + con = sqlite3.connect(":memory:") + self.addCleanup(con.close) + con.execute("ATTACH ? AS ?", (":memory:", "single'quote")) + con.execute("CREATE TABLE [single'quote].other(column_match)") + self.assertEqual(_complete(con, "column_", 0), "column_match ") + + @requires_subprocess() @force_not_colorized_test_class class Completion(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2026-07-15-16-29-05.gh-issue-153768.Q-M37F.rst b/Misc/NEWS.d/next/Library/2026-07-15-16-29-05.gh-issue-153768.Q-M37F.rst new file mode 100644 index 00000000000000..c4b35ec6d252a7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-15-16-29-05.gh-issue-153768.Q-M37F.rst @@ -0,0 +1 @@ +Fix sqlite3 CLI completion for attached database names containing quotes.