from perfkit import sqlstats from perfkit.sqlstats import explain_candidates, v3_functions, is_explainable, pick_params def row(q, total=1.0): return {"query": q, "total_exec_time": total} def test_v3_functions_extracts_names(): assert v3_functions("SELECT * FROM v3.v_sheet_rows($1, $2)") == ["v_sheet_rows"] assert v3_functions("select v3.upd_cell($1,$2)") == ["upd_cell"] assert v3_functions("SELECT 1") == [] def test_is_explainable_select_and_read_functions(): assert is_explainable("SELECT * FROM v3.v_sheet_rows($1)") assert is_explainable(" select a from t where id = $1") assert is_explainable("WITH x AS (SELECT 1) SELECT * FROM x") def test_is_explainable_rejects_dml_and_writers(): assert not is_explainable("UPDATE t SET a = $1") assert not is_explainable("INSERT INTO t VALUES ($1)") assert not is_explainable("DELETE FROM t") assert not is_explainable("SELECT * FROM v3.add_line($1)") assert not is_explainable("SELECT v3.upd_cell($1, $2)") assert not is_explainable("SELECT v3.del_line($1)") assert not is_explainable("SELECT * FROM t FOR UPDATE") assert not is_explainable("SELECT set_config('a', $1, false)") assert not is_explainable("SELECT pg_stat_statements_reset()") assert not is_explainable("EXPLAIN SELECT 1") def test_explain_candidates_top5_by_total_in_order(): rows = [row("UPDATE t SET a=$1", 100)] + [row(f"SELECT {i} FROM t WHERE a=$1", i) for i in range(10)] got = explain_candidates(rows, n=5) assert [r["total_exec_time"] for r in got] == [9, 8, 7, 6, 5] def test_pick_params_uses_sample_when_given(): assert pick_params("SELECT $1, $2", {"SELECT $1, $2": [1, "a"]}) == [1, "a"] assert pick_params("SELECT $1", {}) is None def test_dump_out_dir_passed_to_dump(monkeypatch, tmp_path): """--out: sql_top.json/explain пишутся в отдельный каталог шага (run-all), сетевой лог читается из run_dir.""" seen = {} async def fake(run_dir, top, out_dir): seen.update(run_dir=run_dir, top=top, out_dir=out_dir) return {"explained": 0, "skipped": 0} monkeypatch.setattr(sqlstats, "_dump", fake) out = tmp_path / "sql" / "scroll" assert sqlstats.main(["dump", str(tmp_path), "--top", "7", "--out", str(out)]) == 0 assert seen == {"run_dir": tmp_path, "top": 7, "out_dir": out} assert out.is_dir() def test_dump_without_out_writes_to_run_dir(monkeypatch, tmp_path): seen = {} async def fake(run_dir, top, out_dir): seen.update(out_dir=out_dir) return {"explained": 0, "skipped": 0} monkeypatch.setattr(sqlstats, "_dump", fake) assert sqlstats.main(["dump", str(tmp_path)]) == 0 assert seen["out_dir"] == tmp_path