rosetta-sql 1.0.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.
- benchmark/generate_csv_data.py +83 -0
- benchmark/import_data.py +168 -0
- rosetta/__init__.py +3 -0
- rosetta/__main__.py +8 -0
- rosetta/benchmark.py +1678 -0
- rosetta/buglist.py +108 -0
- rosetta/cli/__init__.py +11 -0
- rosetta/cli/config_cmd.py +243 -0
- rosetta/cli/exec.py +219 -0
- rosetta/cli/interactive_cmd.py +124 -0
- rosetta/cli/list_cmd.py +215 -0
- rosetta/cli/main.py +617 -0
- rosetta/cli/output.py +545 -0
- rosetta/cli/result.py +61 -0
- rosetta/cli/result_cmd.py +247 -0
- rosetta/cli/run.py +625 -0
- rosetta/cli/status.py +161 -0
- rosetta/comparator.py +205 -0
- rosetta/config.py +139 -0
- rosetta/executor.py +403 -0
- rosetta/flamegraph.py +630 -0
- rosetta/interactive.py +1790 -0
- rosetta/models.py +197 -0
- rosetta/parser.py +308 -0
- rosetta/reporter/__init__.py +1 -0
- rosetta/reporter/bench_html.py +1457 -0
- rosetta/reporter/bench_text.py +162 -0
- rosetta/reporter/history.py +1686 -0
- rosetta/reporter/html.py +644 -0
- rosetta/reporter/text.py +110 -0
- rosetta/runner.py +3089 -0
- rosetta/ui.py +736 -0
- rosetta/whitelist.py +161 -0
- rosetta_sql-1.0.0.dist-info/LICENSE +21 -0
- rosetta_sql-1.0.0.dist-info/METADATA +379 -0
- rosetta_sql-1.0.0.dist-info/RECORD +42 -0
- rosetta_sql-1.0.0.dist-info/WHEEL +5 -0
- rosetta_sql-1.0.0.dist-info/entry_points.txt +2 -0
- rosetta_sql-1.0.0.dist-info/top_level.txt +4 -0
- skills/rosetta/scripts/install_rosetta.py +469 -0
- skills/rosetta/scripts/rosetta_wrapper.py +377 -0
- tests/test_cli.py +749 -0
rosetta/cli/main.py
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI entry point with subcommand structure.
|
|
3
|
+
|
|
4
|
+
This module provides a modern CLI architecture that is friendly to both
|
|
5
|
+
AI Agents (JSON output via -j/--json) and humans (default output).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import logging
|
|
10
|
+
import sys
|
|
11
|
+
from typing import List, Optional
|
|
12
|
+
|
|
13
|
+
from .. import __version__
|
|
14
|
+
from .output import OutputFormatter
|
|
15
|
+
from .result import CommandResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _add_global_options(parser: argparse.ArgumentParser) -> None:
|
|
19
|
+
"""Add global options (-j/--json, -c/--config, -v/--verbose) to a parser.
|
|
20
|
+
|
|
21
|
+
Called on every subcommand parser so that flags like ``--json`` can appear
|
|
22
|
+
after the subcommand name (e.g. ``rosetta status --json``).
|
|
23
|
+
"""
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"-j", "--json",
|
|
26
|
+
action="store_true",
|
|
27
|
+
default=False,
|
|
28
|
+
help="JSON output (AI Agent friendly)",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--config", "-c",
|
|
32
|
+
default=None,
|
|
33
|
+
help="Path to DBMS config JSON (default: dbms_config.json)",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--verbose", "-v",
|
|
37
|
+
action="store_true",
|
|
38
|
+
default=False,
|
|
39
|
+
help="Enable verbose / debug logging",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--version",
|
|
43
|
+
action="store_true",
|
|
44
|
+
default=False,
|
|
45
|
+
help="Show rosetta version and exit",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# A lightweight parser that only knows the global flags.
|
|
50
|
+
# Used for a first pass so that ``rosetta -j status`` works
|
|
51
|
+
# (the main parser sees -j *before* the subcommand name).
|
|
52
|
+
_global_preparser = argparse.ArgumentParser(add_help=False)
|
|
53
|
+
_global_preparser.add_argument("-j", "--json", action="store_true", default=False)
|
|
54
|
+
_global_preparser.add_argument("--config", "-c", default=None)
|
|
55
|
+
_global_preparser.add_argument("--verbose", "-v", action="store_true", default=False)
|
|
56
|
+
_global_preparser.add_argument("-V", "--version", action="store_true", default=False)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
60
|
+
"""
|
|
61
|
+
Create the main argument parser with subcommands.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
argparse.ArgumentParser: The configured parser
|
|
65
|
+
"""
|
|
66
|
+
parser = argparse.ArgumentParser(
|
|
67
|
+
prog="rosetta",
|
|
68
|
+
description=(
|
|
69
|
+
"Rosetta — Cross-DBMS SQL testing & benchmarking toolkit.\n\n"
|
|
70
|
+
"Human-readable output by default.\n"
|
|
71
|
+
"Use -j/--json for JSON output (AI Agent friendly)."
|
|
72
|
+
),
|
|
73
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
74
|
+
# Inherit global flags so that ``rosetta -j status`` works
|
|
75
|
+
parents=[_global_preparser],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Create subparsers
|
|
79
|
+
subparsers = parser.add_subparsers(
|
|
80
|
+
dest="command",
|
|
81
|
+
title="commands",
|
|
82
|
+
description="Available subcommands",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Add subcommands
|
|
86
|
+
_add_mtr_subparser(subparsers)
|
|
87
|
+
_add_bench_subparser(subparsers)
|
|
88
|
+
_add_status_subparser(subparsers)
|
|
89
|
+
_add_exec_subparser(subparsers)
|
|
90
|
+
_add_config_subparser(subparsers)
|
|
91
|
+
_add_result_subparser(subparsers)
|
|
92
|
+
_add_interactive_subparser(subparsers)
|
|
93
|
+
|
|
94
|
+
return parser
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# Shared argument helpers
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def _add_mtr_arguments(parser):
|
|
102
|
+
"""Add MTR-specific arguments to a parser."""
|
|
103
|
+
parser.add_argument(
|
|
104
|
+
"-t", "--test",
|
|
105
|
+
required=True,
|
|
106
|
+
help="Path to .test file",
|
|
107
|
+
)
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"--dbms",
|
|
110
|
+
required=True,
|
|
111
|
+
help="DBMS targets, comma-separated (e.g. tdsql,mysql,tidb)",
|
|
112
|
+
)
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"--database", "-d",
|
|
115
|
+
default="rosetta_mtr_test",
|
|
116
|
+
help="Test database name (default: rosetta_mtr_test)",
|
|
117
|
+
)
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
"--baseline", "-b",
|
|
120
|
+
default="tdsql",
|
|
121
|
+
help="Baseline DBMS name for diff (default: tdsql)",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"--output-dir", "-o",
|
|
125
|
+
default="results",
|
|
126
|
+
help="Output directory for reports (default: results)",
|
|
127
|
+
)
|
|
128
|
+
parser.add_argument(
|
|
129
|
+
"--output-format", "-f",
|
|
130
|
+
default="all",
|
|
131
|
+
choices=["text", "html", "all"],
|
|
132
|
+
help="Report format (default: all)",
|
|
133
|
+
)
|
|
134
|
+
parser.add_argument(
|
|
135
|
+
"--skip-explain",
|
|
136
|
+
action="store_true",
|
|
137
|
+
default=True,
|
|
138
|
+
help="Skip EXPLAIN statements (default: on)",
|
|
139
|
+
)
|
|
140
|
+
parser.add_argument(
|
|
141
|
+
"--skip-analyze",
|
|
142
|
+
action="store_true",
|
|
143
|
+
help="Skip ANALYZE TABLE statements",
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"--skip-show-create",
|
|
147
|
+
action="store_true",
|
|
148
|
+
help="Skip SHOW CREATE TABLE statements",
|
|
149
|
+
)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"--parse-only",
|
|
152
|
+
action="store_true",
|
|
153
|
+
help="Only parse .test file and print statements (no execution)",
|
|
154
|
+
)
|
|
155
|
+
parser.add_argument(
|
|
156
|
+
"--diff-only",
|
|
157
|
+
action="store_true",
|
|
158
|
+
help="Re-generate reports from existing .result files (no DB execution)",
|
|
159
|
+
)
|
|
160
|
+
parser.add_argument(
|
|
161
|
+
"--serve", "-s",
|
|
162
|
+
action="store_true",
|
|
163
|
+
help="Start a local HTTP server to view HTML reports",
|
|
164
|
+
)
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
"--port", "-p",
|
|
167
|
+
type=int,
|
|
168
|
+
default=19527,
|
|
169
|
+
help="HTTP server port (default: 19527)",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _add_bench_arguments(parser):
|
|
174
|
+
"""Add benchmark-specific arguments to a parser."""
|
|
175
|
+
parser.add_argument(
|
|
176
|
+
"--dbms",
|
|
177
|
+
required=True,
|
|
178
|
+
help="DBMS targets, comma-separated (e.g. tdsql,mysql)",
|
|
179
|
+
)
|
|
180
|
+
parser.add_argument(
|
|
181
|
+
"--file",
|
|
182
|
+
dest="bench_file",
|
|
183
|
+
help="Benchmark definition file (.json or .sql)",
|
|
184
|
+
)
|
|
185
|
+
parser.add_argument(
|
|
186
|
+
"--template",
|
|
187
|
+
help="Use a built-in template (e.g. oltp_read_write, oltp_read_only)",
|
|
188
|
+
)
|
|
189
|
+
parser.add_argument(
|
|
190
|
+
"--mode",
|
|
191
|
+
choices=["SERIAL", "CONCURRENT"],
|
|
192
|
+
default="SERIAL",
|
|
193
|
+
help="Execution mode: SERIAL or CONCURRENT (default: SERIAL)",
|
|
194
|
+
)
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"--database", "-d",
|
|
197
|
+
default="rosetta_bench_test",
|
|
198
|
+
help="Benchmark database name (default: rosetta_bench_test)",
|
|
199
|
+
)
|
|
200
|
+
parser.add_argument(
|
|
201
|
+
"--output-dir", "-o",
|
|
202
|
+
default="results",
|
|
203
|
+
help="Output directory for reports (default: results)",
|
|
204
|
+
)
|
|
205
|
+
parser.add_argument(
|
|
206
|
+
"--output-format", "-f",
|
|
207
|
+
default="all",
|
|
208
|
+
choices=["text", "html", "all"],
|
|
209
|
+
help="Report format (default: all)",
|
|
210
|
+
)
|
|
211
|
+
# Serial mode options
|
|
212
|
+
parser.add_argument(
|
|
213
|
+
"--iterations",
|
|
214
|
+
type=int,
|
|
215
|
+
default=1,
|
|
216
|
+
help="Number of iterations per query — serial mode (default: 1)",
|
|
217
|
+
)
|
|
218
|
+
# Concurrent mode options
|
|
219
|
+
parser.add_argument(
|
|
220
|
+
"--concurrency",
|
|
221
|
+
type=int,
|
|
222
|
+
default=10,
|
|
223
|
+
help="Number of concurrent threads — concurrent mode (default: 10)",
|
|
224
|
+
)
|
|
225
|
+
parser.add_argument(
|
|
226
|
+
"--duration",
|
|
227
|
+
type=float,
|
|
228
|
+
default=30.0,
|
|
229
|
+
help="Duration in seconds — concurrent mode (default: 30)",
|
|
230
|
+
)
|
|
231
|
+
# Shared options
|
|
232
|
+
parser.add_argument(
|
|
233
|
+
"--warmup",
|
|
234
|
+
type=int,
|
|
235
|
+
default=0,
|
|
236
|
+
help="Warmup iterations (serial) or warmup duration in seconds (concurrent) (default: 0)",
|
|
237
|
+
)
|
|
238
|
+
parser.add_argument(
|
|
239
|
+
"--ramp-up",
|
|
240
|
+
type=float,
|
|
241
|
+
default=0.0,
|
|
242
|
+
help="Ramp-up seconds — concurrent mode (default: 0)",
|
|
243
|
+
)
|
|
244
|
+
parser.add_argument(
|
|
245
|
+
"--query-timeout",
|
|
246
|
+
type=int,
|
|
247
|
+
default=5,
|
|
248
|
+
help="Query timeout in seconds (default: 5, 0 to disable)",
|
|
249
|
+
)
|
|
250
|
+
parser.add_argument(
|
|
251
|
+
"--bench-filter",
|
|
252
|
+
help="Run only queries matching these names (comma-separated)",
|
|
253
|
+
)
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"--repeat",
|
|
256
|
+
type=int,
|
|
257
|
+
default=1,
|
|
258
|
+
help="Number of benchmark rounds (default: 1)",
|
|
259
|
+
)
|
|
260
|
+
parser.add_argument(
|
|
261
|
+
"--skip-setup",
|
|
262
|
+
action="store_true",
|
|
263
|
+
default=False,
|
|
264
|
+
help="Skip setup phase (reuse existing tables)",
|
|
265
|
+
)
|
|
266
|
+
parser.add_argument(
|
|
267
|
+
"--skip-teardown",
|
|
268
|
+
action="store_true",
|
|
269
|
+
default=False,
|
|
270
|
+
help="Skip teardown (keep tables for next run)",
|
|
271
|
+
)
|
|
272
|
+
parser.add_argument(
|
|
273
|
+
"--no-parallel-dbms",
|
|
274
|
+
dest="parallel_dbms",
|
|
275
|
+
action="store_false",
|
|
276
|
+
help="Run DBMS targets sequentially instead of in parallel",
|
|
277
|
+
)
|
|
278
|
+
parser.set_defaults(parallel_dbms=True)
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"--profile",
|
|
281
|
+
action="store_true",
|
|
282
|
+
default=True,
|
|
283
|
+
help="Enable flame-graph capture (default: on)",
|
|
284
|
+
)
|
|
285
|
+
parser.add_argument(
|
|
286
|
+
"--no-profile",
|
|
287
|
+
action="store_false",
|
|
288
|
+
dest="profile",
|
|
289
|
+
help="Disable flame-graph capture",
|
|
290
|
+
)
|
|
291
|
+
parser.add_argument(
|
|
292
|
+
"--perf-freq",
|
|
293
|
+
type=int,
|
|
294
|
+
default=99,
|
|
295
|
+
help="perf sampling frequency in Hz (default: 99)",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# ---------------------------------------------------------------------------
|
|
300
|
+
# Subparser registration
|
|
301
|
+
# ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
def _add_mtr_subparser(subparsers):
|
|
304
|
+
"""Add the 'mtr' top-level subcommand."""
|
|
305
|
+
mtr_parser = subparsers.add_parser(
|
|
306
|
+
"mtr",
|
|
307
|
+
help="Run MTR consistency test",
|
|
308
|
+
description="Execute .test files and compare SQL results across databases",
|
|
309
|
+
)
|
|
310
|
+
_add_global_options(mtr_parser)
|
|
311
|
+
_add_mtr_arguments(mtr_parser)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _add_bench_subparser(subparsers):
|
|
315
|
+
"""Add the 'bench' top-level subcommand."""
|
|
316
|
+
bench_parser = subparsers.add_parser(
|
|
317
|
+
"bench",
|
|
318
|
+
help="Run performance benchmark",
|
|
319
|
+
description="Compare query performance across databases with custom workloads",
|
|
320
|
+
)
|
|
321
|
+
_add_global_options(bench_parser)
|
|
322
|
+
_add_bench_arguments(bench_parser)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _add_list_subparser(subparsers):
|
|
326
|
+
"""Add the 'list' subcommand."""
|
|
327
|
+
list_parser = subparsers.add_parser(
|
|
328
|
+
"list",
|
|
329
|
+
help="List resources (configs, history, templates)",
|
|
330
|
+
description="List databases, execution history, or benchmark templates",
|
|
331
|
+
)
|
|
332
|
+
list_parser.add_argument(
|
|
333
|
+
"resource",
|
|
334
|
+
nargs="?",
|
|
335
|
+
default="dbms",
|
|
336
|
+
choices=["dbms", "history", "templates"],
|
|
337
|
+
help="Resource to list: dbms (databases), history (runs), templates (benchmarks) (default: dbms)",
|
|
338
|
+
)
|
|
339
|
+
list_parser.add_argument(
|
|
340
|
+
"--limit",
|
|
341
|
+
type=int,
|
|
342
|
+
default=20,
|
|
343
|
+
help="Maximum number of items to show (default: 20)",
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _add_status_subparser(subparsers):
|
|
348
|
+
"""Add the 'status' subcommand."""
|
|
349
|
+
status_parser = subparsers.add_parser(
|
|
350
|
+
"status",
|
|
351
|
+
help="Check DBMS connection status",
|
|
352
|
+
description="Check connection status for all configured databases",
|
|
353
|
+
)
|
|
354
|
+
_add_global_options(status_parser)
|
|
355
|
+
status_parser.add_argument(
|
|
356
|
+
"--timeout",
|
|
357
|
+
type=int,
|
|
358
|
+
default=5,
|
|
359
|
+
help="Connection timeout in seconds (default: 5)",
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _add_exec_subparser(subparsers):
|
|
364
|
+
"""Add the 'exec' subcommand."""
|
|
365
|
+
exec_parser = subparsers.add_parser(
|
|
366
|
+
"exec",
|
|
367
|
+
help="Execute SQL statements",
|
|
368
|
+
description="Execute SQL statements on specified databases (CLI playground)",
|
|
369
|
+
)
|
|
370
|
+
_add_global_options(exec_parser)
|
|
371
|
+
exec_parser.add_argument(
|
|
372
|
+
"--sql",
|
|
373
|
+
help="SQL statement to execute",
|
|
374
|
+
)
|
|
375
|
+
exec_parser.add_argument(
|
|
376
|
+
"--file",
|
|
377
|
+
help="File containing SQL statements",
|
|
378
|
+
)
|
|
379
|
+
exec_parser.add_argument(
|
|
380
|
+
"--dbms",
|
|
381
|
+
help="DBMS targets, comma-separated (default: all from config)",
|
|
382
|
+
)
|
|
383
|
+
exec_parser.add_argument(
|
|
384
|
+
"--database", "-d",
|
|
385
|
+
help="Database name (default: from config)",
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _add_config_subparser(subparsers):
|
|
390
|
+
"""Add the 'config' subcommand."""
|
|
391
|
+
config_parser = subparsers.add_parser(
|
|
392
|
+
"config",
|
|
393
|
+
help="Manage configurations",
|
|
394
|
+
description="View, validate, or generate configuration files",
|
|
395
|
+
)
|
|
396
|
+
_add_global_options(config_parser)
|
|
397
|
+
config_parser.add_argument(
|
|
398
|
+
"action",
|
|
399
|
+
choices=["show", "validate", "init"],
|
|
400
|
+
help="Action: show (display config), validate (check config), init (generate sample)",
|
|
401
|
+
)
|
|
402
|
+
config_parser.add_argument(
|
|
403
|
+
"--output",
|
|
404
|
+
help="Output file path (for init action)",
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _add_result_subparser(subparsers):
|
|
409
|
+
"""Add the 'result' subcommand with sub-actions via subparsers."""
|
|
410
|
+
result_parser = subparsers.add_parser(
|
|
411
|
+
"result",
|
|
412
|
+
help="Manage execution results",
|
|
413
|
+
description="Browse, view, and export historical execution results",
|
|
414
|
+
)
|
|
415
|
+
_add_global_options(result_parser)
|
|
416
|
+
|
|
417
|
+
result_sub = result_parser.add_subparsers(dest="result_action")
|
|
418
|
+
|
|
419
|
+
# result list (also the default when no action given)
|
|
420
|
+
list_p = result_sub.add_parser(
|
|
421
|
+
"list", help="List historical runs",
|
|
422
|
+
description="Show a table of past MTR / bench runs",
|
|
423
|
+
)
|
|
424
|
+
_add_global_options(list_p)
|
|
425
|
+
list_p.add_argument(
|
|
426
|
+
"-n", "--limit", type=int, default=20,
|
|
427
|
+
help="Max rows per page (default: 20)",
|
|
428
|
+
)
|
|
429
|
+
list_p.add_argument(
|
|
430
|
+
"-p", "--page", type=int, default=1,
|
|
431
|
+
help="Page number (default: 1)",
|
|
432
|
+
)
|
|
433
|
+
list_p.add_argument(
|
|
434
|
+
"--type", choices=["all", "mtr", "bench"], default="all",
|
|
435
|
+
help="Filter by run type (default: all)",
|
|
436
|
+
)
|
|
437
|
+
list_p.add_argument(
|
|
438
|
+
"--output-dir", "-o", default="results",
|
|
439
|
+
help="Results directory (default: results)",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# result show <run_id>
|
|
443
|
+
show_p = result_sub.add_parser(
|
|
444
|
+
"show", help="Show details of a run",
|
|
445
|
+
description="Display detailed information for a specific run",
|
|
446
|
+
)
|
|
447
|
+
_add_global_options(show_p)
|
|
448
|
+
show_p.add_argument(
|
|
449
|
+
"run_id", nargs="?", default=None,
|
|
450
|
+
help="Run ID or path (default: latest)",
|
|
451
|
+
)
|
|
452
|
+
show_p.add_argument(
|
|
453
|
+
"--output-dir", "-o", default="results",
|
|
454
|
+
help="Results directory (default: results)",
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _add_interactive_subparser(subparsers):
|
|
459
|
+
"""Add the 'interactive' subcommand (with aliases 'repl' and 'i')."""
|
|
460
|
+
for name in ["interactive", "repl", "i"]:
|
|
461
|
+
interp_parser = subparsers.add_parser(
|
|
462
|
+
name,
|
|
463
|
+
help="Launch interactive REPL" + (" (alias)" if name != "interactive" else ""),
|
|
464
|
+
description="Start an interactive session for repeated test execution",
|
|
465
|
+
)
|
|
466
|
+
_add_global_options(interp_parser)
|
|
467
|
+
interp_parser.add_argument(
|
|
468
|
+
"--dbms",
|
|
469
|
+
help="DBMS targets, comma-separated (default: auto-detect reachable DBMS)",
|
|
470
|
+
)
|
|
471
|
+
interp_parser.add_argument(
|
|
472
|
+
"--database", "-d",
|
|
473
|
+
default="cross_dbms_test_db",
|
|
474
|
+
help="Test database name (default: cross_dbms_test_db)",
|
|
475
|
+
)
|
|
476
|
+
interp_parser.add_argument(
|
|
477
|
+
"--output-dir", "-o",
|
|
478
|
+
default="results",
|
|
479
|
+
help="Output directory for reports (default: results)",
|
|
480
|
+
)
|
|
481
|
+
interp_parser.add_argument(
|
|
482
|
+
"--serve", "-s",
|
|
483
|
+
action="store_true",
|
|
484
|
+
default=True,
|
|
485
|
+
help="Start a local HTTP server to view HTML reports (default: on)",
|
|
486
|
+
)
|
|
487
|
+
interp_parser.add_argument(
|
|
488
|
+
"--no-serve",
|
|
489
|
+
action="store_false",
|
|
490
|
+
dest="serve",
|
|
491
|
+
help="Do not start HTTP server",
|
|
492
|
+
)
|
|
493
|
+
interp_parser.add_argument(
|
|
494
|
+
"--port", "-p",
|
|
495
|
+
type=int,
|
|
496
|
+
default=19527,
|
|
497
|
+
help="HTTP server port (default: 19527)",
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
502
|
+
"""
|
|
503
|
+
Main entry point for the rosetta CLI.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
argv: Command-line arguments (default: sys.argv[1:])
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
Exit code (0 for success, non-zero for failure)
|
|
510
|
+
"""
|
|
511
|
+
if argv is None:
|
|
512
|
+
argv = sys.argv[1:]
|
|
513
|
+
|
|
514
|
+
parser = create_parser()
|
|
515
|
+
|
|
516
|
+
# Two-phase parse: first extract global flags that may appear before
|
|
517
|
+
# the subcommand (e.g. ``rosetta -j status``), then let the full
|
|
518
|
+
# parser handle everything. The subcommand parsers also accept
|
|
519
|
+
# the same flags, so ``rosetta status -j`` works too.
|
|
520
|
+
pre_args, _ = _global_preparser.parse_known_args(argv)
|
|
521
|
+
args = parser.parse_args(argv)
|
|
522
|
+
|
|
523
|
+
# Merge: if the flag was set in *either* position, honour it.
|
|
524
|
+
args.json = args.json or pre_args.json
|
|
525
|
+
args.verbose = args.verbose or pre_args.verbose
|
|
526
|
+
args.version = args.version or pre_args.version
|
|
527
|
+
if args.config is None:
|
|
528
|
+
args.config = pre_args.config if pre_args.config is not None else "dbms_config.json"
|
|
529
|
+
|
|
530
|
+
# Derive output format from -j/--json flag
|
|
531
|
+
fmt = "json" if args.json else "human"
|
|
532
|
+
output = OutputFormatter(format=fmt)
|
|
533
|
+
|
|
534
|
+
# Compatibility: keep `-v` as verbose for subcommands, but when used
|
|
535
|
+
# alone at top level treat it as a convenient version shortcut.
|
|
536
|
+
if not args.command and args.verbose and not args.version:
|
|
537
|
+
args.version = True
|
|
538
|
+
|
|
539
|
+
if args.version:
|
|
540
|
+
if args.json:
|
|
541
|
+
output.print(CommandResult.success(
|
|
542
|
+
"version",
|
|
543
|
+
{"name": "rosetta", "version": __version__},
|
|
544
|
+
))
|
|
545
|
+
else:
|
|
546
|
+
print(f"rosetta {__version__}")
|
|
547
|
+
return 0
|
|
548
|
+
|
|
549
|
+
# Configure logging - only show ERROR to console by default
|
|
550
|
+
log_level = logging.DEBUG if args.verbose else logging.ERROR
|
|
551
|
+
logging.basicConfig(
|
|
552
|
+
level=log_level,
|
|
553
|
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
554
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# No command provided — default to interactive mode
|
|
558
|
+
if not args.command:
|
|
559
|
+
from .interactive_cmd import handle_interactive
|
|
560
|
+
# Set default values for interactive mode
|
|
561
|
+
args.dbms = getattr(args, 'dbms', None)
|
|
562
|
+
args.database = getattr(args, 'database', 'cross_dbms_test_db')
|
|
563
|
+
args.output_dir = getattr(args, 'output_dir', 'results')
|
|
564
|
+
args.serve = getattr(args, 'serve', True)
|
|
565
|
+
args.port = getattr(args, 'port', 19527)
|
|
566
|
+
result = handle_interactive(args, output)
|
|
567
|
+
output.print(result)
|
|
568
|
+
return result.exit_code()
|
|
569
|
+
|
|
570
|
+
# Dispatch to command handlers
|
|
571
|
+
try:
|
|
572
|
+
if args.command == "mtr":
|
|
573
|
+
from .run import handle_mtr
|
|
574
|
+
result = handle_mtr(args, output)
|
|
575
|
+
elif args.command == "bench":
|
|
576
|
+
from .run import handle_bench
|
|
577
|
+
result = handle_bench(args, output)
|
|
578
|
+
elif args.command == "status":
|
|
579
|
+
from .status import handle_status
|
|
580
|
+
result = handle_status(args, output)
|
|
581
|
+
elif args.command == "exec":
|
|
582
|
+
from .exec import handle_exec
|
|
583
|
+
result = handle_exec(args, output)
|
|
584
|
+
elif args.command == "config":
|
|
585
|
+
from .config_cmd import handle_config
|
|
586
|
+
result = handle_config(args, output)
|
|
587
|
+
elif args.command == "result":
|
|
588
|
+
from .result_cmd import handle_result
|
|
589
|
+
result = handle_result(args, output)
|
|
590
|
+
elif args.command in ["interactive", "repl", "i"]:
|
|
591
|
+
from .interactive_cmd import handle_interactive
|
|
592
|
+
result = handle_interactive(args, output)
|
|
593
|
+
else:
|
|
594
|
+
result = CommandResult.failure(
|
|
595
|
+
f"Unknown command: {args.command}",
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
# Print result
|
|
599
|
+
output.print(result)
|
|
600
|
+
return result.exit_code()
|
|
601
|
+
|
|
602
|
+
except Exception as e:
|
|
603
|
+
error_result = CommandResult.failure(
|
|
604
|
+
f"Unexpected error: {str(e)}",
|
|
605
|
+
)
|
|
606
|
+
output.print(error_result)
|
|
607
|
+
if args.verbose:
|
|
608
|
+
import traceback
|
|
609
|
+
traceback.print_exc()
|
|
610
|
+
return 1
|
|
611
|
+
finally:
|
|
612
|
+
# Always restore cursor visibility
|
|
613
|
+
try:
|
|
614
|
+
from rich.console import Console
|
|
615
|
+
Console().show_cursor()
|
|
616
|
+
except Exception:
|
|
617
|
+
pass
|