Skip to content

Commit 2e7b4f6

Browse files
committed
db 실행 버그 수정
1 parent eb4838d commit 2e7b4f6

8 files changed

Lines changed: 436 additions & 27 deletions

File tree

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#!/usr/bin/env python3
2+
"""Compare production result grids before and after SQL auto-formatting."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
from pathlib import Path
8+
import re
9+
10+
import verify_mysql_cli as mysql
11+
import verify_oracle_cli as oracle
12+
13+
14+
LAYOUTS = ("wrapped", "stacked")
15+
SQL_DEFINITION_QUERY = re.compile(
16+
r"\s*SHOW\s+(?:CREATE\b|EVENTS\b|TRIGGERS\b)", re.IGNORECASE
17+
)
18+
SQL_TOKEN = re.compile(
19+
r"""
20+
'(?:''|\\.|[^'])*'
21+
| "(?:""|\\.|[^"])*"
22+
| `(?:``|\\.|[^`])*`
23+
| [A-Za-z_][$#A-Za-z0-9_]*
24+
| \d+(?:\.\d+)?
25+
| <=>|<=|>=|<>|!=|:=|<<|>>|&&|\|\|
26+
| \S
27+
""",
28+
re.VERBOSE | re.DOTALL,
29+
)
30+
31+
32+
def normalized_mysql_rows(grid: dict) -> list[list[str]]:
33+
rows = grid["rows"]
34+
sql = grid["sql"]
35+
if sql.strip().upper().startswith("SHOW PROCESSLIST"):
36+
rows = mysql.active_processlist_rows(grid["columns"], rows)
37+
if re.match(r"\s*SHOW\s+OPEN\s+TABLES\b", sql, re.IGNORECASE):
38+
rows = sorted(rows)
39+
return rows
40+
41+
42+
def sql_definition_cells_match(
43+
sql: str, before: str, after: str
44+
) -> bool:
45+
if SQL_DEFINITION_QUERY.match(sql) is None:
46+
return False
47+
before_datetimes = mysql.EMBEDDED_DATETIME.findall(before)
48+
after_datetimes = mysql.EMBEDDED_DATETIME.findall(after)
49+
if len(before_datetimes) != len(after_datetimes):
50+
return False
51+
for before_value, after_value in zip(
52+
before_datetimes, after_datetimes, strict=True
53+
):
54+
before_time = mysql.parse_datetime(before_value)
55+
after_time = mysql.parse_datetime(after_value)
56+
if (
57+
before_time is None
58+
or after_time is None
59+
or abs((before_time - after_time).total_seconds()) > 600
60+
):
61+
return False
62+
before = mysql.EMBEDDED_DATETIME.sub("<datetime>", before)
63+
after = mysql.EMBEDDED_DATETIME.sub("<datetime>", after)
64+
return SQL_TOKEN.findall(before) == SQL_TOKEN.findall(after)
65+
66+
67+
def compare(
68+
label: str,
69+
path: Path,
70+
baseline: dict,
71+
formatted: dict,
72+
oracle_mode: bool,
73+
) -> tuple[int, int, int, int]:
74+
baseline_grids = baseline["grids"]
75+
formatted_grids = formatted["grids"]
76+
if len(baseline_grids) != len(formatted_grids):
77+
raise AssertionError(
78+
f"{path}: {label} grid count differs: "
79+
f"before={len(baseline_grids)}, after={len(formatted_grids)}"
80+
)
81+
82+
cell_count = 0
83+
definition_formatting_cells = 0
84+
changed_column_labels = 0
85+
for grid_index, (before_grid, after_grid) in enumerate(
86+
zip(baseline_grids, formatted_grids, strict=True)
87+
):
88+
if len(before_grid["columns"]) != len(after_grid["columns"]):
89+
raise AssertionError(
90+
f"{path}: {label} grid #{grid_index} column count differs: "
91+
f"before={len(before_grid['columns'])}, "
92+
f"after={len(after_grid['columns'])}"
93+
)
94+
changed_column_labels += sum(
95+
before_column != after_column
96+
for before_column, after_column in zip(
97+
before_grid["columns"], after_grid["columns"], strict=True
98+
)
99+
)
100+
before_rows = (
101+
before_grid["rows"]
102+
if oracle_mode
103+
else normalized_mysql_rows(before_grid)
104+
)
105+
after_rows = (
106+
after_grid["rows"]
107+
if oracle_mode
108+
else normalized_mysql_rows(after_grid)
109+
)
110+
if len(before_rows) != len(after_rows):
111+
raise AssertionError(
112+
f"{path}: {label} grid #{grid_index} row count differs: "
113+
f"before={len(before_rows)}, after={len(after_rows)}"
114+
)
115+
for row_index, (before_row, after_row) in enumerate(
116+
zip(before_rows, after_rows, strict=True)
117+
):
118+
if len(before_row) != len(after_row):
119+
raise AssertionError(
120+
f"{path}: {label} grid #{grid_index} row #{row_index} "
121+
f"cell count differs: before={len(before_row)}, "
122+
f"after={len(after_row)}"
123+
)
124+
for column_index, (before_cell, after_cell) in enumerate(
125+
zip(before_row, after_row, strict=True)
126+
):
127+
column = before_grid["columns"][column_index]
128+
matches = (
129+
oracle.cells_match(column, before_cell, after_cell)
130+
if oracle_mode
131+
else mysql.cells_match(
132+
before_grid["sql"], column, before_cell, after_cell
133+
)
134+
)
135+
if (
136+
not matches
137+
and not oracle_mode
138+
and sql_definition_cells_match(
139+
before_grid["sql"], before_cell, after_cell
140+
)
141+
):
142+
definition_formatting_cells += 1
143+
matches = True
144+
if not matches:
145+
raise AssertionError(
146+
f"{path}: {label} grid #{grid_index}, row #{row_index}, "
147+
f"column {column!r} differs: "
148+
f"before={before_cell!r}, after={after_cell!r}"
149+
)
150+
cell_count += 1
151+
return (
152+
len(baseline_grids),
153+
cell_count,
154+
definition_formatting_cells,
155+
changed_column_labels,
156+
)
157+
158+
159+
def verify_oracle(
160+
paths: list[Path], driver: str, layouts: list[str]
161+
) -> None:
162+
if not oracle.SNAPSHOT_BIN.is_file():
163+
raise RuntimeError(
164+
f"{oracle.SNAPSHOT_BIN} does not exist; "
165+
"build oracle_compare_test_all first"
166+
)
167+
totals = {layout: [0, 0, 0, 0] for layout in layouts}
168+
for path in paths:
169+
baseline = oracle.production_snapshot(path, driver)
170+
for layout in layouts:
171+
formatted = oracle.production_snapshot(path, driver, layout)
172+
grids, cells, definition_cells, column_labels = compare(
173+
f"oracle/{driver}/{layout}", path, baseline, formatted, True
174+
)
175+
totals[layout][0] += grids
176+
totals[layout][1] += cells
177+
totals[layout][2] += definition_cells
178+
totals[layout][3] += column_labels
179+
print(
180+
f"PASS {driver}/{layout} {path.relative_to(oracle.REPO_ROOT)}: "
181+
f"{grids} grid(s), {cells} cell(s)"
182+
)
183+
for layout in layouts:
184+
grids, cells, definition_cells, column_labels = totals[layout]
185+
print(
186+
f"PASS oracle/{driver}/{layout} before/after auto-format: "
187+
f"{len(paths)} file(s), {grids} grid(s), {cells} cell(s), "
188+
f"{definition_cells} formatting-only definition cell(s), "
189+
f"{column_labels} changed generated column label(s)"
190+
)
191+
192+
193+
def verify_mysql_family(
194+
db_type: str, paths: list[Path], layouts: list[str]
195+
) -> None:
196+
if not mysql.SNAPSHOT_BIN.is_file():
197+
raise RuntimeError(
198+
f"{mysql.SNAPSHOT_BIN} does not exist; "
199+
"build mysql_fixture_snapshot first"
200+
)
201+
config = mysql.DATABASES[db_type]
202+
totals = {layout: [0, 0, 0, 0] for layout in layouts}
203+
for path in paths:
204+
baseline = mysql.production_snapshot(db_type, config, path)
205+
for layout in layouts:
206+
formatted = mysql.production_snapshot(
207+
db_type, config, path, layout
208+
)
209+
grids, cells, definition_cells, column_labels = compare(
210+
f"{db_type}/{layout}", path, baseline, formatted, False
211+
)
212+
totals[layout][0] += grids
213+
totals[layout][1] += cells
214+
totals[layout][2] += definition_cells
215+
totals[layout][3] += column_labels
216+
print(
217+
f"PASS {db_type}/{layout} "
218+
f"{path.relative_to(mysql.REPO_ROOT)}: "
219+
f"{grids} grid(s), {cells} cell(s)"
220+
)
221+
for layout in layouts:
222+
grids, cells, definition_cells, column_labels = totals[layout]
223+
print(
224+
f"PASS {db_type}/{layout} before/after auto-format: "
225+
f"{len(paths)} file(s), {grids} grid(s), {cells} cell(s), "
226+
f"{definition_cells} formatting-only definition cell(s), "
227+
f"{column_labels} changed generated column label(s)"
228+
)
229+
230+
231+
def main() -> int:
232+
parser = argparse.ArgumentParser()
233+
parser.add_argument(
234+
"--db-type", choices=("oracle", "mariadb", "mysql"), required=True
235+
)
236+
parser.add_argument("--oracle-driver", choices=("oci", "thin"), default="oci")
237+
parser.add_argument(
238+
"--layout", choices=LAYOUTS, action="append", dest="layouts"
239+
)
240+
parser.add_argument("paths", nargs="*", type=Path)
241+
args = parser.parse_args()
242+
layouts = args.layouts or list(LAYOUTS)
243+
244+
if args.db_type == "oracle":
245+
paths = args.paths or oracle.fixture_paths()
246+
paths = [
247+
path if path.is_absolute() else oracle.REPO_ROOT / path
248+
for path in paths
249+
]
250+
verify_oracle(paths, args.oracle_driver, layouts)
251+
else:
252+
config = mysql.DATABASES[args.db_type]
253+
paths = args.paths or mysql.fixture_paths(config["fixture_dir"])
254+
paths = [
255+
path if path.is_absolute() else mysql.REPO_ROOT / path
256+
for path in paths
257+
]
258+
verify_mysql_family(args.db_type, paths, layouts)
259+
return 0
260+
261+
262+
if __name__ == "__main__":
263+
raise SystemExit(main())

scripts/verify_mysql_cli.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,23 @@ def production_env(config: dict[str, str]) -> dict[str, str]:
7676

7777

7878
def production_snapshot(
79-
db_type: str, config: dict[str, str], path: Path
79+
db_type: str,
80+
config: dict[str, str],
81+
path: Path,
82+
format_layout: str | None = None,
8083
) -> dict:
8184
with tempfile.TemporaryDirectory(prefix=f"space_query_{db_type}_cli_") as temp_dir:
8285
output_path = Path(temp_dir) / "snapshot.json"
86+
command = [
87+
str(SNAPSHOT_BIN),
88+
db_type,
89+
str(path.relative_to(REPO_ROOT)),
90+
str(output_path),
91+
]
92+
if format_layout is not None:
93+
command.extend(["--format", format_layout])
8394
result = run(
84-
[
85-
str(SNAPSHOT_BIN),
86-
db_type,
87-
str(path.relative_to(REPO_ROOT)),
88-
str(output_path),
89-
],
95+
command,
9096
env=production_env(config),
9197
stdout=subprocess.DEVNULL,
9298
stderr=subprocess.PIPE,

scripts/verify_oracle_cli.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,30 +115,46 @@ def oracle_env() -> dict[str, str]:
115115
return result
116116

117117

118-
def production_child(path: str, output_path: Path) -> None:
118+
def production_child(
119+
path: str,
120+
output_path: Path,
121+
driver: str = "oci",
122+
format_layout: str | None = None,
123+
) -> None:
124+
command = [str(SNAPSHOT_BIN), "--child", driver, path, str(output_path)]
125+
if format_layout is not None:
126+
command.extend(["--format", format_layout])
119127
result = run(
120-
[str(SNAPSHOT_BIN), "--child", "oci", path, str(output_path)],
128+
command,
121129
env=oracle_env(),
122130
stdout=subprocess.DEVNULL,
123131
stderr=subprocess.PIPE,
124132
)
125133
if result.returncode != 0:
126134
raise RuntimeError(
127-
f"production OCI child failed for {path} with {result.returncode}:\n"
135+
f"production {driver} child failed for {path} with {result.returncode}:\n"
128136
f"{result.stderr}"
129137
)
130138

131139

132-
def production_snapshot(path: Path) -> dict:
140+
def production_snapshot(
141+
path: Path, driver: str = "oci", format_layout: str | None = None
142+
) -> dict:
133143
with tempfile.TemporaryDirectory(prefix="space_query_oracle_cli_") as temp_dir:
134144
cleanup_path = Path(temp_dir) / "cleanup.json"
135145
snapshot_path = Path(temp_dir) / "snapshot.json"
136146
production_child("__cleanup__", cleanup_path)
137-
production_child(str(path.relative_to(REPO_ROOT)), snapshot_path)
147+
production_child(
148+
str(path.relative_to(REPO_ROOT)),
149+
snapshot_path,
150+
driver,
151+
format_layout,
152+
)
138153
snapshot = json.loads(snapshot_path.read_text())
139154
if snapshot["failures"]:
140155
raise RuntimeError(
141-
f"production OCI failures for {path}:\n" + "\n".join(snapshot["failures"])
156+
f"production {driver} failures for {path}:\n"
157+
+ "\n".join(snapshot["failures"])
142158
)
143159
return snapshot
144160

0 commit comments

Comments
 (0)