rosetta-sql 1.0.3__tar.gz → 1.2.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 (58) hide show
  1. {rosetta_sql-1.0.3/rosetta_sql.egg-info → rosetta_sql-1.2.0}/PKG-INFO +1 -3
  2. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/pyproject.toml +1 -2
  3. rosetta_sql-1.2.0/rosetta/__init__.py +11 -0
  4. rosetta_sql-1.2.0/rosetta/__main__.py +6 -0
  5. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/exec.py +2 -2
  6. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/main.py +17 -16
  7. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/mtr_cmd.py +7 -14
  8. rosetta_sql-1.2.0/rosetta/cli/update_cmd.py +129 -0
  9. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/comparator.py +14 -5
  10. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/interactive.py +802 -4
  11. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/reporter/html.py +1 -0
  12. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/runner.py +384 -6
  13. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0/rosetta_sql.egg-info}/PKG-INFO +1 -3
  14. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta_sql.egg-info/SOURCES.txt +1 -0
  15. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta_sql.egg-info/requires.txt +0 -3
  16. rosetta_sql-1.0.3/rosetta/__init__.py +0 -3
  17. rosetta_sql-1.0.3/rosetta/__main__.py +0 -8
  18. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/LICENSE +0 -0
  19. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/README.md +0 -0
  20. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/benchmark.py +0 -0
  21. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/__init__.py +0 -0
  22. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/config_cmd.py +0 -0
  23. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/interactive_cmd.py +0 -0
  24. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/list_cmd.py +0 -0
  25. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/output.py +0 -0
  26. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/result.py +0 -0
  27. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/result_cmd.py +0 -0
  28. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/run.py +0 -0
  29. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/cli/status.py +0 -0
  30. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/config.py +0 -0
  31. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/executor.py +0 -0
  32. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/flamegraph.py +0 -0
  33. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/models.py +0 -0
  34. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/__init__.py +0 -0
  35. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/adapter.py +0 -0
  36. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/connection.py +0 -0
  37. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/error_handler.py +0 -0
  38. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/executor.py +0 -0
  39. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/nodes.py +0 -0
  40. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/parser.py +0 -0
  41. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/result_processor.py +0 -0
  42. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/mtr/variable.py +0 -0
  43. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/paths.py +0 -0
  44. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/reporter/__init__.py +0 -0
  45. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/reporter/bench_html.py +0 -0
  46. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/reporter/bench_text.py +0 -0
  47. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/reporter/history.py +0 -0
  48. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/reporter/text.py +0 -0
  49. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/serve.py +0 -0
  50. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta/ui.py +0 -0
  51. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta_sql.egg-info/dependency_links.txt +0 -0
  52. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta_sql.egg-info/entry_points.txt +0 -0
  53. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/rosetta_sql.egg-info/top_level.txt +0 -0
  54. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/setup.cfg +0 -0
  55. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/setup.py +0 -0
  56. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/skills/rosetta/scripts/install_rosetta.py +0 -0
  57. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/skills/rosetta/scripts/rosetta_wrapper.py +0 -0
  58. {rosetta_sql-1.0.3 → rosetta_sql-1.2.0}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosetta-sql
3
- Version: 1.0.3
3
+ Version: 1.2.0
4
4
  Summary: Cross-DBMS SQL behavioral consistency verification tool
5
5
  Author: yangshijie
6
6
  License: MIT
@@ -27,8 +27,6 @@ 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
- Provides-Extra: gcov
31
- Requires-Dist: fastcov>=1.13; extra == "gcov"
32
30
  Dynamic: license-file
33
31
 
34
32
  # Rosetta
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rosetta-sql"
7
- version = "1.0.3"
7
+ version = "1.2.0"
8
8
  description = "Cross-DBMS SQL behavioral consistency verification tool"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -34,7 +34,6 @@ dependencies = [
34
34
 
35
35
  [project.optional-dependencies]
36
36
  mysql-connector = ["mysql-connector-python>=8.0"]
37
- gcov = ["fastcov>=1.13"]
38
37
 
39
38
  [project.scripts]
40
39
  rosetta = "rosetta.cli:main"
@@ -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()
@@ -82,10 +82,10 @@ def handle_exec(args, output: "OutputFormatter") -> CommandResult:
82
82
  # so the MTR parser treats each as a separate statement.
83
83
  parts = [p.strip() for p in sql_text.split(";") if p.strip()]
84
84
  sql_text_for_parse = ";\n".join(parts) + ";\n"
85
- mtr_parser = MtrParser()
85
+ mtr_parser = MtrParser("<inline>")
86
86
  parsed = mtr_parser.parse_text(sql_text_for_parse)
87
87
  else:
88
- mtr_parser = MtrParser()
88
+ mtr_parser = MtrParser("<inline>")
89
89
  parsed = mtr_parser.parse_text(sql_text)
90
90
  statements = [cmd.argument for cmd in parsed.commands
91
91
  if cmd.cmd_type == MtrCommandType.SQL]
@@ -94,7 +94,8 @@ def create_parser() -> argparse.ArgumentParser:
94
94
  _add_config_subparser(subparsers)
95
95
  _add_result_subparser(subparsers)
96
96
  _add_interactive_subparser(subparsers)
97
-
97
+ _add_update_subparser(subparsers)
98
+
98
99
  return parser
99
100
 
100
101
 
@@ -192,27 +193,14 @@ def _add_mtr_arguments(parser):
192
193
  default=False,
193
194
  help="Enable optimistic transaction mode (--mysqld=--tdsql_trans_type=1)",
194
195
  )
