Skip to content

Commit 7af1bc6

Browse files
Support SQL inputs for stats
1 parent 6099a03 commit 7af1bc6

File tree

4 files changed

+144
-18
lines changed

4 files changed

+144
-18
lines changed

sqlcompare/run_cmd.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ def _resolve_connection(connection: str | None) -> str:
2323

2424

2525
def run_cmd(
26-
table1: str = typer.Argument(
26+
previous: str = typer.Argument(
2727
..., help="Previous table name, CSV/XLSX file path, or SQL"
2828
),
29-
table2: str = typer.Argument(
29+
current: str = typer.Argument(
3030
..., help="Current table name, CSV/XLSX file path, or SQL"
3131
),
3232
index: str = typer.Argument(
@@ -70,8 +70,8 @@ def run_cmd(
7070
"""
7171
schema = schema or get_default_schema()
7272

73-
prev_spec = detect_input(table1)
74-
new_spec = detect_input(table2)
73+
prev_spec = detect_input(previous)
74+
new_spec = detect_input(current)
7575

7676
if prev_spec.kind == "file" or new_spec.kind == "file":
7777
if prev_spec.kind != "file" or new_spec.kind != "file":
@@ -123,8 +123,8 @@ def run_cmd(
123123
return
124124

125125
compare_table(
126-
table1,
127-
table2,
126+
previous,
127+
current,
128128
index,
129129
connection,
130130
schema,

sqlcompare/stats.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77

88
def stats_cmd(
9-
table1: str = typer.Argument(..., help="Previous table name or CSV/XLSX file path"),
10-
table2: str = typer.Argument(..., help="Current table name or CSV/XLSX file path"),
9+
previous: str = typer.Argument(
10+
..., help="Previous table name or CSV/XLSX file path"
11+
),
12+
current: str = typer.Argument(..., help="Current table name or CSV/XLSX file path"),
1113
connection: str | None = typer.Option(
1214
None, "--connection", "-c", help="Database connector name"
1315
),
@@ -42,4 +44,4 @@ def stats_cmd(
4244
PREV_DISTINCT, NEW_DISTINCT: Unique value counts
4345
*_DIFF: Calculated differences
4446
"""
45-
compare_table_stats(table1, table2, connection)
47+
compare_table_stats(previous, current, connection)

sqlcompare/utils/test_types/stats.py

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import os
2+
import uuid
13
from pathlib import Path
24

5+
from sqlcompare.config import get_default_schema
36
from sqlcompare.db import DBConnection
47
from sqlcompare.log import log
8+
from sqlcompare.helpers import create_table_from_select, detect_input, ensure_schema
59
from sqlcompare.utils.format import format_table
610

711

@@ -51,23 +55,65 @@ def _is_supported_file(path: Path) -> bool:
5155
return path.exists() and path.suffix.lower() in (".csv", ".xlsx")
5256

5357

58+
def _resolve_connection(connection: str | None) -> str:
59+
if connection:
60+
return connection
61+
default_conn = os.getenv("SQLCOMPARE_CONN_DEFAULT") or os.getenv("DTK_CONN_DEFAULT")
62+
if not default_conn:
63+
raise ValueError(
64+
"No connection specified. Use --connection or set SQLCOMPARE_CONN_DEFAULT."
65+
)
66+
return default_conn
67+
68+
5469
def compare_table_stats(table1: str, table2: str, connection: str | None) -> None:
5570
table1_name = table1
5671
table2_name = table2
5772
connection_id = connection
5873

59-
if connection is None:
60-
path1 = Path(table1).expanduser()
61-
path2 = Path(table2).expanduser()
62-
if _is_supported_file(path1) and _is_supported_file(path2):
74+
spec_prev = detect_input(table1)
75+
spec_new = detect_input(table2)
76+
77+
if spec_prev.kind == "file" or spec_new.kind == "file":
78+
if spec_prev.kind != "file" or spec_new.kind != "file":
79+
raise ValueError(
80+
"Both table arguments must be file paths when using CSV/XLSX inputs."
81+
)
82+
if connection is None:
6383
connection_id = "duckdb:///:memory:"
64-
table1_name = path1.stem
65-
table2_name = path2.stem
84+
table1_name = Path(spec_prev.value).stem
85+
table2_name = Path(spec_new.value).stem
86+
elif spec_prev.kind == "sql" or spec_new.kind == "sql":
87+
connection_id = _resolve_connection(connection)
88+
schema = get_default_schema()
89+
schema_prefix = f"{schema}." if schema else ""
90+
suffix = uuid.uuid4().hex[:8]
91+
table1_name = (
92+
spec_prev.value
93+
if spec_prev.kind == "table"
94+
else f"{schema_prefix}sqlcompare_stats_{suffix}_previous"
95+
)
96+
table2_name = (
97+
spec_new.value
98+
if spec_new.kind == "table"
99+
else f"{schema_prefix}sqlcompare_stats_{suffix}_new"
100+
)
101+
else:
102+
table1_name = spec_prev.value
103+
table2_name = spec_new.value
66104

67105
with DBConnection(connection_id) as db:
68-
if connection is None and connection_id == "duckdb:///:memory:":
69-
db.create_table_from_file(table1_name, table1)
70-
db.create_table_from_file(table2_name, table2)
106+
if spec_prev.kind == "file" and spec_new.kind == "file":
107+
if connection is None and connection_id == "duckdb:///:memory:":
108+
db.create_table_from_file(table1_name, spec_prev.value)
109+
db.create_table_from_file(table2_name, spec_new.value)
110+
if spec_prev.kind == "sql" or spec_new.kind == "sql":
111+
schema = get_default_schema()
112+
ensure_schema(db, schema)
113+
if spec_prev.kind == "sql":
114+
create_table_from_select(db, table1_name, spec_prev.value)
115+
if spec_new.kind == "sql":
116+
create_table_from_select(db, table2_name, spec_new.value)
71117

72118
cols_prev = db.get_table_columns(table1_name)
73119
cols_new = db.get_table_columns(table2_name)

tests/test_stats_comparison.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,81 @@ def test_table_command_with_stats_from_files() -> None:
100100

101101
assert result.exit_code == 0, result.output
102102
assert "Table statistics comparison" in result.output
103+
104+
105+
def test_stats_command_with_inline_sql(tmp_path, monkeypatch) -> None:
106+
db_path = tmp_path / "sqlcompare.duckdb"
107+
seed_duckdb(db_path)
108+
config_dir = tmp_path / "config"
109+
set_cli_env(
110+
monkeypatch,
111+
config_dir,
112+
"duckdb_test",
113+
f"duckdb:///{db_path}",
114+
schema="analysis_schema",
115+
)
116+
runner = CliRunner()
117+
118+
result = runner.invoke(
119+
app,
120+
[
121+
"stats",
122+
"SELECT * FROM previous",
123+
"SELECT * FROM current",
124+
"--connection",
125+
"duckdb_test",
126+
],
127+
)
128+
129+
assert result.exit_code == 0, result.output
130+
assert "Table statistics comparison" in result.output
131+
132+
133+
def test_stats_command_with_sql_files(tmp_path, monkeypatch) -> None:
134+
db_path = tmp_path / "sqlcompare.duckdb"
135+
seed_duckdb(db_path)
136+
config_dir = tmp_path / "config"
137+
set_cli_env(
138+
monkeypatch,
139+
config_dir,
140+
"duckdb_test",
141+
f"duckdb:///{db_path}",
142+
schema="analysis_schema",
143+
)
144+
prev_sql = tmp_path / "previous.sql"
145+
new_sql = tmp_path / "current.sql"
146+
prev_sql.write_text("SELECT * FROM previous", encoding="utf-8")
147+
new_sql.write_text("SELECT * FROM current", encoding="utf-8")
148+
runner = CliRunner()
149+
150+
result = runner.invoke(
151+
app,
152+
[
153+
"stats",
154+
str(prev_sql),
155+
str(new_sql),
156+
"--connection",
157+
"duckdb_test",
158+
],
159+
)
160+
161+
assert result.exit_code == 0, result.output
162+
assert "Table statistics comparison" in result.output
163+
164+
165+
def test_stats_command_sql_requires_connection(tmp_path, monkeypatch) -> None:
166+
monkeypatch.delenv("SQLCOMPARE_CONN_DEFAULT", raising=False)
167+
monkeypatch.delenv("DTK_CONN_DEFAULT", raising=False)
168+
runner = CliRunner()
169+
170+
result = runner.invoke(
171+
app,
172+
[
173+
"stats",
174+
"SELECT * FROM previous",
175+
"SELECT * FROM current",
176+
],
177+
)
178+
179+
assert result.exit_code != 0
180+
assert "No connection specified" in str(result.exception)

0 commit comments

Comments
 (0)