rosetta-sql 1.0.2__tar.gz → 1.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.
Files changed (65) hide show
  1. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/PKG-INFO +7 -6
  2. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/README.md +4 -4
  3. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/pyproject.toml +1 -1
  4. rosetta_sql-1.1.0/rosetta/__init__.py +11 -0
  5. rosetta_sql-1.1.0/rosetta/__main__.py +6 -0
  6. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/config_cmd.py +29 -8
  7. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/exec.py +22 -5
  8. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/interactive_cmd.py +10 -18
  9. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/list_cmd.py +5 -43
  10. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/main.py +174 -77
  11. rosetta_sql-1.1.0/rosetta/cli/mtr_cmd.py +1160 -0
  12. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/output.py +169 -75
  13. rosetta_sql-1.1.0/rosetta/cli/result.py +101 -0
  14. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/result_cmd.py +22 -9
  15. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/run.py +283 -192
  16. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/status.py +3 -1
  17. rosetta_sql-1.1.0/rosetta/comparator.py +340 -0
  18. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/config.py +29 -18
  19. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/executor.py +134 -6
  20. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/interactive.py +341 -208
  21. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/models.py +5 -16
  22. rosetta_sql-1.1.0/rosetta/mtr/__init__.py +42 -0
  23. rosetta_sql-1.1.0/rosetta/mtr/adapter.py +299 -0
  24. rosetta_sql-1.1.0/rosetta/mtr/connection.py +284 -0
  25. rosetta_sql-1.1.0/rosetta/mtr/error_handler.py +316 -0
  26. rosetta_sql-1.1.0/rosetta/mtr/executor.py +1070 -0
  27. rosetta_sql-1.1.0/rosetta/mtr/nodes.py +383 -0
  28. rosetta_sql-1.1.0/rosetta/mtr/parser.py +1158 -0
  29. rosetta_sql-1.1.0/rosetta/mtr/result_processor.py +384 -0
  30. rosetta_sql-1.1.0/rosetta/mtr/variable.py +335 -0
  31. rosetta_sql-1.1.0/rosetta/paths.py +53 -0
  32. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/reporter/bench_html.py +11 -3
  33. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/reporter/history.py +230 -529
  34. rosetta_sql-1.1.0/rosetta/reporter/html.py +1136 -0
  35. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/reporter/text.py +13 -6
  36. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/runner.py +522 -320
  37. rosetta_sql-1.1.0/rosetta/serve.py +65 -0
  38. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/ui.py +14 -27
  39. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta_sql.egg-info/PKG-INFO +7 -6
  40. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta_sql.egg-info/SOURCES.txt +12 -5
  41. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta_sql.egg-info/top_level.txt +1 -3
  42. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/skills/rosetta/scripts/rosetta_wrapper.py +1 -1
  43. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/tests/test_cli.py +19 -19
  44. rosetta_sql-1.0.2/benchmark/generate_csv_data.py +0 -83
  45. rosetta_sql-1.0.2/benchmark/import_data.py +0 -168
  46. rosetta_sql-1.0.2/rosetta/__init__.py +0 -3
  47. rosetta_sql-1.0.2/rosetta/__main__.py +0 -8
  48. rosetta_sql-1.0.2/rosetta/buglist.py +0 -108
  49. rosetta_sql-1.0.2/rosetta/cli/result.py +0 -61
  50. rosetta_sql-1.0.2/rosetta/comparator.py +0 -205
  51. rosetta_sql-1.0.2/rosetta/parser.py +0 -308
  52. rosetta_sql-1.0.2/rosetta/reporter/html.py +0 -644
  53. rosetta_sql-1.0.2/rosetta/whitelist.py +0 -161
  54. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/LICENSE +0 -0
  55. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/benchmark.py +0 -0
  56. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/cli/__init__.py +0 -0
  57. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/flamegraph.py +0 -0
  58. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/reporter/__init__.py +0 -0
  59. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta/reporter/bench_text.py +0 -0
  60. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta_sql.egg-info/dependency_links.txt +0 -0
  61. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta_sql.egg-info/entry_points.txt +0 -0
  62. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/rosetta_sql.egg-info/requires.txt +0 -0
  63. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/setup.cfg +0 -0
  64. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/setup.py +0 -0
  65. {rosetta_sql-1.0.2 → rosetta_sql-1.1.0}/skills/rosetta/scripts/install_rosetta.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: rosetta-sql