195
- parser.add_argument(
196
- "-v", "--vector",
197
- action="store_true",
198
- default=False,
199
- help="Enable vector engine mode (--ve-protocol)",
200
- )
201
- parser.add_argument(
202
- "-pq", "--parallel-query",
203
- action="store_true",
204
- default=False,
205
- help="Enable parallel query mode (--parallel-query)",
206
- )
207
196
  parser.add_argument(
208
197
  "-m", "--mode",
209
198
  type=str,
210
199
  default=None,
211
200
  help="Run multiple MTR modes in parallel. "
212
- "Comma-separated list of: row (行存), col (列存/ve-protocol), pq (并行查询), "
201
+ "Comma-separated list of: row, col, pq, "
213
202
  "or 'all' (equivalent to row,col,pq). "
214
- "Example: --mode row,col,pq. "
215
- "When specified, --vector and --parallel-query flags are ignored.",
203
+ "Example: --mode row,col,pq",
216
204
  )
217
205
  parser.add_argument(
218
206
  "-r", "--record",
@@ -588,6 +576,16 @@ def _add_interactive_subparser(subparsers):
588
576
  )
589
577
 
590
578
 
579
+ def _add_update_subparser(subparsers):
580
+ """Add the 'update' subcommand."""
581
+ update_parser = subparsers.add_parser(
582
+ "update",
583
+ help="Update rosetta to the latest version",
584
+ description="Pull latest code from git and reinstall",
585
+ )
586
+ _add_global_options(update_parser)
587
+
588
+
591
589
  def main(argv: Optional[List[str]] = None) -> int:
