diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 2f8335a895c70b..e5bccf17eb63e2 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -134,13 +134,6 @@ shelve (Contributed by Andrea Oliveri in :gh:`134004`.) -sqlite3 -------- - -* Support SQL keyword completion in the :mod:`sqlite3` command-line interface. - (Contributed by Long Tan in :gh:`133393`.) - - ssl --- diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 9e74b49ee828bc..c2fa23c46cf990 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -12,8 +12,6 @@ from textwrap import dedent from _colorize import get_theme, theme_no_color -from ._completer import completer - def execute(c, sql, suppress_errors=True, theme=theme_no_color): """Helper that wraps execution of SQL code. @@ -138,9 +136,12 @@ def main(*args): execute(con, args.sql, suppress_errors=False, theme=theme) else: # No SQL provided; start the REPL. - with completer(): - console = SqliteInteractiveConsole(con, use_color=True) - console.interact(banner, exitmsg="") + console = SqliteInteractiveConsole(con, use_color=True) + try: + import readline # noqa: F401 + except ImportError: + pass + console.interact(banner, exitmsg="") finally: con.close() diff --git a/Lib/sqlite3/_completer.py b/Lib/sqlite3/_completer.py deleted file mode 100644 index f21ef69cad6439..00000000000000 --- a/Lib/sqlite3/_completer.py +++ /dev/null @@ -1,42 +0,0 @@ -from contextlib import contextmanager - -try: - from _sqlite3 import SQLITE_KEYWORDS -except ImportError: - SQLITE_KEYWORDS = () - -_completion_matches = [] - - -def _complete(text, state): - global _completion_matches - - if state == 0: - text_upper = text.upper() - _completion_matches = [c for c in SQLITE_KEYWORDS if c.startswith(text_upper)] - try: - return _completion_matches[state] + " " - except IndexError: - return None - - -@contextmanager -def completer(): - try: - import readline - except ImportError: - yield - return - - old_completer = readline.get_completer() - try: - readline.set_completer(_complete) - if readline.backend == "editline": - # libedit uses "^I" instead of "tab" - command_string = "bind ^I rl_complete" - else: - command_string = "tab: complete" - readline.parse_and_bind(command_string) - yield - finally: - readline.set_completer(old_completer) diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py index 7f0b0f3650535a..37e0f74f688659 100644 --- a/Lib/test/test_sqlite3/test_cli.py +++ b/Lib/test/test_sqlite3/test_cli.py @@ -1,19 +1,14 @@ """sqlite3 CLI tests.""" import sqlite3 -import sys -import textwrap import unittest from sqlite3.__main__ import main as cli -from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink -from test.support.pty_helper import run_pty from test.support import ( captured_stdout, captured_stderr, captured_stdin, force_not_colorized_test_class, - requires_subprocess, ) @@ -205,98 +200,5 @@ def test_color(self): self.assertIn('\x1b[1;35mOperationalError (SQLITE_ERROR)\x1b[0m: ' '\x1b[35mnear "sel": syntax error\x1b[0m', err) - -@requires_subprocess() -@force_not_colorized_test_class -class Completion(unittest.TestCase): - PS1 = "sqlite> " - - @classmethod - def setUpClass(cls): - _sqlite3 = import_module("_sqlite3") - if not hasattr(_sqlite3, "SQLITE_KEYWORDS"): - raise unittest.SkipTest("unable to determine SQLite keywords") - - readline = import_module("readline") - if readline.backend == "editline": - raise unittest.SkipTest("libedit readline is not supported") - - def write_input(self, input_, env=None): - script = textwrap.dedent(""" - import readline - from sqlite3.__main__ import main - - readline.parse_and_bind("set colored-completion-prefix off") - main() - """) - return run_pty(script, input_, env) - - def test_complete_sql_keywords(self): - # List candidates starting with 'S', there should be multiple matches. - input_ = b"S\t\tEL\t 1;\n.quit\n" - output = self.write_input(input_) - self.assertIn(b"SELECT", output) - self.assertIn(b"SET", output) - self.assertIn(b"SAVEPOINT", output) - self.assertIn(b"(1,)", output) - - # Keywords are completed in upper case for even lower case user input. - input_ = b"sel\t\t 1;\n.quit\n" - output = self.write_input(input_) - self.assertIn(b"SELECT", output) - self.assertIn(b"(1,)", output) - - @unittest.skipIf(sys.platform.startswith("freebsd"), - "Two actual tabs are inserted when there are no matching" - " completions in the pseudo-terminal opened by run_pty()" - " on FreeBSD") - def test_complete_no_match(self): - input_ = b"xyzzy\t\t\b\b\b\b\b\b\b.quit\n" - # Set NO_COLOR to disable coloring for self.PS1. - output = self.write_input(input_, env={"NO_COLOR": "1"}) - lines = output.decode().splitlines() - indices = ( - i for i, line in enumerate(lines, 1) - if line.startswith(f"{self.PS1}xyzzy") - ) - line_num = next(indices, -1) - self.assertNotEqual(line_num, -1) - # Completions occupy lines, assert no extra lines when there is nothing - # to complete. - self.assertEqual(line_num, len(lines)) - - def test_complete_no_input(self): - from _sqlite3 import SQLITE_KEYWORDS - - script = textwrap.dedent(""" - import readline - from sqlite3.__main__ import main - - # Configure readline to ...: - # - hide control sequences surrounding each candidate - # - hide "Display all xxx possibilities? (y or n)" - # - hide "--More--" - # - show candidates one per line - readline.parse_and_bind("set colored-completion-prefix off") - readline.parse_and_bind("set colored-stats off") - readline.parse_and_bind("set completion-query-items 0") - readline.parse_and_bind("set page-completions off") - readline.parse_and_bind("set completion-display-width 0") - - main() - """) - input_ = b"\t\t.quit\n" - output = run_pty(script, input_, env={"NO_COLOR": "1"}) - lines = output.decode().splitlines() - indices = [ - i for i, line in enumerate(lines) - if line.startswith(self.PS1) - ] - self.assertEqual(len(indices), 2) - start, end = indices - candidates = [l.strip() for l in lines[start+1:end]] - self.assertEqual(candidates, sorted(SQLITE_KEYWORDS)) - - if __name__ == "__main__": unittest.main() diff --git a/Misc/ACKS b/Misc/ACKS index 739af8d9e11a10..2435943f1bb2bd 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1868,7 +1868,6 @@ Neil Tallim Geoff Talvola Anish Tambe Musashi Tamura -Long Tan William Tanksley Christian Tanzer Steven Taschuk diff --git a/Misc/NEWS.d/next/Library/2025-05-05-03-14-08.gh-issue-133390.AuTggn.rst b/Misc/NEWS.d/next/Library/2025-05-05-03-14-08.gh-issue-133390.AuTggn.rst deleted file mode 100644 index 38d5c311b1d437..00000000000000 --- a/Misc/NEWS.d/next/Library/2025-05-05-03-14-08.gh-issue-133390.AuTggn.rst +++ /dev/null @@ -1 +0,0 @@ -Support keyword completion in the :mod:`sqlite3` command-line interface. diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 5464fd1227ad20..909ddd1f990e19 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -32,7 +32,6 @@ #include "microprotocols.h" #include "row.h" #include "blob.h" -#include "util.h" #if SQLITE_VERSION_NUMBER < 3015002 #error "SQLite 3.15.2 or higher required" @@ -405,40 +404,6 @@ pysqlite_error_name(int rc) return NULL; } -static int -add_keyword_tuple(PyObject *module) -{ -#if SQLITE_VERSION_NUMBER >= 3024000 - int count = sqlite3_keyword_count(); - PyObject *keywords = PyTuple_New(count); - if (keywords == NULL) { - return -1; - } - for (int i = 0; i < count; i++) { - const char *keyword; - int size; - int result = sqlite3_keyword_name(i, &keyword, &size); - if (result != SQLITE_OK) { - pysqlite_state *state = pysqlite_get_state(module); - set_error_from_code(state, result); - goto error; - } - PyObject *kwd = PyUnicode_FromStringAndSize(keyword, size); - if (!kwd) { - goto error; - } - PyTuple_SET_ITEM(keywords, i, kwd); - } - return PyModule_Add(module, "SQLITE_KEYWORDS", keywords); - -error: - Py_DECREF(keywords); - return -1; -#else - return 0; -#endif -} - static int add_integer_constants(PyObject *module) { #define ADD_INT(ival) \ @@ -737,10 +702,6 @@ module_exec(PyObject *module) goto error; } - if (add_keyword_tuple(module) < 0) { - goto error; - } - if (PyModule_AddStringConstant(module, "sqlite_version", sqlite3_libversion())) { goto error; }