vmysql-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vmysql_cli-0.1.0/PKG-INFO +24 -0
- vmysql_cli-0.1.0/pyproject.toml +42 -0
- vmysql_cli-0.1.0/setup.cfg +4 -0
- vmysql_cli-0.1.0/vmysql_cli.egg-info/PKG-INFO +24 -0
- vmysql_cli-0.1.0/vmysql_cli.egg-info/SOURCES.txt +8 -0
- vmysql_cli-0.1.0/vmysql_cli.egg-info/dependency_links.txt +1 -0
- vmysql_cli-0.1.0/vmysql_cli.egg-info/entry_points.txt +2 -0
- vmysql_cli-0.1.0/vmysql_cli.egg-info/requires.txt +4 -0
- vmysql_cli-0.1.0/vmysql_cli.egg-info/top_level.txt +1 -0
- vmysql_cli-0.1.0/vmysql_cli.py +214 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vmysql_cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 自然语言操作MySQL——输入中文即可查询、插入、修改数据库
|
|
5
|
+
Author: vvv
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/你的用户名/vmysql-cli
|
|
8
|
+
Project-URL: Issues, https://github.com/你的用户名/vmysql-cli/issues
|
|
9
|
+
Keywords: mysql,nl2sql,cli,ai,deepseek
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Database
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
22
|
+
Requires-Dist: pandas>=2.0
|
|
23
|
+
Requires-Dist: openai>=1.0
|
|
24
|
+
Requires-Dist: pymysql>=1.0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vmysql_cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "自然语言操作MySQL——输入中文即可查询、插入、修改数据库"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "vvv" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mysql", "nl2sql", "cli", "ai", "deepseek"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Database",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"sqlalchemy>=2.0",
|
|
29
|
+
"pandas>=2.0",
|
|
30
|
+
"openai>=1.0",
|
|
31
|
+
"pymysql>=1.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
vmysql_cli = "vmysql_cli:main"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/你的用户名/vmysql-cli"
|
|
39
|
+
Issues = "https://github.com/你的用户名/vmysql-cli/issues"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools]
|
|
42
|
+
py-modules = ["vmysql_cli"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vmysql_cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 自然语言操作MySQL——输入中文即可查询、插入、修改数据库
|
|
5
|
+
Author: vvv
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/你的用户名/vmysql-cli
|
|
8
|
+
Project-URL: Issues, https://github.com/你的用户名/vmysql-cli/issues
|
|
9
|
+
Keywords: mysql,nl2sql,cli,ai,deepseek
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Database
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
22
|
+
Requires-Dist: pandas>=2.0
|
|
23
|
+
Requires-Dist: openai>=1.0
|
|
24
|
+
Requires-Dist: pymysql>=1.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vmysql_cli
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sqlalchemy as a
|
|
5
|
+
import pandas as b
|
|
6
|
+
from openai import OpenAI
|
|
7
|
+
|
|
8
|
+
ds_client = None
|
|
9
|
+
DB_URL = None
|
|
10
|
+
CONFIG_FILE = Path.home() / ".vmysql_cli_config.json"
|
|
11
|
+
|
|
12
|
+
def _load_config():
|
|
13
|
+
"""加载配置:环境变量 > 本地配置文件 > 交互式输入"""
|
|
14
|
+
global ds_client, DB_URL
|
|
15
|
+
config = {}
|
|
16
|
+
|
|
17
|
+
# 1. 尝试从配置文件加载
|
|
18
|
+
if CONFIG_FILE.exists():
|
|
19
|
+
try:
|
|
20
|
+
config = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
|
|
21
|
+
except Exception:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
# 2. 缺失的配置交互式询问
|
|
25
|
+
prompted = False
|
|
26
|
+
if "api_key" not in config:
|
|
27
|
+
config["api_key"] = input("请输入 DeepSeek API Key: ").strip()
|
|
28
|
+
prompted = True
|
|
29
|
+
if "base_url" not in config:
|
|
30
|
+
print("API 地址示例:")
|
|
31
|
+
print(" DeepSeek → https://api.deepseek.com/v1")
|
|
32
|
+
print(" Ollama → http://localhost:11434/v1")
|
|
33
|
+
config["base_url"] = input("请输入 API Base URL: ").strip()
|
|
34
|
+
prompted = True
|
|
35
|
+
# 3. 缺失的数据库配置交互式询问
|
|
36
|
+
if "db_url" not in config:
|
|
37
|
+
host = input("数据库 IP 地址 (默认 127.0.0.1): ").strip() or "127.0.0.1"
|
|
38
|
+
port = input("端口 (默认 3306): ").strip() or "3306"
|
|
39
|
+
user = input("用户名 (默认 root): ").strip() or "root"
|
|
40
|
+
pwd = input("密码: ").strip()
|
|
41
|
+
config["db_url"] = f"mysql+pymysql://{user}:{pwd}@{host}:{port}/db0?charset=utf8"
|
|
42
|
+
prompted = True
|
|
43
|
+
|
|
44
|
+
# 3. 保存配置文件(仅在首次交互输入时)
|
|
45
|
+
if prompted:
|
|
46
|
+
try:
|
|
47
|
+
CONFIG_FILE.write_text(json.dumps(config, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
48
|
+
print(f"配置已保存到 {CONFIG_FILE}")
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
ds_client = OpenAI(api_key=config["api_key"], base_url=config["base_url"])
|
|
54
|
+
DB_URL = config["db_url"]
|
|
55
|
+
|
|
56
|
+
_engine = None
|
|
57
|
+
_schema_cache = None
|
|
58
|
+
_preview_cache = None
|
|
59
|
+
_sql_cache = {}
|
|
60
|
+
|
|
61
|
+
def _get_engine():
|
|
62
|
+
global _engine
|
|
63
|
+
if _engine is None:
|
|
64
|
+
_engine = a.create_engine(DB_URL)
|
|
65
|
+
return _engine
|
|
66
|
+
|
|
67
|
+
def get_database_schema():
|
|
68
|
+
global _schema_cache
|
|
69
|
+
if _schema_cache is not None:
|
|
70
|
+
return _schema_cache
|
|
71
|
+
engine = _get_engine()
|
|
72
|
+
|
|
73
|
+
# 2. 查询数据库所有表
|
|
74
|
+
tables_df = b.read_sql("SHOW TABLES", engine)
|
|
75
|
+
table_names = tables_df.iloc[:, 0].tolist() # 提取所有表名
|
|
76
|
+
|
|
77
|
+
# 3. 遍历每张表,查询字段结构,并拼接成文本
|
|
78
|
+
lines = ["【当前数据库完整结构】\n"]
|
|
79
|
+
for table in table_names:
|
|
80
|
+
lines.append(f"\n表名:{table}\n")
|
|
81
|
+
struct_df = b.read_sql(f"SHOW FULL COLUMNS FROM `{table}`", engine)
|
|
82
|
+
for _, row in struct_df.iterrows():
|
|
83
|
+
extras = []
|
|
84
|
+
if row['Null'] == 'NO':
|
|
85
|
+
extras.append('NOT NULL')
|
|
86
|
+
if row['Key'] == 'PRI':
|
|
87
|
+
extras.append('PRIMARY KEY')
|
|
88
|
+
if 'auto_increment' in str(row['Extra']).lower():
|
|
89
|
+
extras.append('AUTO_INCREMENT')
|
|
90
|
+
if row['Default'] is not None:
|
|
91
|
+
extras.append(f'DEFAULT={row["Default"]}')
|
|
92
|
+
extra_str = ' [' + ' '.join(extras) + ']' if extras else ''
|
|
93
|
+
lines.append(f"- {row['Field']} ({row['Type']}){extra_str}\n")
|
|
94
|
+
_schema_cache = ''.join(lines)
|
|
95
|
+
return _schema_cache
|
|
96
|
+
def get_data_preview():
|
|
97
|
+
"""查每张表的前20条数据,让AI看到实际内容(有缓存)"""
|
|
98
|
+
global _preview_cache
|
|
99
|
+
if _preview_cache is not None:
|
|
100
|
+
return _preview_cache
|
|
101
|
+
try:
|
|
102
|
+
tables = list(b.read_sql("SHOW TABLES", _get_engine()).iloc[:, 0])
|
|
103
|
+
except Exception:
|
|
104
|
+
return ""
|
|
105
|
+
preview = "\n【当前各表数据预览(最多20行)】\n"
|
|
106
|
+
for t in tables:
|
|
107
|
+
try:
|
|
108
|
+
df = b.read_sql(f"SELECT * FROM `{t}` LIMIT 20", _get_engine())
|
|
109
|
+
preview += f"\n表 {t} ({len(df)} 行):\n{df.to_string(index=False)}\n"
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
_preview_cache = preview
|
|
113
|
+
return preview
|
|
114
|
+
|
|
115
|
+
_prompt_prefix_cache = None
|
|
116
|
+
|
|
117
|
+
def _get_prompt_prefix():
|
|
118
|
+
"""缓存 prompt 前缀,避免每次重建大片文本"""
|
|
119
|
+
global _prompt_prefix_cache
|
|
120
|
+
if _prompt_prefix_cache is not None:
|
|
121
|
+
return _prompt_prefix_cache
|
|
122
|
+
_prompt_prefix_cache = f'''
|
|
123
|
+
我现在的数据库真实结构如下:
|
|
124
|
+
{get_database_schema()}
|
|
125
|
+
{get_data_preview()}
|
|
126
|
+
你是MySQL数据库,数据库名为db0。上面列出的就是全部可用的表和字段,没有其他表。
|
|
127
|
+
请结合数据预览理解用户意图(如"无意义的数据""异常数据"等需根据实际数据判断)。
|
|
128
|
+
|
|
129
|
+
请严格遵守规则:
|
|
130
|
+
1. 只输出长度为2的Python列表,无多余文字
|
|
131
|
+
2. 第1项:SQL语句,必须使用:xxx命名占位符
|
|
132
|
+
3. 第2项:Python字典,作为查询参数,不是字符串
|
|
133
|
+
4. 允许SELECT/INSERT/UPDATE/DELETE/DROP/CREATE,DELETE和UPDATE必须带WHERE
|
|
134
|
+
5. 操作已有表时只能使用上面列出的表名和字段名,CREATE TABLE可以定义新字段
|
|
135
|
+
6. 禁止使用information_schema、sys、mysql等系统库
|
|
136
|
+
7. INSERT时永远不要包含自增主键列(如id),让数据库自动生成
|
|
137
|
+
|
|
138
|
+
示例1:["select * from student where id = :id", {{"id": 1}}]
|
|
139
|
+
示例2:["select * from student", {{}}]
|
|
140
|
+
问"有哪些表"示例3:["show tables", {{}}]
|
|
141
|
+
插入示例:["insert into student(name,age) values(:n,:a)", {{"n":"张三","a":20}}]
|
|
142
|
+
批量插入:["insert into student(name,age) values(:n1,:a1),(:n2,:a2)", {{"n1":"张三","a1":20,"n2":"李四","a2":22}}]
|
|
143
|
+
'''.strip()
|
|
144
|
+
return _prompt_prefix_cache
|
|
145
|
+
|
|
146
|
+
def get_nl_to_sql(user_input):
|
|
147
|
+
if user_input in _sql_cache:
|
|
148
|
+
return _sql_cache[user_input], 0.0, 0.0
|
|
149
|
+
t0 = time.perf_counter()
|
|
150
|
+
prompt = _get_prompt_prefix()
|
|
151
|
+
t1 = time.perf_counter()
|
|
152
|
+
|
|
153
|
+
res = ds_client.chat.completions.create(
|
|
154
|
+
model="deepseek-chat",
|
|
155
|
+
messages=[{"role":"system","content":prompt},{"role":"user","content":user_input}],
|
|
156
|
+
temperature=0
|
|
157
|
+
)
|
|
158
|
+
raw = res.choices[0].message.content.strip()
|
|
159
|
+
t2 = time.perf_counter()
|
|
160
|
+
result = eval(raw)
|
|
161
|
+
_sql_cache[user_input] = result
|
|
162
|
+
return result, t1 - t0, t2 - t1
|
|
163
|
+
def _fill_sql(sql, params):
|
|
164
|
+
"""把 :xxx 占位符替换为实际参数值,按key长度降序避免 :n 错误替换 :n1"""
|
|
165
|
+
result = sql
|
|
166
|
+
for k in sorted(params.keys(), key=len, reverse=True):
|
|
167
|
+
v = params[k]
|
|
168
|
+
if v is None:
|
|
169
|
+
result = result.replace(f":{k}", "NULL")
|
|
170
|
+
elif isinstance(v, (int, float)):
|
|
171
|
+
result = result.replace(f":{k}", str(v))
|
|
172
|
+
else:
|
|
173
|
+
result = result.replace(f":{k}", f"'{v}'")
|
|
174
|
+
return result
|
|
175
|
+
|
|
176
|
+
def sql(c):
|
|
177
|
+
c = c.strip()
|
|
178
|
+
# 本地处理表结构查询,不走AI(仅精确匹配)
|
|
179
|
+
if c in ('表结构', '有哪些表', '有什么表', '表有哪些', '表', '库', '结构', 'show tables'):
|
|
180
|
+
print("[耗时] Schema: 0.00s | AI: 0.00s | 执行: 0.00s (本地处理)")
|
|
181
|
+
if c == '表':
|
|
182
|
+
tables = b.read_sql("SHOW TABLES", _get_engine()).iloc[:, 0].tolist()
|
|
183
|
+
return '\n'.join(tables)
|
|
184
|
+
schema = _schema_cache or get_database_schema()
|
|
185
|
+
return schema
|
|
186
|
+
d, t_schema, t_ai = get_nl_to_sql(c)
|
|
187
|
+
t1 = time.perf_counter()
|
|
188
|
+
sql_upper = d[0].strip().upper()
|
|
189
|
+
if sql_upper.startswith(('INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER')):
|
|
190
|
+
with _get_engine().connect() as conn:
|
|
191
|
+
r = conn.execute(a.text(d[0]), d[1])
|
|
192
|
+
conn.commit()
|
|
193
|
+
t_exec = time.perf_counter() - t1
|
|
194
|
+
print(f"[耗时] Schema: {t_schema:.2f}s | AI: {t_ai:.2f}s | 执行: {t_exec:.2f}s")
|
|
195
|
+
print(f"[SQL] {_fill_sql(d[0], d[1])}")
|
|
196
|
+
return f"影响了 {r.rowcount} 行"
|
|
197
|
+
result = b.read_sql(
|
|
198
|
+
sql=a.text(d[0]),
|
|
199
|
+
con=_get_engine(),
|
|
200
|
+
params=d[1]
|
|
201
|
+
)
|
|
202
|
+
t_exec = time.perf_counter() - t1
|
|
203
|
+
print(f"[耗时] Schema: {t_schema:.2f}s | AI: {t_ai:.2f}s | 执行: {t_exec:.2f}s")
|
|
204
|
+
print(f"[SQL] {_fill_sql(d[0], d[1])}")
|
|
205
|
+
return result
|
|
206
|
+
def main():
|
|
207
|
+
_load_config()
|
|
208
|
+
while 1:
|
|
209
|
+
result = sql(input(""))
|
|
210
|
+
print(result)
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
main()
|
|
214
|
+
|