cz-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.
- cz_cli/__init__.py +1 -0
- cz_cli/commands/__init__.py +0 -0
- cz_cli/commands/profile.py +265 -0
- cz_cli/commands/schema.py +213 -0
- cz_cli/commands/skills_installer.py +222 -0
- cz_cli/commands/sql.py +674 -0
- cz_cli/commands/table.py +402 -0
- cz_cli/commands/workspace.py +153 -0
- cz_cli/connection.py +256 -0
- cz_cli/logger.py +92 -0
- cz_cli/main.py +303 -0
- cz_cli/masking.py +69 -0
- cz_cli/output.py +257 -0
- cz_cli/skills/cz-cli/SKILL.md +222 -0
- cz_cli-0.1.0.dist-info/METADATA +446 -0
- cz_cli-0.1.0.dist-info/RECORD +20 -0
- cz_cli-0.1.0.dist-info/WHEEL +5 -0
- cz_cli-0.1.0.dist-info/entry_points.txt +3 -0
- cz_cli-0.1.0.dist-info/licenses/LICENSE +190 -0
- cz_cli-0.1.0.dist-info/top_level.txt +1 -0
cz_cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""clickzetta profile command — manage connection profiles."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from cz_cli import output
|
|
12
|
+
from cz_cli.logger import log_operation
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import tomllib
|
|
16
|
+
except ImportError:
|
|
17
|
+
import tomli as tomllib # type: ignore
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_PROFILES_DIR = Path.home() / ".clickzetta"
|
|
21
|
+
_PROFILES_FILE = _PROFILES_DIR / "profiles.toml"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_profiles() -> dict[str, Any]:
|
|
25
|
+
"""Load profiles from ~/.clickzetta/profiles.toml"""
|
|
26
|
+
if not _PROFILES_FILE.exists():
|
|
27
|
+
return {"profiles": {}}
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
with open(_PROFILES_FILE, "rb") as f:
|
|
31
|
+
return tomllib.load(f)
|
|
32
|
+
except Exception as exc:
|
|
33
|
+
return {"profiles": {}}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _save_profiles(data: dict[str, Any]) -> None:
|
|
37
|
+
"""Save profiles to ~/.clickzetta/profiles.toml"""
|
|
38
|
+
_PROFILES_DIR.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
|
|
40
|
+
# Convert to TOML format
|
|
41
|
+
lines = []
|
|
42
|
+
|
|
43
|
+
# Add default_profile at the top if it exists
|
|
44
|
+
if "default_profile" in data:
|
|
45
|
+
lines.append(f'default_profile = "{data["default_profile"]}"')
|
|
46
|
+
lines.append("")
|
|
47
|
+
|
|
48
|
+
for profile_name, profile_data in data.get("profiles", {}).items():
|
|
49
|
+
lines.append(f"[profiles.{profile_name}]")
|
|
50
|
+
for key, value in profile_data.items():
|
|
51
|
+
if isinstance(value, str):
|
|
52
|
+
lines.append(f'{key} = "{value}"')
|
|
53
|
+
else:
|
|
54
|
+
lines.append(f"{key} = {value}")
|
|
55
|
+
lines.append("")
|
|
56
|
+
|
|
57
|
+
with open(_PROFILES_FILE, "w", encoding="utf-8") as f:
|
|
58
|
+
f.write("\n".join(lines))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@click.group("profile")
|
|
62
|
+
@click.pass_context
|
|
63
|
+
def profile_cmd(ctx: click.Context) -> None:
|
|
64
|
+
"""Manage connection profiles."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@profile_cmd.command("list")
|
|
68
|
+
@click.pass_context
|
|
69
|
+
def list_profiles(ctx: click.Context) -> None:
|
|
70
|
+
"""List all configured profiles."""
|
|
71
|
+
fmt: str = ctx.obj["format"]
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
data = _load_profiles()
|
|
75
|
+
profiles = data.get("profiles", {})
|
|
76
|
+
default_profile = data.get("default_profile")
|
|
77
|
+
|
|
78
|
+
result = []
|
|
79
|
+
for name, profile_data in profiles.items():
|
|
80
|
+
result.append({
|
|
81
|
+
"name": name,
|
|
82
|
+
"username": profile_data.get("username", ""),
|
|
83
|
+
"service": profile_data.get("service", ""),
|
|
84
|
+
"instance": profile_data.get("instance", ""),
|
|
85
|
+
"workspace": profile_data.get("workspace", ""),
|
|
86
|
+
"is_default": name == default_profile,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
log_operation("profile list", ok=True)
|
|
90
|
+
output.success(result, fmt=fmt)
|
|
91
|
+
except Exception as exc:
|
|
92
|
+
log_operation("profile list", ok=False, error_code="INTERNAL_ERROR")
|
|
93
|
+
output.error("INTERNAL_ERROR", str(exc), fmt=fmt)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@profile_cmd.command("create")
|
|
97
|
+
@click.argument("name")
|
|
98
|
+
@click.option("--username", required=True, help="Username")
|
|
99
|
+
@click.option("--password", required=True, help="Password")
|
|
100
|
+
@click.option("--service", default="dev-api.clickzetta.com", help="Service endpoint")
|
|
101
|
+
@click.option("--instance", required=True, help="Instance ID")
|
|
102
|
+
@click.option("--workspace", required=True, help="Workspace name")
|
|
103
|
+
@click.option("--schema", default="public", help="Default schema")
|
|
104
|
+
@click.option("--vcluster", default="default", help="Virtual cluster")
|
|
105
|
+
@click.option("--skip-verify", is_flag=True, help="Skip connection verification")
|
|
106
|
+
@click.pass_context
|
|
107
|
+
def create_profile(
|
|
108
|
+
ctx: click.Context,
|
|
109
|
+
name: str,
|
|
110
|
+
username: str,
|
|
111
|
+
password: str,
|
|
112
|
+
service: str,
|
|
113
|
+
instance: str,
|
|
114
|
+
workspace: str,
|
|
115
|
+
schema: str,
|
|
116
|
+
vcluster: str,
|
|
117
|
+
skip_verify: bool,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Create a new profile."""
|
|
120
|
+
fmt: str = ctx.obj["format"]
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
data = _load_profiles()
|
|
124
|
+
profiles = data.get("profiles", {})
|
|
125
|
+
|
|
126
|
+
if name in profiles:
|
|
127
|
+
log_operation("profile create", ok=False, error_code="PROFILE_EXISTS")
|
|
128
|
+
output.error("PROFILE_EXISTS", f"Profile '{name}' already exists", fmt=fmt)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
# Verify connection unless --skip-verify
|
|
132
|
+
if not skip_verify:
|
|
133
|
+
from cz_cli.connection import get_connection
|
|
134
|
+
try:
|
|
135
|
+
conn = get_connection(
|
|
136
|
+
username=username,
|
|
137
|
+
password=password,
|
|
138
|
+
service=service,
|
|
139
|
+
instance=instance,
|
|
140
|
+
workspace=workspace,
|
|
141
|
+
schema=schema,
|
|
142
|
+
vcluster=vcluster,
|
|
143
|
+
)
|
|
144
|
+
# Test connection
|
|
145
|
+
cursor = conn.cursor()
|
|
146
|
+
cursor.execute("SELECT 1")
|
|
147
|
+
cursor.close()
|
|
148
|
+
conn.close()
|
|
149
|
+
except Exception as exc:
|
|
150
|
+
log_operation("profile create", ok=False, error_code="CONNECTION_FAILED")
|
|
151
|
+
output.error(
|
|
152
|
+
"CONNECTION_FAILED",
|
|
153
|
+
f"Failed to connect with provided credentials: {str(exc)}",
|
|
154
|
+
fmt=fmt,
|
|
155
|
+
)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
profiles[name] = {
|
|
159
|
+
"username": username,
|
|
160
|
+
"password": password,
|
|
161
|
+
"service": service,
|
|
162
|
+
"instance": instance,
|
|
163
|
+
"workspace": workspace,
|
|
164
|
+
"schema": schema,
|
|
165
|
+
"vcluster": vcluster,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
data["profiles"] = profiles
|
|
169
|
+
_save_profiles(data)
|
|
170
|
+
|
|
171
|
+
log_operation("profile create", ok=True)
|
|
172
|
+
output.success({"message": f"Profile '{name}' created successfully"}, fmt=fmt)
|
|
173
|
+
except Exception as exc:
|
|
174
|
+
log_operation("profile create", ok=False, error_code="INTERNAL_ERROR")
|
|
175
|
+
output.error("INTERNAL_ERROR", str(exc), fmt=fmt)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@profile_cmd.command("update")
|
|
179
|
+
@click.argument("name")
|
|
180
|
+
@click.argument("key")
|
|
181
|
+
@click.argument("value")
|
|
182
|
+
@click.pass_context
|
|
183
|
+
def update_profile(ctx: click.Context, name: str, key: str, value: str) -> None:
|
|
184
|
+
"""Update a profile field."""
|
|
185
|
+
fmt: str = ctx.obj["format"]
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
data = _load_profiles()
|
|
189
|
+
profiles = data.get("profiles", {})
|
|
190
|
+
|
|
191
|
+
if name not in profiles:
|
|
192
|
+
log_operation("profile update", ok=False, error_code="PROFILE_NOT_FOUND")
|
|
193
|
+
output.error("PROFILE_NOT_FOUND", f"Profile '{name}' not found", fmt=fmt)
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
valid_keys = ["username", "password", "service", "instance", "workspace", "schema", "vcluster"]
|
|
197
|
+
if key not in valid_keys:
|
|
198
|
+
log_operation("profile update", ok=False, error_code="INVALID_KEY")
|
|
199
|
+
output.error("INVALID_KEY", f"Invalid key '{key}'. Valid keys: {', '.join(valid_keys)}", fmt=fmt)
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
profiles[name][key] = value
|
|
203
|
+
data["profiles"] = profiles
|
|
204
|
+
_save_profiles(data)
|
|
205
|
+
|
|
206
|
+
log_operation("profile update", ok=True)
|
|
207
|
+
output.success({"message": f"Profile '{name}' updated successfully"}, fmt=fmt)
|
|
208
|
+
except Exception as exc:
|
|
209
|
+
log_operation("profile update", ok=False, error_code="INTERNAL_ERROR")
|
|
210
|
+
output.error("INTERNAL_ERROR", str(exc), fmt=fmt)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@profile_cmd.command("delete")
|
|
214
|
+
@click.argument("name")
|
|
215
|
+
@click.pass_context
|
|
216
|
+
def delete_profile(ctx: click.Context, name: str) -> None:
|
|
217
|
+
"""Delete a profile."""
|
|
218
|
+
fmt: str = ctx.obj["format"]
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
data = _load_profiles()
|
|
222
|
+
profiles = data.get("profiles", {})
|
|
223
|
+
|
|
224
|
+
if name not in profiles:
|
|
225
|
+
log_operation("profile delete", ok=False, error_code="PROFILE_NOT_FOUND")
|
|
226
|
+
output.error("PROFILE_NOT_FOUND", f"Profile '{name}' not found", fmt=fmt)
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
del profiles[name]
|
|
230
|
+
data["profiles"] = profiles
|
|
231
|
+
_save_profiles(data)
|
|
232
|
+
|
|
233
|
+
log_operation("profile delete", ok=True)
|
|
234
|
+
output.success({"message": f"Profile '{name}' deleted successfully"}, fmt=fmt)
|
|
235
|
+
except Exception as exc:
|
|
236
|
+
log_operation("profile delete", ok=False, error_code="INTERNAL_ERROR")
|
|
237
|
+
output.error("INTERNAL_ERROR", str(exc), fmt=fmt)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@profile_cmd.command("use")
|
|
241
|
+
@click.argument("name")
|
|
242
|
+
@click.pass_context
|
|
243
|
+
def use_profile(ctx: click.Context, name: str) -> None:
|
|
244
|
+
"""Set a profile as default."""
|
|
245
|
+
fmt: str = ctx.obj["format"]
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
data = _load_profiles()
|
|
249
|
+
profiles = data.get("profiles", {})
|
|
250
|
+
|
|
251
|
+
if name not in profiles:
|
|
252
|
+
log_operation("profile use", ok=False, error_code="PROFILE_NOT_FOUND")
|
|
253
|
+
output.error("PROFILE_NOT_FOUND", f"Profile '{name}' not found", fmt=fmt)
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
# Set default_profile marker
|
|
257
|
+
data["default_profile"] = name
|
|
258
|
+
|
|
259
|
+
_save_profiles(data)
|
|
260
|
+
|
|
261
|
+
log_operation("profile use", ok=True)
|
|
262
|
+
output.success({"message": f"Profile '{name}' set as default"}, fmt=fmt)
|
|
263
|
+
except Exception as exc:
|
|
264
|
+
log_operation("profile use", ok=False, error_code="INTERNAL_ERROR")
|
|
265
|
+
output.error("INTERNAL_ERROR", str(exc), fmt=fmt)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""clickzetta schema command — manage schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from cz_cli import output
|
|
10
|
+
from cz_cli.connection import get_connection
|
|
11
|
+
from cz_cli.logger import log_operation
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group("schema")
|
|
15
|
+
@click.pass_context
|
|
16
|
+
def schema_cmd(ctx: click.Context) -> None:
|
|
17
|
+
"""Manage schemas."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@schema_cmd.command("list")
|
|
21
|
+
@click.option("--like", help="Filter schemas by pattern (e.g. 'test%').")
|
|
22
|
+
@click.option("--limit", default=100, help="Maximum number of schemas to return (default: 100).")
|
|
23
|
+
@click.pass_context
|
|
24
|
+
def list_schemas(ctx: click.Context, like: str | None, limit: int) -> None:
|
|
25
|
+
"""List all schemas in the current workspace."""
|
|
26
|
+
fmt: str = ctx.obj["format"]
|
|
27
|
+
profile: str | None = ctx.obj.get("profile")
|
|
28
|
+
jdbc_url: str | None = ctx.obj.get("jdbc_url")
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
conn = get_connection(jdbc_url=jdbc_url, profile=profile)
|
|
32
|
+
except Exception as exc:
|
|
33
|
+
log_operation("schema list", ok=False, error_code="CONNECTION_ERROR")
|
|
34
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
timer = output.Timer()
|
|
38
|
+
try:
|
|
39
|
+
with timer:
|
|
40
|
+
cursor = conn.cursor()
|
|
41
|
+
try:
|
|
42
|
+
sql = "SHOW SCHEMAS"
|
|
43
|
+
if like:
|
|
44
|
+
sql += f" LIKE '{like}'"
|
|
45
|
+
# Note: SHOW SCHEMAS does not support LIMIT clause, we'll limit client-side
|
|
46
|
+
|
|
47
|
+
cursor.execute(sql)
|
|
48
|
+
rows = cursor.fetchall()
|
|
49
|
+
|
|
50
|
+
# Convert tuples to dicts
|
|
51
|
+
columns = [d[0] for d in cursor.description] if cursor.description else []
|
|
52
|
+
rows_dict = [dict(zip(columns, row)) if isinstance(row, tuple) else row for row in rows]
|
|
53
|
+
|
|
54
|
+
result = []
|
|
55
|
+
for row in rows_dict:
|
|
56
|
+
result.append({
|
|
57
|
+
"name": row.get("schema_name", row.get("name", row.get(columns[0], "") if columns else "")),
|
|
58
|
+
"type": row.get("type", ""),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
# Client-side limit (SHOW SCHEMAS doesn't support LIMIT clause)
|
|
62
|
+
total_count = len(result)
|
|
63
|
+
if total_count > limit:
|
|
64
|
+
result = result[:limit]
|
|
65
|
+
|
|
66
|
+
# Add AI message if results were limited
|
|
67
|
+
ai_msg = None
|
|
68
|
+
if total_count > limit:
|
|
69
|
+
ai_msg = f"Results limited to {limit} of {total_count} schemas. Use --limit to adjust or --like to filter."
|
|
70
|
+
|
|
71
|
+
log_operation("schema list", ok=True, time_ms=timer.elapsed_ms)
|
|
72
|
+
output.success(result, time_ms=timer.elapsed_ms, fmt=fmt, ai_message=ai_msg)
|
|
73
|
+
finally:
|
|
74
|
+
cursor.close()
|
|
75
|
+
except Exception as exc:
|
|
76
|
+
log_operation("schema list", ok=False, error_code="SQL_ERROR")
|
|
77
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
78
|
+
finally:
|
|
79
|
+
conn.close()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@schema_cmd.command("describe")
|
|
83
|
+
@click.argument("name")
|
|
84
|
+
@click.pass_context
|
|
85
|
+
def describe_schema(ctx: click.Context, name: str) -> None:
|
|
86
|
+
"""Show schema details including tables."""
|
|
87
|
+
fmt: str = ctx.obj["format"]
|
|
88
|
+
profile: str | None = ctx.obj.get("profile")
|
|
89
|
+
jdbc_url: str | None = ctx.obj.get("jdbc_url")
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
conn = get_connection(jdbc_url=jdbc_url, profile=profile)
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
log_operation("schema describe", ok=False, error_code="CONNECTION_ERROR")
|
|
95
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
timer = output.Timer()
|
|
99
|
+
try:
|
|
100
|
+
with timer:
|
|
101
|
+
cursor = conn.cursor()
|
|
102
|
+
try:
|
|
103
|
+
# Get schema info
|
|
104
|
+
cursor.execute(f"SHOW SCHEMAS EXTENDED WHERE schema_name='{name}'")
|
|
105
|
+
schema_rows = cursor.fetchall()
|
|
106
|
+
|
|
107
|
+
if not schema_rows:
|
|
108
|
+
log_operation("schema describe", ok=False, error_code="SCHEMA_NOT_FOUND")
|
|
109
|
+
output.error("SCHEMA_NOT_FOUND", f"Schema '{name}' not found", fmt=fmt)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# Convert tuple to dict
|
|
113
|
+
columns = [d[0] for d in cursor.description] if cursor.description else []
|
|
114
|
+
schema_info = dict(zip(columns, schema_rows[0])) if isinstance(schema_rows[0], tuple) else schema_rows[0]
|
|
115
|
+
|
|
116
|
+
# Get tables in schema
|
|
117
|
+
cursor.execute(f"SHOW TABLES IN {name}")
|
|
118
|
+
table_rows = cursor.fetchall()
|
|
119
|
+
|
|
120
|
+
# Convert tuples to dicts
|
|
121
|
+
table_columns = [d[0] for d in cursor.description] if cursor.description else []
|
|
122
|
+
tables = []
|
|
123
|
+
for r in table_rows:
|
|
124
|
+
if isinstance(r, tuple):
|
|
125
|
+
row_dict = dict(zip(table_columns, r))
|
|
126
|
+
tables.append(row_dict.get(table_columns[0], "") if table_columns else "")
|
|
127
|
+
elif isinstance(r, dict):
|
|
128
|
+
tables.append(list(r.values())[0] if r else "")
|
|
129
|
+
else:
|
|
130
|
+
tables.append(str(r))
|
|
131
|
+
|
|
132
|
+
result: dict[str, Any] = {
|
|
133
|
+
"name": name,
|
|
134
|
+
"type": schema_info.get("type", ""),
|
|
135
|
+
"table_count": len(tables),
|
|
136
|
+
"tables": tables,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
log_operation("schema describe", ok=True, time_ms=timer.elapsed_ms)
|
|
140
|
+
output.success(result, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
141
|
+
finally:
|
|
142
|
+
cursor.close()
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
log_operation("schema describe", ok=False, error_code="SQL_ERROR")
|
|
145
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
146
|
+
finally:
|
|
147
|
+
conn.close()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@schema_cmd.command("create")
|
|
151
|
+
@click.argument("name")
|
|
152
|
+
@click.pass_context
|
|
153
|
+
def create_schema(ctx: click.Context, name: str) -> None:
|
|
154
|
+
"""Create a new schema."""
|
|
155
|
+
fmt: str = ctx.obj["format"]
|
|
156
|
+
profile: str | None = ctx.obj.get("profile")
|
|
157
|
+
jdbc_url: str | None = ctx.obj.get("jdbc_url")
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
conn = get_connection(jdbc_url=jdbc_url, profile=profile)
|
|
161
|
+
except Exception as exc:
|
|
162
|
+
log_operation("schema create", ok=False, error_code="CONNECTION_ERROR")
|
|
163
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
timer = output.Timer()
|
|
167
|
+
try:
|
|
168
|
+
with timer:
|
|
169
|
+
cursor = conn.cursor()
|
|
170
|
+
try:
|
|
171
|
+
cursor.execute(f"CREATE SCHEMA {name}")
|
|
172
|
+
log_operation("schema create", ok=True, time_ms=timer.elapsed_ms)
|
|
173
|
+
output.success({"message": f"Schema '{name}' created successfully"}, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
174
|
+
finally:
|
|
175
|
+
cursor.close()
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
log_operation("schema create", ok=False, error_code="SQL_ERROR")
|
|
178
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
179
|
+
finally:
|
|
180
|
+
conn.close()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@schema_cmd.command("drop")
|
|
184
|
+
@click.argument("name")
|
|
185
|
+
@click.pass_context
|
|
186
|
+
def drop_schema(ctx: click.Context, name: str) -> None:
|
|
187
|
+
"""Drop a schema."""
|
|
188
|
+
fmt: str = ctx.obj["format"]
|
|
189
|
+
profile: str | None = ctx.obj.get("profile")
|
|
190
|
+
jdbc_url: str | None = ctx.obj.get("jdbc_url")
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
conn = get_connection(jdbc_url=jdbc_url, profile=profile)
|
|
194
|
+
except Exception as exc:
|
|
195
|
+
log_operation("schema drop", ok=False, error_code="CONNECTION_ERROR")
|
|
196
|
+
output.error("CONNECTION_ERROR", str(exc), fmt=fmt)
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
timer = output.Timer()
|
|
200
|
+
try:
|
|
201
|
+
with timer:
|
|
202
|
+
cursor = conn.cursor()
|
|
203
|
+
try:
|
|
204
|
+
cursor.execute(f"DROP SCHEMA {name}")
|
|
205
|
+
log_operation("schema drop", ok=True, time_ms=timer.elapsed_ms)
|
|
206
|
+
output.success({"message": f"Schema '{name}' dropped successfully"}, time_ms=timer.elapsed_ms, fmt=fmt)
|
|
207
|
+
finally:
|
|
208
|
+
cursor.close()
|
|
209
|
+
except Exception as exc:
|
|
210
|
+
log_operation("schema drop", ok=False, error_code="SQL_ERROR")
|
|
211
|
+
output.error("SQL_ERROR", str(exc), fmt=fmt)
|
|
212
|
+
finally:
|
|
213
|
+
conn.close()
|