3
- Version: 1.0.2
3
+ Version: 1.1.0
4
4
  Summary: Cross-DBMS SQL behavioral consistency verification tool
5
5
  Author: yangshijie
6
6
  License: MIT
@@ -27,6 +27,7 @@ Requires-Dist: rich>=13.0
27
27
  Requires-Dist: prompt_toolkit>=3.0.52
28
28
  Provides-Extra: mysql-connector
29
29
  Requires-Dist: mysql-connector-python>=8.0; extra == "mysql-connector"
30
+ Dynamic: license-file
30
31
 
31
32
  # Rosetta
32
33
  Cross-DBMS SQL testing & benchmarking toolkit.
@@ -86,7 +87,7 @@ curl -fsSL https://raw.githubusercontent.com/sjyango/rosetta/main/uninstall.sh |
86
87
  rosetta config init
87
88
 
88
89
  # 2. Edit DB connection info
89
- vim dbms_config.json
90
+ vim rosetta_config.json
90
91
 
91
92
  # 3. Check DB connectivity
92
93
  rosetta status
@@ -118,7 +119,7 @@ All commands support these flags (can appear before or after the subcommand):
118
119
  | Argument | Default | Description |
119
120
  |----------|---------|-------------|
120
121
  | `-j / --json` | `False` | JSON output (AI Agent friendly) |
121
- | `-c / --config` | `dbms_config.json` | DBMS config file path |
122
+ | `-c / --config` | `rosetta_config.json` | DBMS config file path |
122
123
  | `-v / --verbose` | `False` | Enable verbose/debug logging |
123
124
 
124
125
  ### Commands
@@ -272,7 +273,7 @@ rosetta config validate
272
273
 
273
274
  | Action | Description |
274
275
  |--------|-------------|
275
- | `init` | Generate a sample `dbms_config.sample.json` |
276
+ | `init` | Generate a sample `rosetta_config.sample.json` |
276
277
  | `show` | Display current config details |
277
278
  | `validate` | Validate JSON structure and test connectivity |
278
279
 
@@ -340,7 +341,7 @@ rosetta i --serve
340
341
  ---
341
342
 
342
343
  ## Configuration
343
- Sample `dbms_config.json`:
344
+ Sample `rosetta_config.json`:
344
345
  ```json
345
346
  {
346
347
  "databases": [
@@ -56,7 +56,7 @@ curl -fsSL https://raw.githubusercontent.com/sjyango/rosetta/main/uninstall.sh |
56
56
  rosetta config init
57
57
 
58
58
  # 2. Edit DB connection info
59
- vim dbms_config.json
59
+ vim rosetta_config.json
60
60
 
61
61
  # 3. Check DB connectivity
62
62
  rosetta status
@@ -88,7 +88,7 @@ All commands support these flags (can appear before or after the subcommand):
88
88
  | Argument | Default | Description |
89
89
  |----------|---------|-------------|
90
90
  | `-j / --json` | `False` | JSON output (AI Agent friendly) |
91
- | `-c / --config` | `dbms_config.json` | DBMS config file path |
91
+ | `-c / --config` | `rosetta_config.json` | DBMS config file path |
92
92
  | `-v / --verbose` | `False` | Enable verbose/debug logging |
93
93
 
94
94
  ### Commands
@@ -242,7 +242,7 @@ rosetta config validate
242
242
 
243
243
  | Action | Description |
244
244
  |--------|-------------|
245
- | `init` | Generate a sample `dbms_config.sample.json` |
245
+ | `init` | Generate a sample `rosetta_config.sample.json` |
246
246
  | `show` | Display current config details |
247
247
  | `validate` | Validate JSON structure and test connectivity |
248
248
 
@@ -310,7 +310,7 @@ rosetta i --serve
310
310
  ---
311
311
 
312
312
  ## Configuration
313
- Sample `dbms_config.json`:
313
+ Sample `rosetta_config.json`:
314
314
  ```json
315
315
  {
316
316
  "databases": [
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rosetta-sql"
7
- version = "1.0.2"
7
+ version = "1.1.0"
8
8
  description = "Cross-DBMS SQL behavioral consistency verification tool"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,11 @@
1
+ """Rosetta — Cross-DBMS SQL behavioral consistency verification tool."""
2
+
3
+ from importlib.metadata import version as _get_version
4
+
5
+ try:
6
+ __version__ = _get_version("rosetta-sql")
7
+ except Exception:
8
+ # Fallback for editable install / development mode
9
+ import tomllib
10
+ with open(__file__ + "/../pyproject.toml", "rb") as _f:
11
+ __version__ = tomllib.load(_f)["project"]["version"]
@@ -0,0 +1,6 @@
1
+ """Allow running as: python -m rosetta"""
2
+
3
+ from .cli.main import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -50,7 +50,9 @@ def _handle_config_show(args, output: "OutputFormatter") -> CommandResult:
50
50
 
51
51
  if not os.path.isfile(args.config):
52
52
  return CommandResult.failure(
53
- f"Config file not found: {args.config}",
53
+ f"Config file not found: {args.config}\n"
54
+ f"Run 'rosetta config init' to create a sample config, "
55
+ f"or use '-c' to specify the config file path.",
54
56
  )
55
57
 
56
58
  try:
@@ -103,7 +105,9 @@ def _handle_config_validate(args, output: "OutputFormatter") -> CommandResult:
103
105
 
104
106
  if not os.path.isfile(args.config):
105
107
  return CommandResult.failure(
106
- f"Config file not found: {args.config}",
108
+ f"Config file not found: {args.config}\n"
109
+ f"Run 'rosetta config init' to create a sample config, "
110
+ f"or use '-c' to specify the config file path.",
107
111
  )
108
112
 
109
113
  errors = []
@@ -204,7 +208,10 @@ def _handle_config_validate(args, output: "OutputFormatter") -> CommandResult:
204
208
 
205
209
  def _handle_config_init(args, output: "OutputFormatter") -> CommandResult:
206
210
  """
207
- Generate sample configuration file.
211
+ Initialize ~/.rosetta directory and generate sample config.
212
+
213
+ Creates the ~/.rosetta/ directory structure and generates a sample
214
+ config.json if it doesn't already exist.
208
215
 
209
216
  Args:
210
217
  args: Parsed arguments
@@ -214,30 +221,44 @@ def _handle_config_init(args, output: "OutputFormatter") -> CommandResult:
214
221
  CommandResult with generated config path
215
222
  """
216
223
  from ..config import generate_sample_config
224
+ from ..paths import CONFIG_FILE, ensure_home
217
225
 
218
226
  # Determine output path
219
- output_path = args.output if args.output else "dbms_config.sample.json"
227
+ output_path = args.output if args.output else CONFIG_FILE
228
+
229
+ # Ensure ~/.rosetta directory exists
230
+ home = ensure_home()
220
231
 
221
232
  # Check if file already exists
222
233
  if os.path.isfile(output_path):
234
+ # Preserve the original command name: "init" when called via
235
+ # ``rosetta init``, "config init" when called via ``rosetta config init``
236
+ command = getattr(args, 'command', None)
237
+ cmd_name = "init" if command == "init" else "config init"
223
238
  return CommandResult.failure(
224
- f"File already exists: {output_path}. Use --output to specify a different path",
225
- command="config init",
239
+ f"Config already exists: {output_path}. "
240
+ f"Edit it directly or use --output to specify a different path.",
241
+ command=cmd_name,
226
242
  )
227
243
 
228
244
  # Generate sample config
229
245
  try:
230
246
  generate_sample_config(output_path)
231
247
  except Exception as e:
248
+ command = getattr(args, 'command', None)
249
+ cmd_name = "init" if command == "init" else "config init"
232
250
  return CommandResult.failure(
233
251
  f"Failed to generate config: {str(e)}",
234
- command="config init",
252
+ command=cmd_name,
235
253
  )
236
254
 
237
255
  return CommandResult.success(
238
256
  "config init",
239
257
  {
258
+ "rosetta_home": home,
240
259
  "config_path": os.path.abspath(output_path),
241
- "message": f"Sample config written to {output_path}",
260
+ "message": f"Initialized {home}\n"
261
+ f"Config written to {output_path}\n"
262
+ f"Edit the database connections, then run: rosetta status",
242
263
  },
243
264
  )
@@ -26,12 +26,15 @@ def handle_exec(args, output: "OutputFormatter") -> CommandResult:
26
26
  import time as _time
27
27
  from ..config import load_config, filter_configs
28
28
  from ..executor import DBConnection, check_port
29
- from ..parser import TestFileParser
29
+ from ..mtr.parser import MtrParser
30
+ from ..mtr.nodes import MtrCommandType
30
31
 
31
32
  # Load config
32
33
  if not os.path.isfile(args.config):
33
34
  return CommandResult.failure(
34
- f"Config file not found: {args.config}",
35
+ f"Config file not found: {args.config}\n"
36
+ f"Run 'rosetta config init' to create a sample config, "
37
+ f"or use '-c' to specify the config file path.",
35
38
  )
36
39
 
37
40
  all_configs = load_config(args.config)
@@ -65,13 +68,27 @@ def handle_exec(args, output: "OutputFormatter") -> CommandResult:
65
68
  sql_text = f.read()
66
69
  else:
67
70
  return CommandResult.failure(
68
- "Either --sql or --file is required",
71
+ "Either --sql or --file is required. "
72
+ "Use --sql 'SELECT ...' or --file /path/to/script.sql",
69
73
  )
70
74
 
71
75
  # Parse SQL statements
76
+ # For --sql mode, split on semicolons first so that
77
+ # "SELECT 1; SELECT 2" is treated as two statements.
78
+ # The MTR parser is line-based and won't split in-line semicolons.
72
79
  try:
73
- parsed = TestFileParser.parse_text(sql_text)
74
- statements = [s.text for s in parsed]
80
+ if args.sql and ";" in sql_text:
81
+ # Split by semicolons, filter empty, and re-join with newlines
82
+ # so the MTR parser treats each as a separate statement.
83
+ parts = [p.strip() for p in sql_text.split(";") if p.strip()]
84
+ sql_text_for_parse = ";\n".join(parts) + ";\n"
85
+ mtr_parser = MtrParser()
86
+ parsed = mtr_parser.parse_text(sql_text_for_parse)
87
+ else:
88
+ mtr_parser = MtrParser()
89
+ parsed = mtr_parser.parse_text(sql_text)
90
+ statements = [cmd.argument for cmd in parsed.commands
91
+ if cmd.cmd_type == MtrCommandType.SQL]
75
92
  except Exception as e:
76
93
  return CommandResult.failure(f"Parse error: {str(e)}")
77
94
 
@@ -31,7 +31,9 @@ def handle_interactive(args, output: "OutputFormatter") -> CommandResult:
31
31
  # Load config
32
32
  if not os.path.isfile(args.config):
33
33
  return CommandResult.failure(
34
- f"Config file not found: {args.config}",
34
+ f"Config file not found: {args.config}\n"
35
+ f"Run 'rosetta config init' to create a sample config, "
36
+ f"or use '-c' to specify the config file path.",
35
37
  )
36
38
 
37
39
  all_configs = load_config(args.config)
@@ -55,7 +57,7 @@ def handle_interactive(args, output: "OutputFormatter") -> CommandResult:
55
57
 
56
58
  if not reachable_configs:
57
59
  return CommandResult.failure(
58
- "No reachable DBMS found. Check your dbms_config.json"
60
+ "No reachable DBMS found. Check your ~/.rosetta/config.json"
59
61
  )
60
62
 
61
63
  configs = reachable_configs
@@ -75,7 +77,7 @@ def handle_interactive(args, output: "OutputFormatter") -> CommandResult:
75
77
  "dbms_targets": [c.name for c in configs],
76
78
  "database": args.database,
77
79
  "output_dir": os.path.abspath(args.output_dir),
78
- "serve": args.serve,
80
+ "serve": True,
79
81
  "port": args.port,
80
82
  },
81
83
  )
@@ -95,29 +97,19 @@ def handle_interactive(args, output: "OutputFormatter") -> CommandResult:
95
97
 
96
98
  # Use filtered configs (either user-specified or auto-detected reachable)
97
99
  legacy_args.dbms = ",".join(c.name for c in configs)
98
- if args.serve:
99
- legacy_args.serve = args.serve
100
100
  if args.port:
101
101
  legacy_args.port = args.port
102
102
 
103
+ # serve is always on for interactive mode
104
+ legacy_args.serve = True
105
+
103
106
  # Launch interactive session
104
107
  exit_code = _enter_interactive(legacy_args)
105
108
 
106
- return CommandResult.success(
107
- "interactive",
108
- {
109
- "exit_code": exit_code,
110
- "message": "Interactive session ended",
111
- },
112
- )
109
+ return CommandResult.success("interactive")
113
110
 
114
111
  except KeyboardInterrupt:
115
- return CommandResult.success(
116
- "interactive",
117
- {
118
- "message": "Interactive session interrupted",
119
- },
120
- )
112
+ return CommandResult.success("interactive")
121
113
  except Exception as e:
122
114
  return CommandResult.failure(
123
115
  f"Interactive session failed: {str(e)}",
@@ -26,8 +26,6 @@ def handle_list(args, output: "OutputFormatter") -> CommandResult:
26
26
  return _handle_list_dbms(args, output)
27
27
  elif args.resource == "history":
28
28
  return _handle_list_history(args, output)
29
- elif args.resource == "templates":
30
- return _handle_list_templates(args, output)
31
29
  else:
32
30
  return CommandResult.failure(
33
31
  f"Unknown list resource: {args.resource}",
@@ -51,7 +49,9 @@ def _handle_list_dbms(args, output: "OutputFormatter") -> CommandResult:
51
49
  # Load config
52
50
  if not os.path.isfile(args.config):
53
51
  return CommandResult.failure(
54
- f"Config file not found: {args.config}",
52
+ f"Config file not found: {args.config}\n"
53
+ f"Run 'rosetta config init' to create a sample config, "
54
+ f"or use '-c' to specify the config file path.",
55
55
  )
56
56
 
57
57
  all_configs = load_config(args.config)
@@ -107,7 +107,8 @@ def _handle_list_history(args, output: "OutputFormatter") -> CommandResult:
107
107
  import json
108
108
  from pathlib import Path
109
109
 
110
- output_dir = args.output_dir if hasattr(args, "output_dir") else "results"
110
+ from ..paths import RESULTS_DIR as _DEFAULT_RESULTS
111
+ output_dir = args.output_dir if hasattr(args, "output_dir") else _DEFAULT_RESULTS
111
112
 
112
113
  if not os.path.isdir(output_dir):
113
114
  return CommandResult.failure(
@@ -173,43 +174,4 @@ def _handle_list_history(args, output: "OutputFormatter") -> CommandResult:
173
174
  )
174
175
 
175
176
 
176
- def _handle_list_templates(args, output: "OutputFormatter") -> CommandResult:
177
- """
178
- List built-in benchmark templates.
179
-
180
- Args:
181
- args: Parsed arguments
182
- output: Output formatter
183
-
184
- Returns:
185
- CommandResult with template list
186
- """
187
- from ..benchmark import BenchmarkLoader
188
-
189
- templates = BenchmarkLoader.list_builtin_templates()
190
-
191
- template_info = []
192
- for name in templates:
193
- info = {
194
- "name": name,
195
- "description": _get_template_description(name),
196
- }
197
- template_info.append(info)
198
-
199
- return CommandResult.success(
200
- "list templates",
201
- {
202
- "total": len(templates),
203
- "templates": template_info,
204
- },
205
- )
206
-
207
177
 
208
- def _get_template_description(name: str) -> str:
209
- """Get description for a built-in template."""
210
- descriptions = {
211
- "oltp_read_write": "OLTP Read-Write mixed workload with point selects, updates, inserts, and aggregates",
212
- "oltp_read_only": "OLTP Read-Only workload with point selects, range scans, and aggregates",
213
- "oltp_write_only": "OLTP Write-Only workload with inserts, updates, replaces, and deletes",
214
- }
215
- return descriptions.get(name, "Built-in benchmark template")