-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsqlite_utils_shell.py
More file actions
146 lines (119 loc) · 3.84 KB
/
Copy pathsqlite_utils_shell.py
File metadata and controls
146 lines (119 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import click
import readline
import sqlite_utils
from sqlite_utils.utils import sqlite3
import sys
import tabulate
MAX_ROWS_TO_RETURN = 100
SQL_KEYWORDS = [
"select ",
"from ",
"where ",
"insert ",
"update ",
"delete ",
"create ",
"drop ",
"begin ",
"commit ",
"rollback ",
]
def completer(text, state):
options = [i for i in SQL_KEYWORDS if i.lower().startswith(text.lower())]
if state < len(options):
return options[state]
else:
return None
if "libedit" in readline.__doc__:
readline.parse_and_bind("bind ^I rl_complete")
else:
readline.parse_and_bind("tab: complete")
readline.set_completer(completer)
@sqlite_utils.hookimpl
def register_commands(cli):
@cli.command()
@click.argument(
"path", type=click.Path(dir_okay=False, readable=True), required=False
)
@click.option(
"load_extensions",
"--load-extension",
multiple=True,
type=click.Path(exists=True),
)
def shell(path, load_extensions):
"Start an interactive SQL shell for this database"
def input_(prompt):
try:
return click.prompt(prompt, type=str, prompt_suffix="")
except click.exceptions.Abort:
sys.exit(0)
run_sql_shell(
path,
input_,
lambda *args: click.echo(" ".join(map(str, args))),
load_extensions,
)
def run_sql_shell(path, input_, print_, load_extensions=None):
if path:
db = sqlite_utils.Database(path)
print_("Attached to {}".format(path))
else:
db = sqlite_utils.Database(memory=True)
print_("In-memory database, content will be lost on exit")
if load_extensions:
db.conn.enable_load_extension(True)
for extension in load_extensions:
db.conn.load_extension(str(extension))
print_("Type 'exit' to exit.")
statement = ""
prompt = "sqlite-utils> "
def is_valid_query(sql):
try:
db.execute("explain " + sql)
except sqlite3.OperationalError:
return False
return True
while True:
line = input_(prompt)
if line:
readline.add_history(line)
prompt = " ...> "
if line.lower() in ("exit", "quit"):
break
statement += "\n" + line
if sqlite3.complete_statement(statement) or (
not statement.strip().endswith(";") and is_valid_query(statement + ";")
):
try:
statement = statement.strip()
cursor = db.execute(statement)
if cursor.description is None:
# It was create table / insert / update
rowcount = cursor.rowcount
if rowcount != -1:
print_(
"{} row{} affected".format(
rowcount, "s" if rowcount != 1 else ""
)
)
else:
print_("Done")
else:
headers = [row[0] for row in cursor.description]
# Only show first MAX_ROWS_TO_RETURN
first_rows = list(cursor.fetchmany(MAX_ROWS_TO_RETURN + 1))
has_more = len(first_rows) == MAX_ROWS_TO_RETURN + 1
print_(
tabulate.tabulate(
first_rows[:MAX_ROWS_TO_RETURN], headers=headers
)
)
if has_more:
print_("[ results were truncated ]")
except sqlite3.Error as e:
print_("An error occurred:", e)
finally:
prompt = "sqlite-utils> "
statement = ""
db.conn.close()