592
590
  """
593
591
  Main entry point for the rosetta CLI.
@@ -687,6 +685,9 @@ def main(argv: Optional[List[str]] = None) -> int:
687
685
  elif args.command in ["interactive", "repl", "i"]:
688
686
  from .interactive_cmd import handle_interactive
689
687
  result = handle_interactive(args, output)
688
+ elif args.command == "update":
689
+ from .update_cmd import handle_update
690
+ result = handle_update(args, output)
690
691
  else:
691
692
  result = CommandResult.failure(
692
693
  f"Unknown command: {args.command}",
@@ -858,8 +858,6 @@ def _run_native_mtr(args, output: "OutputFormatter") -> CommandResult:
858
858
  "suite_timeout": file_cfg["suite_timeout"],
859
859
  "optimistic": False,
860
860
  "record": False,
861
- "vector": False,
862
- "parallel_query": False,
863
861
  "suite": None,
864
862
  "cases": [],
865
863
  }
@@ -900,8 +898,6 @@ def _run_native_mtr(args, output: "OutputFormatter") -> CommandResult:
900
898
 
901
899
  cfg["optimistic"] = getattr(args, "optimistic", False)
902
900
  cfg["record"] = getattr(args, "record", False)
903
- cfg["vector"] = getattr(args, "vector", False)
904
- cfg["parallel_query"] = getattr(args, "parallel_query", False)
905
901
  cfg["suite"] = getattr(args, "suite", None)
906
902
  cfg["cases"] = getattr(args, "cases", [])
907
903
 
@@ -948,16 +944,19 @@ def _run_native_mtr(args, output: "OutputFormatter") -> CommandResult:
948
944
  info_lines.append(f"[bold]Record[/bold] : ON")
949
945
  if cfg["optimistic"]:
950
946
  info_lines.append(f"[bold]Optimistic[/bold] : ON")
951
- if cfg["vector"]:
952
- info_lines.append(f"[bold]Vector[/bold] : ON")
953
- if cfg["parallel_query"]:
954
- info_lines.append(f"[bold]PQ[/bold] : ON")
955
947
  console_plan.print(_Panel(
956
948
  "\n".join(info_lines),
957
949
  title="[bold cyan]MTR Execution Plan[/bold cyan]",
958
950
  title_align="left",
959
951
  padding=(0, 1),
960
952
  ))
953
+ # Print actual MTR command
954
+ console_plan.print(_Panel(
955
+ f"[dim]{cmd}[/dim]",
956
+ title="[bold cyan]Command[/bold cyan]",
957
+ title_align="left",
958
+ padding=(0, 1),
959
+ ))
961
960
 
962
961
  # --- 4. Execute MTR ---
963
962
  original_dir = os.getcwd()
@@ -965,10 +964,6 @@ def _run_native_mtr(args, output: "OutputFormatter") -> CommandResult:
965
964
 
966
965
  # Build mode label for progress display
967
966
  mode_parts = []
968
- if cfg["vector"]:
969
- mode_parts.append("ve-protocol")
970
- if cfg["parallel_query"]:
971
- mode_parts.append("parallel-query")
972
967
  if cfg["optimistic"]:
973
968
  mode_parts.append("optimistic")
974
969
  mode_label = "+".join(mode_parts) if mode_parts else "row (default)"
@@ -1137,8 +1132,6 @@ def _run_native_mtr(args, output: "OutputFormatter") -> CommandResult:
1137
1132
  "cases": cfg["cases"],
1138
1133
  "record": cfg["record"],
1139
1134
  "optimistic": cfg["optimistic"],
1140
- "vector": cfg["vector"],
1141
- "parallel_query": cfg["parallel_query"],
1142
1135
  "exit_code": exit_code,
1143
1136
  "elapsed_seconds": total_elapsed,
1144
1137
  "log_dir": log_dir,
@@ -0,0 +1,129 @@
1
+ """Handler for 'rosetta update' — self-update to latest PyPI version."""
2
+
3
+ import json
4
+ import subprocess
5
+ import sys
6
+ import urllib.request
7
+ from importlib.metadata import version
8
+
9
+ from .result import CommandResult
10
+
11
+ _PYPI_JSON_URL = "https://pypi.org/pypi/rosetta-sql/json"
12
+ _PACKAGE_NAME = "rosetta-sql"
13
+
14
+
15
+ def handle_update(args, output) -> CommandResult:
16
+ """Check for updates and upgrade rosetta to the latest PyPI release.
17
+
18
+ Steps:
19
+ 1. Query PyPI JSON API for latest version
20
+ 2. Compare with locally installed version
21
+ 3. If behind → pip install --upgrade rosetta-sql
22
+ 4. If up-to-date → inform user
23
+ """
24
+ is_json = getattr(args, "json", False)
25
+
26
+ # Step 1: Get local version
27
+ local_ver = _get_local_version()
28
+
29
+ # Step 2: Get remote (PyPI) latest version
30
+ if not is_json:
31
+ from rich.console import Console
32
+ Console().print(" [dim]Checking for updates...[/dim]")
33
+ pypi_info = _fetch_pypi_info()
34
+ if pypi_info is None:
35
+ return CommandResult.failure(
36
+ "Failed to fetch version info from PyPI. Check your network.",
37
+ )
38
+ remote_ver = pypi_info["info"]["version"]
39
+
40
+ # Step 3: Compare
41
+ if _version_tuple(local_ver) >= _version_tuple(remote_ver):
42
+ if is_json:
43
+ return CommandResult.success(
44
+ "update",
45
+ {"status": "up_to_date", "version": local_ver,
46
+ "message": f"Already at latest version ({local_ver})"},
47
+ )
48
+ else:
49
+ from rich.console import Console
50
+ Console().print(
51
+ f"\n [green bold]✓[/green bold] Already up to date: "
52
+ f"rosetta {local_ver}\n",
53
+ )
54
+ return CommandResult.success("update")
55
+
56
+ # Step 4: Upgrade
57
+ if not is_json:
58
+ from rich.console import Console
59
+ console = Console()
60
+ console.print(f"\n [yellow]Update available:[/yellow]")
61
+ console.print(f" Current : {local_ver}")
62
+ console.print(f" Latest : {remote_ver}")
63
+ console.print()
64
+ Console().print(" [dim]Upgrading...[/dim]")
65
+
66
+ result = subprocess.run(
67
+ [sys.executable, "-m", "pip", "install", "--upgrade",
68
+ _PACKAGE_NAME, "--quiet"],
69
+ capture_output=True,
70
+ timeout=120,
71
+ )
72
+ if result.returncode != 0:
73
+ return CommandResult.failure(
74
+ f"pip install failed:\n{result.stderr.decode(errors='replace')}",
75
+ )
76
+
77
+ final_ver = _get_local_version()
78
+ if is_json:
79
+ return CommandResult.success(
80
+ "update",
81
+ {
82
+ "status": "updated",
83
+ "old_version": local_ver,
84
+ "new_version": final_ver,
85
+ },
86
+ )
87
+ else:
88
+ from rich.console import Console
89
+ Console().print(
90
+ f"\n [green bold]✓[/green bold] Updated to "
91
+ f"[bold cyan]{final_ver}[/bold cyan]\n",
92
+ )
93
+ return CommandResult.success("update")
94
+
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # Helpers
98
+ # ---------------------------------------------------------------------------
99
+
100
+ def _get_local_version() -> str:
101
+ try:
102
+ return version(_PACKAGE_NAME)
103
+ except Exception:
104
+ return "unknown"
105
+
106
+
107
+ def _fetch_pypi_info():
108
+ """Query PyPI JSON API. Returns parsed dict or None on failure."""
109
+ try:
110
+ req = urllib.request.Request(_PYPI_JSON_URL, headers={"Accept": "application/json"})
111
+ with urllib.request.urlopen(req, timeout=15) as resp:
112
+ return json.loads(resp.read().decode())
113
+ except Exception:
114
+ return None
115
+
116
+
117
+ def _version_tuple(v: str) -> tuple:
118
+ """Parse '1.2.3' into comparable tuple of ints."""
119
+ parts = []
120
+ for part in v.split("."):
121
+ # Handle suffixes like .dev1, .a1, .rc1 — treat as 0
122
+ num = ""
123
+ for ch in part:
124
+ if ch.isdigit():
125
+ num += ch
126
+ else:
127
+ break
128
+ parts.append(int(num) if num else 0)
129
+ return tuple(parts) if parts else (0,)
@@ -13,7 +13,12 @@ _RE_ENGINE = re.compile(r"ENGINE\s*=\s*\w+")
13
13
  _RE_CHARSET_COLLATE = re.compile(
14
14
  r"DEFAULT CHARSET=\w+(\s+COLLATE=\w+)?"
15
15
  )
16
- _RE_ERROR_LINE = re.compile(r"^ERROR\b[^(]*\((\d+),")
16
+ _RE_ERROR_LINE = re.compile(r"^ERROR\b(?:\s*(\d+)|\D*\((\d+)[,:\)])")
17
+ # OceanBase appends trace_id lines after errors like [IP:PORT] [timestamp] or [YB4Z...]
18
+ _RE_OB_TRACE = re.compile(
19
+ r"^\[\d+\.\d+\.\d+\.\d+:\d+\]" # [IP:PORT]
20
+ r"|\[[A-Z0-9]{8,}(-[\w]+){2,}\]" # [OB-trace-id]
21
+ )
17
22
  _RE_AUTO_INCREMENT = re.compile(r"\s*AUTO_INCREMENT=\d+")
18
23
  _RE_ROW_FORMAT = re.compile(r"\s*ROW_FORMAT=\w+")
19
24
  _RE_STATS_PERSISTENT = re.compile(r"\s*STATS_PERSISTENT=\d+")
@@ -30,14 +35,16 @@ def normalize_line(line: str) -> str:
30
35
  """Normalize a single output line to ignore known non-functional diffs.
31
36
 
32
37
  Handled cases:
33
- - ERROR lines: only keep error code
38
+ - ERROR lines: only keep error code (supports both MTR "ERROR(code,"
39
+ and MySQL-style "ERROR 1662:" formats)
34
40
  - TDSQL tail (txid, sql-node, error-store-node)
35
41
  - ENGINE=, CHARSET=, AUTO_INCREMENT=, ROW_FORMAT=, DEFINER=, etc.
36
42
  """
37
43
  s = line
38
44
  m = _RE_ERROR_LINE.match(s)
39
45
  if m:
40
- return f"ERROR: ({m.group(1)})"
46
+ code = m.group(1) or m.group(2)
47
+ return f"ERROR: ({code})"
41
48
  if s.startswith("ERROR"):
42
49
  return "ERROR: (unknown)"
43
50
  s = _RE_TDSQL_TAIL.sub("", s)
@@ -53,12 +60,14 @@ def normalize_line(line: str) -> str:
53
60
  def normalize_block(block: List[str]) -> List[str]:
54
61
  """Normalize all lines in a block for comparison.
55
62
 
56
- Filters out Warning lines and the "Warnings:" header.
63
+ Filters out Warning lines, the "Warnings:" header, and OceanBase
64
+ trace_id appendages that appear after error messages.
57
65
  """
58
66
  lines = [normalize_line(l) for l in block]
59
67
  return [l for l in lines
60
68
  if l.strip() != "Warnings:"
61
- and not _RE_WARNING_LINE.match(l.strip())]
69
+ and not _RE_WARNING_LINE.match(l.strip())
70
+ and not _RE_OB_TRACE.match(l.strip())]
62
71
 
63
72
 
64
73
  def filter_warnings(block: List[str]) -> List[str]: