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

Commitd49b257

Browse files
tanloongtaegyunkim
authored andcommitted
pythongh-133390: Support SQL keyword completion for sqlite3 CLI (python#133393)
1 parent3017c6d commitd49b257

File tree

7 files changed

+193
-6
lines changed

7 files changed

+193
-6
lines changed

‎Doc/whatsnew/3.15.rst‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ shelve
134134
(Contributed by Andrea Oliveri in:gh:`134004`.)
135135

136136

137+
sqlite3
138+
-------
139+
140+
* Support SQL keyword completion in the:mod:`sqlite3` command-line interface.
141+
(Contributed by Long Tan in:gh:`133393`.)
142+
143+
137144
ssl
138145
---
139146

‎Lib/sqlite3/__main__.py‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
fromtextwrapimportdedent
1313
from_colorizeimportget_theme,theme_no_color
1414

15+
from ._completerimportcompleter
16+
1517

1618
defexecute(c,sql,suppress_errors=True,theme=theme_no_color):
1719
"""Helper that wraps execution of SQL code.
@@ -136,12 +138,9 @@ def main(*args):
136138
execute(con,args.sql,suppress_errors=False,theme=theme)
137139
else:
138140
# No SQL provided; start the REPL.
139-
console=SqliteInteractiveConsole(con,use_color=True)
140-
try:
141-
importreadline# noqa: F401
142-
exceptImportError:
143-
pass
144-
console.interact(banner,exitmsg="")
141+
withcompleter():
142+
console=SqliteInteractiveConsole(con,use_color=True)
143+
console.interact(banner,exitmsg="")
145144
finally:
146145
con.close()
147146

‎Lib/sqlite3/_completer.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
fromcontextlibimportcontextmanager
2+
3+
try:
4+
from_sqlite3importSQLITE_KEYWORDS
5+
exceptImportError:
6+
SQLITE_KEYWORDS= ()
7+
8+
_completion_matches= []
9+
10+
11+
def_complete(text,state):
12+
global_completion_matches
13+
14+
ifstate==0:
15+
text_upper=text.upper()
16+
_completion_matches= [cforcinSQLITE_KEYWORDSifc.startswith(text_upper)]
17+
try:
18+
return_completion_matches[state]+" "
19+
exceptIndexError:
20+
returnNone
21+
22+
23+
@contextmanager
24+
defcompleter():
25+
try:
26+
importreadline
27+
exceptImportError:
28+
yield
29+
return
30+
31+
old_completer=readline.get_completer()
32+
try:
33+
readline.set_completer(_complete)
34+
ifreadline.backend=="editline":
35+
# libedit uses "^I" instead of "tab"
36+
command_string="bind ^I rl_complete"
37+
else:
38+
command_string="tab: complete"
39+
readline.parse_and_bind(command_string)
40+
yield
41+
finally:
42+
readline.set_completer(old_completer)

‎Lib/test/test_sqlite3/test_cli.py‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
"""sqlite3 CLI tests."""
22
importsqlite3
3+
importsys
4+
importtextwrap
35
importunittest
46

57
fromsqlite3.__main__importmainascli
8+
fromtest.support.import_helperimportimport_module
69
fromtest.support.os_helperimportTESTFN,unlink
10+
fromtest.support.pty_helperimportrun_pty
711
fromtest.supportimport (
812
captured_stdout,
913
captured_stderr,
1014
captured_stdin,
1115
force_not_colorized_test_class,
16+
requires_subprocess,
1217
)
1318

1419

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

208+
209+
@requires_subprocess()
210+
@force_not_colorized_test_class
211+
classCompletion(unittest.TestCase):
212+
PS1="sqlite> "
213+
214+
@classmethod
215+
defsetUpClass(cls):
216+
_sqlite3=import_module("_sqlite3")
217+
ifnothasattr(_sqlite3,"SQLITE_KEYWORDS"):
218+
raiseunittest.SkipTest("unable to determine SQLite keywords")
219+
220+
readline=import_module("readline")
221+
ifreadline.backend=="editline":
222+
raiseunittest.SkipTest("libedit readline is not supported")
223+
224+
defwrite_input(self,input_,env=None):
225+
script=textwrap.dedent("""
226+
import readline
227+
from sqlite3.__main__ import main
228+
229+
readline.parse_and_bind("set colored-completion-prefix off")
230+
main()
231+
""")
232+
returnrun_pty(script,input_,env)
233+
234+
deftest_complete_sql_keywords(self):
235+
# List candidates starting with 'S', there should be multiple matches.
236+
input_=b"S\t\tEL\t 1;\n.quit\n"
237+
output=self.write_input(input_)
238+
self.assertIn(b"SELECT",output)
239+
self.assertIn(b"SET",output)
240+
self.assertIn(b"SAVEPOINT",output)
241+
self.assertIn(b"(1,)",output)
242+
243+
# Keywords are completed in upper case for even lower case user input.
244+
input_=b"sel\t\t 1;\n.quit\n"
245+
output=self.write_input(input_)
246+
self.assertIn(b"SELECT",output)
247+
self.assertIn(b"(1,)",output)
248+
249+
@unittest.skipIf(sys.platform.startswith("freebsd"),
250+
"Two actual tabs are inserted when there are no matching"
251+
" completions in the pseudo-terminal opened by run_pty()"
252+
" on FreeBSD")
253+
deftest_complete_no_match(self):
254+
input_=b"xyzzy\t\t\b\b\b\b\b\b\b.quit\n"
255+
# Set NO_COLOR to disable coloring for self.PS1.
256+
output=self.write_input(input_,env={"NO_COLOR":"1"})
257+
lines=output.decode().splitlines()
258+
indices= (
259+
ifori,lineinenumerate(lines,1)
260+
ifline.startswith(f"{self.PS1}xyzzy")
261+
)
262+
line_num=next(indices,-1)
263+
self.assertNotEqual(line_num,-1)
264+
# Completions occupy lines, assert no extra lines when there is nothing
265+
# to complete.
266+
self.assertEqual(line_num,len(lines))
267+
268+
deftest_complete_no_input(self):
269+
from_sqlite3importSQLITE_KEYWORDS
270+
271+
script=textwrap.dedent("""
272+
import readline
273+
from sqlite3.__main__ import main
274+
275+
# Configure readline to ...:
276+
# - hide control sequences surrounding each candidate
277+
# - hide "Display all xxx possibilities? (y or n)"
278+
# - hide "--More--"
279+
# - show candidates one per line
280+
readline.parse_and_bind("set colored-completion-prefix off")
281+
readline.parse_and_bind("set colored-stats off")
282+
readline.parse_and_bind("set completion-query-items 0")
283+
readline.parse_and_bind("set page-completions off")
284+
readline.parse_and_bind("set completion-display-width 0")
285+
286+
main()
287+
""")
288+
input_=b"\t\t.quit\n"
289+
output=run_pty(script,input_,env={"NO_COLOR":"1"})
290+
lines=output.decode().splitlines()
291+
indices= [
292+
ifori,lineinenumerate(lines)
293+
ifline.startswith(self.PS1)
294+
]
295+
self.assertEqual(len(indices),2)
296+
start,end=indices
297+
candidates= [l.strip()forlinlines[start+1:end]]
298+
self.assertEqual(candidates,sorted(SQLITE_KEYWORDS))
299+
300+
203301
if__name__=="__main__":
204302
unittest.main()

‎Misc/ACKS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,7 @@ Neil Tallim
18681868
Geoff Talvola
18691869
Anish Tambe
18701870
Musashi Tamura
1871+
Long Tan
18711872
William Tanksley
18721873
Christian Tanzer
18731874
Steven Taschuk
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support keyword completion in the:mod:`sqlite3` command-line interface.

‎Modules/_sqlite/module.c‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include"microprotocols.h"
3333
#include"row.h"
3434
#include"blob.h"
35+
#include"util.h"
3536

3637
#ifSQLITE_VERSION_NUMBER<3015002
3738
#error "SQLite 3.15.2 or higher required"
@@ -404,6 +405,40 @@ pysqlite_error_name(int rc)
404405
returnNULL;
405406
}
406407

408+
staticint
409+
add_keyword_tuple(PyObject*module)
410+
{
411+
#ifSQLITE_VERSION_NUMBER >=3024000
412+
intcount=sqlite3_keyword_count();
413+
PyObject*keywords=PyTuple_New(count);
414+
if (keywords==NULL) {
415+
return-1;
416+
}
417+
for (inti=0;i<count;i++) {
418+
constchar*keyword;
419+
intsize;
420+
intresult=sqlite3_keyword_name(i,&keyword,&size);
421+
if (result!=SQLITE_OK) {
422+
pysqlite_state*state=pysqlite_get_state(module);
423+
set_error_from_code(state,result);
424+
gotoerror;
425+
}
426+
PyObject*kwd=PyUnicode_FromStringAndSize(keyword,size);
427+
if (!kwd) {
428+
gotoerror;
429+
}
430+
PyTuple_SET_ITEM(keywords,i,kwd);
431+
}
432+
returnPyModule_Add(module,"SQLITE_KEYWORDS",keywords);
433+
434+
error:
435+
Py_DECREF(keywords);
436+
return-1;
437+
#else
438+
return0;
439+
#endif
440+
}
441+
407442
staticint
408443
add_integer_constants(PyObject*module) {
409444
#defineADD_INT(ival) \
@@ -702,6 +737,10 @@ module_exec(PyObject *module)
702737
gotoerror;
703738
}
704739

740+
if (add_keyword_tuple(module)<0) {
741+
gotoerror;
742+
}
743+
705744
if (PyModule_AddStringConstant(module,"sqlite_version",sqlite3_libversion())) {
706745
gotoerror;
707746
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp