seekdb-cli 0.1.0__py3-none-any.whl
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.
- seekdb_cli/__init__.py +1 -0
- seekdb_cli/commands/__init__.py +0 -0
- seekdb_cli/commands/ai.py +306 -0
- seekdb_cli/commands/collection.py +188 -0
- seekdb_cli/commands/data.py +262 -0
- seekdb_cli/commands/profile.py +142 -0
- seekdb_cli/commands/query.py +178 -0
- seekdb_cli/commands/relations.py +174 -0
- seekdb_cli/commands/schema.py +172 -0
- seekdb_cli/commands/sql.py +362 -0
- seekdb_cli/connection.py +454 -0
- seekdb_cli/logger.py +92 -0
- seekdb_cli/main.py +266 -0
- seekdb_cli/masking.py +69 -0
- seekdb_cli/output.py +203 -0
- seekdb_cli/vecconnection.py +51 -0
- seekdb_cli-0.1.0.dist-info/METADATA +101 -0
- seekdb_cli-0.1.0.dist-info/RECORD +21 -0
- seekdb_cli-0.1.0.dist-info/WHEEL +5 -0
- seekdb_cli-0.1.0.dist-info/entry_points.txt +2 -0
- seekdb_cli-0.1.0.dist-info/top_level.txt +1 -0
seekdb_cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""seekdb ai command — use database AI (DBMS_AI_SERVICE, AI_COMPLETE)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import pymysql
|
|
9
|
+
|
|
10
|
+
from seekdb_cli import output
|
|
11
|
+
from seekdb_cli.connection import get_connection
|
|
12
|
+
from seekdb_cli.logger import log_operation
|
|
13
|
+
|
|
14
|
+
# OceanBase AI model types (for CREATE_AI_MODEL)
|
|
15
|
+
AI_MODEL_TYPES = ("dense_embedding", "completion", "rerank")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group("ai")
|
|
19
|
+
@click.pass_context
|
|
20
|
+
def ai_cmd(ctx: click.Context) -> None:
|
|
21
|
+
"""AI model management and completion (via database DBMS_AI_SERVICE)."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# seekdb ai model ...
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
@ai_cmd.group("model")
|
|
29
|
+
@click.pass_context
|
|
30
|
+
def model_group(ctx: click.Context) -> None:
|
|
31
|
+
"""Manage AI models registered in the database (DBMS_AI_SERVICE)."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@model_group.command("list")
|
|
35
|
+
@click.pass_context
|
|
36
|
+
def model_list(ctx: click.Context) -> None:
|
|
37
|
+
"""List all registered AI models (from oceanbase.DBA_OB_AI_MODELS)."""
|
|
38
|
+
fmt: str = ctx.obj["format"]
|
|
39
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
conn = get_connection(dsn)
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
log_operation("ai model list", ok=False, error_code="CONNECTION_ERROR")
|
|
45
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
timer = output.Timer()
|
|
49
|
+
try:
|
|
50
|
+
with timer, conn.cursor() as cur:
|
|
51
|
+
cur.execute(
|
|
52
|
+
"SELECT MODEL_ID, NAME, TYPE, MODEL_NAME "
|
|
53
|
+
"FROM oceanbase.DBA_OB_AI_MODELS ORDER BY NAME"
|
|
54
|
+
)
|
|
55
|
+
rows = cur.fetchall()
|
|
56
|
+
# DictCursor: keys may be uppercase from view
|
|
57
|
+
result = []
|
|
58
|
+
for r in rows:
|
|
59
|
+
row = dict(r)
|
|
60
|
+
result.append({
|
|
61
|
+
"name": row.get("NAME") or row.get("name"),
|
|
62
|
+
"type": row.get("TYPE") or row.get("type"),
|
|
63
|
+
"model_name": row.get("MODEL_NAME") or row.get("model_name"),
|
|
64
|
+
"model_id": row.get("MODEL_ID") or row.get("model_id"),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
log_operation("ai model list", ok=True, time_ms=timer.elapsed_ms)
|
|
68
|
+
output.success(result, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
69
|
+
except pymysql.err.ProgrammingError as exc:
|
|
70
|
+
log_operation("ai model list", ok=False, error_code="SQL_ERROR")
|
|
71
|
+
output.error(
|
|
72
|
+
"SQL_ERROR",
|
|
73
|
+
str(exc) + ". Ensure the database supports DBA_OB_AI_MODELS (OceanBase AI).",
|
|
74
|
+
fmt=fmt,
|
|
75
|
+
)
|
|
76
|
+
except pymysql.err.Error as exc:
|
|
77
|
+
log_operation("ai model list", ok=False, error_code="SQL_ERROR")
|
|
78
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
79
|
+
finally:
|
|
80
|
+
conn.close()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@model_group.command("create")
|
|
84
|
+
@click.argument("name")
|
|
85
|
+
@click.option(
|
|
86
|
+
"--type",
|
|
87
|
+
"model_type",
|
|
88
|
+
type=click.Choice(AI_MODEL_TYPES),
|
|
89
|
+
required=True,
|
|
90
|
+
help="Model type: dense_embedding, completion, or rerank.",
|
|
91
|
+
)
|
|
92
|
+
@click.option(
|
|
93
|
+
"--model",
|
|
94
|
+
"provider_model_name",
|
|
95
|
+
required=True,
|
|
96
|
+
help="Provider model name (e.g. BAAI/bge-m3, THUDM/GLM-4-9B-0414).",
|
|
97
|
+
)
|
|
98
|
+
@click.pass_context
|
|
99
|
+
def model_create(
|
|
100
|
+
ctx: click.Context,
|
|
101
|
+
name: str,
|
|
102
|
+
model_type: str,
|
|
103
|
+
provider_model_name: str,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Register an AI model via DBMS_AI_SERVICE.CREATE_AI_MODEL. Create an endpoint separately to use it."""
|
|
106
|
+
fmt: str = ctx.obj["format"]
|
|
107
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
conn = get_connection(dsn)
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
log_operation("ai model create", ok=False, error_code="CONNECTION_ERROR")
|
|
113
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
config = json.dumps({"type": model_type, "model_name": provider_model_name})
|
|
117
|
+
timer = output.Timer()
|
|
118
|
+
try:
|
|
119
|
+
with timer, conn.cursor() as cur:
|
|
120
|
+
cur.execute(
|
|
121
|
+
"CALL DBMS_AI_SERVICE.CREATE_AI_MODEL(%s, %s)",
|
|
122
|
+
(name, config),
|
|
123
|
+
)
|
|
124
|
+
conn.commit()
|
|
125
|
+
|
|
126
|
+
log_operation("ai model create", ok=True, time_ms=timer.elapsed_ms)
|
|
127
|
+
output.success(
|
|
128
|
+
{"name": name, "type": model_type, "model_name": provider_model_name},
|
|
129
|
+
time_ms=timer.elapsed_ms,
|
|
130
|
+
fmt=fmt,
|
|
131
|
+
)
|
|
132
|
+
except pymysql.err.Error as exc:
|
|
133
|
+
log_operation("ai model create", ok=False, error_code="SQL_ERROR")
|
|
134
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
135
|
+
finally:
|
|
136
|
+
conn.close()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@model_group.command("delete")
|
|
140
|
+
@click.argument("name")
|
|
141
|
+
@click.pass_context
|
|
142
|
+
def model_delete(ctx: click.Context, name: str) -> None:
|
|
143
|
+
"""Drop an AI model via DBMS_AI_SERVICE.DROP_AI_MODEL. Drop endpoints first if any."""
|
|
144
|
+
fmt: str = ctx.obj["format"]
|
|
145
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
conn = get_connection(dsn)
|
|
149
|
+
except Exception as exc:
|
|
150
|
+
log_operation("ai model delete", ok=False, error_code="CONNECTION_ERROR")
|
|
151
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
timer = output.Timer()
|
|
155
|
+
try:
|
|
156
|
+
with timer, conn.cursor() as cur:
|
|
157
|
+
cur.execute("CALL DBMS_AI_SERVICE.DROP_AI_MODEL(%s)", (name,))
|
|
158
|
+
conn.commit()
|
|
159
|
+
|
|
160
|
+
log_operation("ai model delete", ok=True, time_ms=timer.elapsed_ms)
|
|
161
|
+
output.success({"deleted": name}, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
162
|
+
except pymysql.err.Error as exc:
|
|
163
|
+
log_operation("ai model delete", ok=False, error_code="SQL_ERROR")
|
|
164
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
165
|
+
finally:
|
|
166
|
+
conn.close()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# seekdb ai model endpoint ...
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
@model_group.group("endpoint")
|
|
174
|
+
@click.pass_context
|
|
175
|
+
def endpoint_group(ctx: click.Context) -> None:
|
|
176
|
+
"""Create or delete AI model endpoints (DBMS_AI_SERVICE)."""
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@endpoint_group.command("create")
|
|
180
|
+
@click.argument("endpoint_name")
|
|
181
|
+
@click.argument("ai_model_name")
|
|
182
|
+
@click.option("--url", required=True, help="API URL (e.g. https://api.siliconflow.cn/v1/chat/completions).")
|
|
183
|
+
@click.option("--access-key", required=True, help="API key for the service.")
|
|
184
|
+
@click.option("--provider", default="siliconflow", help="Provider name (siliconflow, openai, dashscope, etc.).")
|
|
185
|
+
@click.pass_context
|
|
186
|
+
def endpoint_create(
|
|
187
|
+
ctx: click.Context,
|
|
188
|
+
endpoint_name: str,
|
|
189
|
+
ai_model_name: str,
|
|
190
|
+
url: str,
|
|
191
|
+
access_key: str,
|
|
192
|
+
provider: str,
|
|
193
|
+
) -> None:
|
|
194
|
+
"""Create an AI model endpoint via DBMS_AI_SERVICE.CREATE_AI_MODEL_ENDPOINT."""
|
|
195
|
+
fmt: str = ctx.obj["format"]
|
|
196
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
conn = get_connection(dsn)
|
|
200
|
+
except Exception as exc:
|
|
201
|
+
log_operation("ai model endpoint create", ok=False, error_code="CONNECTION_ERROR")
|
|
202
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
config = json.dumps({
|
|
206
|
+
"ai_model_name": ai_model_name,
|
|
207
|
+
"url": url,
|
|
208
|
+
"access_key": access_key,
|
|
209
|
+
"provider": provider,
|
|
210
|
+
})
|
|
211
|
+
timer = output.Timer()
|
|
212
|
+
try:
|
|
213
|
+
with timer, conn.cursor() as cur:
|
|
214
|
+
cur.execute(
|
|
215
|
+
"CALL DBMS_AI_SERVICE.CREATE_AI_MODEL_ENDPOINT(%s, %s)",
|
|
216
|
+
(endpoint_name, config),
|
|
217
|
+
)
|
|
218
|
+
conn.commit()
|
|
219
|
+
|
|
220
|
+
log_operation("ai model endpoint create", ok=True, time_ms=timer.elapsed_ms)
|
|
221
|
+
output.success(
|
|
222
|
+
{"endpoint": endpoint_name, "ai_model": ai_model_name, "provider": provider},
|
|
223
|
+
time_ms=timer.elapsed_ms,
|
|
224
|
+
fmt=fmt,
|
|
225
|
+
)
|
|
226
|
+
except pymysql.err.Error as exc:
|
|
227
|
+
log_operation("ai model endpoint create", ok=False, error_code="SQL_ERROR")
|
|
228
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
229
|
+
finally:
|
|
230
|
+
conn.close()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@endpoint_group.command("delete")
|
|
234
|
+
@click.argument("endpoint_name")
|
|
235
|
+
@click.pass_context
|
|
236
|
+
def endpoint_delete(ctx: click.Context, endpoint_name: str) -> None:
|
|
237
|
+
"""Drop an AI model endpoint via DBMS_AI_SERVICE.DROP_AI_MODEL_ENDPOINT."""
|
|
238
|
+
fmt: str = ctx.obj["format"]
|
|
239
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
conn = get_connection(dsn)
|
|
243
|
+
except Exception as exc:
|
|
244
|
+
log_operation("ai model endpoint delete", ok=False, error_code="CONNECTION_ERROR")
|
|
245
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
timer = output.Timer()
|
|
249
|
+
try:
|
|
250
|
+
with timer, conn.cursor() as cur:
|
|
251
|
+
cur.execute(
|
|
252
|
+
"CALL DBMS_AI_SERVICE.DROP_AI_MODEL_ENDPOINT(%s)",
|
|
253
|
+
(endpoint_name,),
|
|
254
|
+
)
|
|
255
|
+
conn.commit()
|
|
256
|
+
|
|
257
|
+
log_operation("ai model endpoint delete", ok=True, time_ms=timer.elapsed_ms)
|
|
258
|
+
output.success({"deleted": endpoint_name}, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
259
|
+
except pymysql.err.Error as exc:
|
|
260
|
+
log_operation("ai model endpoint delete", ok=False, error_code="SQL_ERROR")
|
|
261
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
262
|
+
finally:
|
|
263
|
+
conn.close()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# seekdb ai complete ...
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
@ai_cmd.command("complete")
|
|
271
|
+
@click.argument("prompt")
|
|
272
|
+
@click.option("--model", "model_name", required=True, help="Registered completion model name (from ai model list).")
|
|
273
|
+
@click.pass_context
|
|
274
|
+
def ai_complete(ctx: click.Context, prompt: str, model_name: str) -> None:
|
|
275
|
+
"""Run text completion using the database AI_COMPLETE function."""
|
|
276
|
+
fmt: str = ctx.obj["format"]
|
|
277
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
conn = get_connection(dsn)
|
|
281
|
+
except Exception as exc:
|
|
282
|
+
log_operation("ai complete", ok=False, error_code="CONNECTION_ERROR")
|
|
283
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
timer = output.Timer()
|
|
287
|
+
try:
|
|
288
|
+
with timer, conn.cursor() as cur:
|
|
289
|
+
cur.execute(
|
|
290
|
+
"SELECT AI_COMPLETE(%s, %s) AS response",
|
|
291
|
+
(model_name, prompt),
|
|
292
|
+
)
|
|
293
|
+
row = cur.fetchone()
|
|
294
|
+
response_text = (row.get("response") or row.get("RESPONSE") or "") if row else ""
|
|
295
|
+
|
|
296
|
+
log_operation("ai complete", ok=True, time_ms=timer.elapsed_ms)
|
|
297
|
+
output.success(
|
|
298
|
+
{"model": model_name, "response": response_text},
|
|
299
|
+
time_ms=timer.elapsed_ms,
|
|
300
|
+
fmt=fmt,
|
|
301
|
+
)
|
|
302
|
+
except pymysql.err.Error as exc:
|
|
303
|
+
log_operation("ai complete", ok=False, error_code="AI_ERROR")
|
|
304
|
+
output.error("AI_ERROR", str(exc), fmt=fmt)
|
|
305
|
+
finally:
|
|
306
|
+
conn.close()
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""seekdb collection command — manage vector collections."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from seekdb_cli import output
|
|
10
|
+
from seekdb_cli.logger import log_operation
|
|
11
|
+
from seekdb_cli.vecconnection import get_vec_client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group("collection")
|
|
15
|
+
@click.pass_context
|
|
16
|
+
def collection_cmd(ctx: click.Context) -> None:
|
|
17
|
+
"""Manage vector collections."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@collection_cmd.command("list")
|
|
21
|
+
@click.pass_context
|
|
22
|
+
def list_collections(ctx: click.Context) -> None:
|
|
23
|
+
"""List all collections in the current database."""
|
|
24
|
+
fmt: str = ctx.obj["format"]
|
|
25
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
client = get_vec_client(dsn)
|
|
29
|
+
except (ImportError, ValueError) as exc:
|
|
30
|
+
log_operation("collection list", ok=False, error_code="CONNECTION_ERROR")
|
|
31
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
timer = output.Timer()
|
|
35
|
+
try:
|
|
36
|
+
with timer:
|
|
37
|
+
collections = client.list_collections()
|
|
38
|
+
result = []
|
|
39
|
+
for col in collections:
|
|
40
|
+
info: dict[str, Any] = {"name": col.name}
|
|
41
|
+
try:
|
|
42
|
+
info["count"] = col.count()
|
|
43
|
+
except Exception:
|
|
44
|
+
info["count"] = None
|
|
45
|
+
result.append(info)
|
|
46
|
+
|
|
47
|
+
log_operation("collection list", ok=True, time_ms=timer.elapsed_ms)
|
|
48
|
+
output.success(result, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
49
|
+
except Exception as exc:
|
|
50
|
+
log_operation("collection list", ok=False, error_code="COLLECTION_ERROR")
|
|
51
|
+
output.error("COLLECTION_ERROR", str(exc), fmt=fmt)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@collection_cmd.command("create")
|
|
55
|
+
@click.argument("name")
|
|
56
|
+
@click.option("--dimension", "-d", type=int, default=384, help="Vector dimension (default: 384).")
|
|
57
|
+
@click.option("--distance", type=click.Choice(["cosine", "l2", "ip"]), default="cosine", help="Distance metric.")
|
|
58
|
+
@click.pass_context
|
|
59
|
+
def create_collection(ctx: click.Context, name: str, dimension: int, distance: str) -> None:
|
|
60
|
+
"""Create a new collection."""
|
|
61
|
+
fmt: str = ctx.obj["format"]
|
|
62
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
client = get_vec_client(dsn)
|
|
66
|
+
except (ImportError, ValueError) as exc:
|
|
67
|
+
log_operation("collection create", ok=False, error_code="CONNECTION_ERROR")
|
|
68
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
timer = output.Timer()
|
|
72
|
+
try:
|
|
73
|
+
from pyseekdb import HNSWConfiguration
|
|
74
|
+
|
|
75
|
+
with timer:
|
|
76
|
+
config = HNSWConfiguration(dimension=dimension, distance=distance)
|
|
77
|
+
client.create_collection(
|
|
78
|
+
name=name,
|
|
79
|
+
configuration=config,
|
|
80
|
+
embedding_function=None,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
log_operation("collection create", ok=True, time_ms=timer.elapsed_ms)
|
|
84
|
+
output.success(
|
|
85
|
+
{"name": name, "dimension": dimension, "distance": distance},
|
|
86
|
+
time_ms=timer.elapsed_ms,
|
|
87
|
+
fmt=fmt,
|
|
88
|
+
)
|
|
89
|
+
except Exception as exc:
|
|
90
|
+
log_operation("collection create", ok=False, error_code="COLLECTION_ERROR")
|
|
91
|
+
output.error("COLLECTION_ERROR", str(exc), fmt=fmt)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@collection_cmd.command("delete")
|
|
95
|
+
@click.argument("name")
|
|
96
|
+
@click.pass_context
|
|
97
|
+
def delete_collection(ctx: click.Context, name: str) -> None:
|
|
98
|
+
"""Delete a collection."""
|
|
99
|
+
fmt: str = ctx.obj["format"]
|
|
100
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
client = get_vec_client(dsn)
|
|
104
|
+
except (ImportError, ValueError) as exc:
|
|
105
|
+
log_operation("collection delete", ok=False, error_code="CONNECTION_ERROR")
|
|
106
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
timer = output.Timer()
|
|
110
|
+
try:
|
|
111
|
+
with timer:
|
|
112
|
+
if not client.has_collection(name):
|
|
113
|
+
log_operation("collection delete", ok=False, error_code="COLLECTION_NOT_FOUND")
|
|
114
|
+
output.error(
|
|
115
|
+
"COLLECTION_NOT_FOUND",
|
|
116
|
+
f"Collection '{name}' does not exist",
|
|
117
|
+
fmt=fmt,
|
|
118
|
+
extra={"collections": [c.name for c in client.list_collections()]},
|
|
119
|
+
)
|
|
120
|
+
return
|
|
121
|
+
client.delete_collection(name)
|
|
122
|
+
|
|
123
|
+
log_operation("collection delete", ok=True, time_ms=timer.elapsed_ms)
|
|
124
|
+
output.success({"deleted": name}, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
125
|
+
except SystemExit:
|
|
126
|
+
raise
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
log_operation("collection delete", ok=False, error_code="COLLECTION_ERROR")
|
|
129
|
+
output.error("COLLECTION_ERROR", str(exc), fmt=fmt)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@collection_cmd.command("info")
|
|
133
|
+
@click.argument("name")
|
|
134
|
+
@click.pass_context
|
|
135
|
+
def info_collection(ctx: click.Context, name: str) -> None:
|
|
136
|
+
"""Show collection details (count, peek)."""
|
|
137
|
+
fmt: str = ctx.obj["format"]
|
|
138
|
+
dsn: str | None = ctx.obj["dsn"]
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
client = get_vec_client(dsn)
|
|
142
|
+
except (ImportError, ValueError) as exc:
|
|
143
|
+
log_operation("collection info", ok=False, error_code="CONNECTION_ERROR")
|
|
144
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
timer = output.Timer()
|
|
148
|
+
try:
|
|
149
|
+
with timer:
|
|
150
|
+
if not client.has_collection(name):
|
|
151
|
+
log_operation("collection info", ok=False, error_code="COLLECTION_NOT_FOUND")
|
|
152
|
+
output.error(
|
|
153
|
+
"COLLECTION_NOT_FOUND",
|
|
154
|
+
f"Collection '{name}' does not exist",
|
|
155
|
+
fmt=fmt,
|
|
156
|
+
extra={"collections": [c.name for c in client.list_collections()]},
|
|
157
|
+
)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
col = client.get_collection(name, embedding_function=None)
|
|
161
|
+
count = col.count()
|
|
162
|
+
preview = col.peek(limit=3)
|
|
163
|
+
|
|
164
|
+
data: dict[str, Any] = {
|
|
165
|
+
"name": name,
|
|
166
|
+
"count": count,
|
|
167
|
+
"preview": {
|
|
168
|
+
"ids": preview.get("ids", []),
|
|
169
|
+
"documents": preview.get("documents", []),
|
|
170
|
+
"metadatas": preview.get("metadatas", []),
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
# Dimension and distance come from list_collections() metadata (pyseekdb API)
|
|
174
|
+
for coll in client.list_collections():
|
|
175
|
+
if getattr(coll, "name", None) == name:
|
|
176
|
+
if getattr(coll, "dimension", None) is not None:
|
|
177
|
+
data["dimension"] = coll.dimension
|
|
178
|
+
if getattr(coll, "distance", None) is not None:
|
|
179
|
+
data["distance"] = coll.distance
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
log_operation("collection info", ok=True, time_ms=timer.elapsed_ms)
|
|
183
|
+
output.success(data, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
184
|
+
except SystemExit:
|
|
185
|
+
raise
|
|
186
|
+
except Exception as exc:
|
|
187
|
+
log_operation("collection info", ok=False, error_code="COLLECTION_ERROR")
|
|
188
|
+
output.error("COLLECTION_ERROR", str(exc), fmt=fmt)
|