112 lines
3.7 KiB
Python
112 lines
3.7 KiB
Python
from _sqlite3 import OperationalError
|
|
from contextlib import contextmanager
|
|
|
|
try:
|
|
from _sqlite3 import SQLITE_KEYWORDS
|
|
except ImportError:
|
|
SQLITE_KEYWORDS = ()
|
|
|
|
CLI_COMMANDS = ('.quit', '.help', '.version')
|
|
|
|
_completion_matches = []
|
|
|
|
|
|
def _complete(con, text, state):
|
|
global _completion_matches
|
|
|
|
if state == 0:
|
|
if text.startswith("."):
|
|
_completion_matches = [
|
|
c + " " for c in CLI_COMMANDS if c.startswith(text)
|
|
]
|
|
else:
|
|
text_upper = text.upper()
|
|
_completion_matches = [
|
|
c + " " for c in SQLITE_KEYWORDS if c.startswith(text_upper)
|
|
]
|
|
|
|
cursor = con.cursor()
|
|
schemata = tuple(row[1] for row
|
|
in cursor.execute("PRAGMA database_list"))
|
|
# tables, indexes, triggers, and views
|
|
# escape '_' which can appear in attached database names
|
|
select_clauses = (
|
|
f"""\
|
|
SELECT name || ' ' FROM \"{schema}\".sqlite_master
|
|
WHERE name LIKE REPLACE(:text, '_', '^_') || '%' ESCAPE '^'"""
|
|
for schema in schemata
|
|
)
|
|
_completion_matches.extend(
|
|
row[0]
|
|
for row in cursor.execute(
|
|
" UNION ".join(select_clauses), {"text": text}
|
|
)
|
|
)
|
|
# columns
|
|
try:
|
|
select_clauses = (
|
|
f"""\
|
|
SELECT pti.name || ' ' FROM "{schema}".sqlite_master AS sm
|
|
JOIN pragma_table_xinfo(sm.name,'{schema}') AS pti
|
|
WHERE sm.type='table' AND
|
|
pti.name LIKE REPLACE(:text, '_', '^_') || '%' ESCAPE '^'"""
|
|
for schema in schemata
|
|
)
|
|
_completion_matches.extend(
|
|
row[0]
|
|
for row in cursor.execute(
|
|
" UNION ".join(select_clauses), {"text": text}
|
|
)
|
|
)
|
|
except OperationalError:
|
|
# skip on SQLite<3.16.0 where pragma table-valued function is
|
|
# not supported yet
|
|
pass
|
|
# functions
|
|
try:
|
|
_completion_matches.extend(
|
|
row[0] for row in cursor.execute("""\
|
|
SELECT DISTINCT UPPER(name) || '('
|
|
FROM pragma_function_list()
|
|
WHERE name NOT IN ('->', '->>') AND
|
|
name LIKE REPLACE(:text, '_', '^_') || '%' ESCAPE '^'""",
|
|
{"text": text},
|
|
)
|
|
)
|
|
except OperationalError:
|
|
# skip on SQLite<3.30.0 where function_list is not supported yet
|
|
pass
|
|
# schemata
|
|
text_lower = text.lower()
|
|
_completion_matches.extend(c for c in schemata
|
|
if c.lower().startswith(text_lower))
|
|
_completion_matches = sorted(set(_completion_matches))
|
|
try:
|
|
return _completion_matches[state]
|
|
except IndexError:
|
|
return None
|
|
|
|
|
|
@contextmanager
|
|
def completer(con):
|
|
try:
|
|
import readline
|
|
except ImportError:
|
|
yield
|
|
return
|
|
|
|
old_completer = readline.get_completer()
|
|
def complete(text, state):
|
|
return _complete(con, text, state)
|
|
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)
|