Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-133390: Support SQL keyword completion for sqlite3 CLI#133393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
erlend-aasland merged 54 commits intopython:mainfromtanloong:sqlite3-cli-completion
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from39 commits
Commits
Show all changes
54 commits
Select commitHold shift + click to select a range
1b96be3
Support basic completion for sqlite3 command-line interface
tanloongMay 4, 2025
5e50871
Add news entry
tanloongMay 4, 2025
c1941cb
Move completion code to separate module
tanloongMay 4, 2025
47daca5
Update Lib/sqlite3/_completer.py
tanloongMay 4, 2025
c54c2f6
Update Doc/whatsnew/3.14.rst
tanloongMay 4, 2025
8fff491
Add test
tanloongMay 5, 2025
a766805
Move keyword list to module level
tanloongMay 5, 2025
da55014
Remove whatsnew entry from 3.14
tanloongMay 5, 2025
ca587e0
Avoid regeneration of candidates. Store them when state is 0 and returns
tanloongMay 7, 2025
311b4f3
Add whatsnew entry to 3.15
tanloongMay 7, 2025
70f46e9
Address Bénédikt's review
tanloongMay 10, 2025
9d03730
Remove color handling of output; If CI fails might need to add back
tanloongMay 10, 2025
bfcff38
Fix `run_pty()` doesn't return and test hangs
tanloongMay 10, 2025
805d997
Revert "Remove color handling of output; If CI fails might need to ad…
tanloongMay 10, 2025
276b4a7
Turn off colored-completion-prefix for readline
tanloongMay 10, 2025
09eeac8
No need to pass "NO_COLOR" to `run_pty()`
tanloongMay 10, 2025
fc57d71
Flip name
tanloongMay 10, 2025
c508069
Triggering completion on Ubuntu requires 2 tabs
tanloongMay 10, 2025
231b9e7
Move KEYWORDS to C
tanloongMay 10, 2025
121b069
Improve style of C code
tanloongMay 10, 2025
90a86cf
Improve tests
tanloongMay 11, 2025
5170733
Address Bénédikt's review
tanloongMay 16, 2025
b40982a
Revert "Improve style of C code"
tanloongMay 16, 2025
226ea9f
Revert "Move KEYWORDS to C"
tanloongMay 16, 2025
4eebbd9
Read keyword names dynamically
encukouMay 16, 2025
3f9b2c1
Check candidates against KEYWORDS
tanloongMay 16, 2025
0410fa2
Use slice to get candidates
tanloongMay 16, 2025
bd0b9ce
Address Bénédikt's review
tanloongMay 16, 2025
35a17e7
Make candidates tuple
tanloongMay 16, 2025
3dd16b3
Revert "Revert "Move KEYWORDS to C""
tanloongMay 16, 2025
f3ea951
Revert "Revert "Improve style of C code""
tanloongMay 16, 2025
a493ad3
Merge pull request #2 from encukou/sqlite3-cli-completion
tanloongMay 16, 2025
34cfc78
Fix 'KEYWORDS' not found
tanloongMay 16, 2025
477b48b
Sort keywords before checking the equality
tanloongMay 16, 2025
68bb4f3
Fix comparing between tuple and list
tanloongMay 16, 2025
4c3b122
Fix comparing between tuple and list
tanloongMay 16, 2025
4f1221e
Rename 'test_completion_order' to 'test_completion_for_nothing'
tanloongMay 16, 2025
3865131
Don't decrease reference for `PyModule_Add()` and `PyTuple_SetItem()`
tanloongMay 25, 2025
8d4f659
Merge branch 'main' into sqlite3-cli-completion
encukouMay 29, 2025
ccd98a5
Add @force_not_colorized_test_class
tanloongMay 31, 2025
d681425
Merge branch 'main' into sqlite3-cli-completion
encukouJun 5, 2025
ffd0f02
Add two '\b\b'; Skip tests on FreeBSD
tanloongJun 5, 2025
6188a6d
Amend skipping reason
tanloongJun 5, 2025
370dd8b
Remove comment 'set the keyword tuple'
tanloongJun 5, 2025
16b1674
Disable keyword completion for SQLite<3.24.0
tanloongJun 5, 2025
ea108ba
Don't disable the whole completion in case there will be more completion
tanloongJun 5, 2025
13b527e
Use compile-time check
tanloongJun 5, 2025
fafd1bb
Correct #if usage
tanloongJun 5, 2025
140818c
Wrap add_keyword_tuple() definition and its call in #if/#endif
tanloongJun 5, 2025
fd6c89e
Suggestions to python/cpython#133393
erlend-aaslandJun 6, 2025
588fb6a
Merge pull request #3 from erlend-aasland/suggestion
tanloongJun 6, 2025
5623f16
Merge branch 'main' into sqlite3-cli-completion
erlend-aaslandJun 6, 2025
88c8d59
Update Doc/whatsnew/3.15.rst
erlend-aaslandJun 6, 2025
b3a2b88
Merge branch 'main' into sqlite3-cli-completion
erlend-aaslandJun 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletionsDoc/whatsnew/3.15.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,14 @@ difflib
class, and migrated the output to the HTML5 standard.
(Contributed by Jiahao Li in :gh:`134580`.)


sqlite3
-------

* Support keyword completion in the :mod:`sqlite3` command-line interface.
(Contributed by Long Tan in :gh:`133393`.)


ssl
---

Expand Down
11 changes: 5 additions & 6 deletionsLib/sqlite3/__main__.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@
from textwrap import dedent
from _colorize import get_theme, theme_no_color

from ._completer import enable_completer


def execute(c, sql, suppress_errors=True, theme=theme_no_color):
"""Helper that wraps execution of SQL code.
Expand DownExpand Up@@ -136,12 +138,9 @@ def main(*args):
execute(con, args.sql, suppress_errors=False, theme=theme)
else:
# No SQL provided; start the REPL.
console = SqliteInteractiveConsole(con, use_color=True)
try:
import readline # noqa: F401
except ImportError:
pass
console.interact(banner, exitmsg="")
with enable_completer():
console = SqliteInteractiveConsole(con, use_color=True)
console.interact(banner, exitmsg="")
finally:
con.close()

Expand Down
37 changes: 37 additions & 0 deletionsLib/sqlite3/_completer.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
from _sqlite3 import SQLITE_KEYWORDS
from contextlib import contextmanager

_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 enable_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)
76 changes: 76 additions & 0 deletionsLib/test/test_sqlite3/test_cli.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
"""sqlite3 CLI tests."""
import sqlite3
import textwrap
import unittest

from _sqlite3 import SQLITE_KEYWORDS
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,
)


Expand DownExpand Up@@ -200,5 +205,76 @@ def test_color(self):
self.assertIn('\x1b[1;35mOperationalError (SQLITE_ERROR)\x1b[0m: '
'\x1b[35mnear "sel": syntax error\x1b[0m', err)


@requires_subprocess()
class CompletionTest(unittest.TestCase):
PS1 = "sqlite> "

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is no longer freely customizable by users via sys.ps1 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Can you explain your remark. Do you want changes?


@classmethod
def setUpClass(cls):
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
readline.parse_and_bind("set colored-completion-prefix off")
from sqlite3.__main__ import main; main()
""")
return run_pty(script, input, env)

def test_keyword_completion(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)

def test_nothing_to_complete(self):
input = b"xyzzy\t\t\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"})
output_lines = output.decode().splitlines()
line_num = next((i for i, line in enumerate(output_lines, 1)
if line.startswith(f"{self.PS1}xyzzy")), -1)
self.assertNotEqual(line_num, -1)
# completions occupy lines, assert no extra lines when there is nothing
# to complete
self.assertEqual(line_num, len(output_lines))

def test_completion_for_nothing(self):
script = textwrap.dedent("""
import readline
readline.parse_and_bind("set colored-completion-prefix off")
# hide control sequences surrounding each candidate
readline.parse_and_bind("set colored-stats off")
# hide "Display all xxx possibilities? (y or n)"
readline.parse_and_bind("set completion-query-items 0")
# hide "--More--"
readline.parse_and_bind("set page-completions off")
# show candidates one per line
readline.parse_and_bind("set completion-display-width 0")
from sqlite3.__main__ import main; main()
""")
input = b"\t\t.quit\n"
output = run_pty(script, input, env={"NO_COLOR": "1"})
output_lines = output.decode().splitlines()
indices = [i for i, line in enumerate(output_lines)
if line.startswith(self.PS1)]
self.assertEqual(len(indices), 2)
start, end = indices[0] + 1, indices[1]
candidates = list(map(str.strip, output_lines[start:end]))
self.assertEqual(candidates, sorted(SQLITE_KEYWORDS))


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletionsMisc/ACKS
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1867,6 +1867,7 @@ Neil Tallim
Geoff Talvola
Anish Tambe
Musashi Tamura
Long Tan
William Tanksley
Christian Tanzer
Steven Taschuk
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
Support keyword completion in the :mod:`sqlite3` command-line interface.
38 changes: 38 additions & 0 deletionsModules/_sqlite/module.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@
#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"
Expand DownExpand Up@@ -404,6 +405,38 @@ pysqlite_error_name(int rc)
return NULL;
}

static int
add_keyword_tuple(PyObject *module)
{
int count = sqlite3_keyword_count();
PyObject *keywords = PyTuple_New(count);
if (keywords == NULL) {
goto error;
}
for (int i = 0; i < count; i++) {
const char *keyword;
int size;
int result = sqlite3_keyword_name(i, &keyword, &size);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

For the record:
Wecould justassert that the result isSQLITE_OK. This is just an out-of-bounds check, and we know that we are within bounds here. OTOH, we don't know how the SQLite internals may change, so let's keep it like it is.

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;
}
if (PyTuple_SetItem(keywords, i, kwd) < 0) {
goto error;
}
}
return PyModule_Add(module, "SQLITE_KEYWORDS", keywords);

error:
Py_XDECREF(keywords);
return -1;
}

static int
add_integer_constants(PyObject *module) {
#define ADD_INT(ival) \
Expand DownExpand Up@@ -702,6 +735,11 @@ module_exec(PyObject *module)
goto error;
}

/* Set the keyword tuple */
if (add_keyword_tuple(module) < 0) {
goto error;
}

if (PyModule_AddStringConstant(module, "sqlite_version", sqlite3_libversion())) {
goto error;
}
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp