fix(api): свод 2 — варианты фильтров без коррелированного подзапроса поиска, откат засева в teardown
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
719f568863
commit
e81df1d71e
@ -879,9 +879,12 @@ WITH q AS MATERIALIZED (
|
|||||||
NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_smeta_direction)), '') AS f_dir,
|
NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_smeta_direction)), '') AS f_dir,
|
||||||
NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_org_name)), '') AS f_org
|
NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_org_name)), '') AS f_org
|
||||||
), lines AS MATERIALIZED (
|
), lines AS MATERIALIZED (
|
||||||
|
-- Подзапрос IN не должен ссылаться на столбцы q/l (иначе он коррелированный и считается
|
||||||
|
-- на каждую строку): параметр нормализуется заново, тогда это hashed SubPlan, один раз.
|
||||||
SELECT l.smeta_type, l.smeta_direction, l.org_name,
|
SELECT l.smeta_type, l.smeta_direction, l.org_name,
|
||||||
(q.s IS NULL OR l.line_id IN (
|
(q.s IS NULL OR l.line_id IN (
|
||||||
SELECT m.line_id FROM v3.svod2_lines(p_sheet, p_year, p_org_unit_ids, q.s) m)) AS hit,
|
SELECT m.line_id FROM v3.svod2_lines(p_sheet, p_year, p_org_unit_ids,
|
||||||
|
NULLIF(v3.svod2_casefold(v3.svod2_js_trim(p_search)), '')) m)) AS hit,
|
||||||
(q.f_type IS NULL OR (l.smeta_type IS NOT NULL
|
(q.f_type IS NULL OR (l.smeta_type IS NOT NULL
|
||||||
AND v3.svod2_js_trim(v3.svod2_casefold(l.smeta_type)) = q.f_type)) AS ok_type,
|
AND v3.svod2_js_trim(v3.svod2_casefold(l.smeta_type)) = q.f_type)) AS ok_type,
|
||||||
(q.f_dir IS NULL OR (l.smeta_direction IS NOT NULL
|
(q.f_dir IS NULL OR (l.smeta_direction IS NOT NULL
|
||||||
|
|||||||
@ -123,7 +123,7 @@ def svod2_data(client, svod2_sql) -> dict:
|
|||||||
# id строки contract_detail: contract_detail.line_id = 910002, технический id,
|
# id строки contract_detail: contract_detail.line_id = 910002, технический id,
|
||||||
# который попадает в шапку строки, — expense_item_id этой строки
|
# который попадает в шапку строки, — expense_item_id этой строки
|
||||||
cd_id = svod2_sql("SELECT expense_item_id FROM v3.budget_line WHERE id = 910002")[0][0]
|
cd_id = svod2_sql("SELECT expense_item_id FROM v3.budget_line WHERE id = 910002")[0][0]
|
||||||
return {
|
yield {
|
||||||
"year": SVOD2_YEAR,
|
"year": SVOD2_YEAR,
|
||||||
"org_a": 910001,
|
"org_a": 910001,
|
||||||
"org_b": 910002,
|
"org_b": 910002,
|
||||||
@ -132,3 +132,6 @@ def svod2_data(client, svod2_sql) -> dict:
|
|||||||
"cd_id": cd_id,
|
"cd_id": cd_id,
|
||||||
"ckk_only_value": "777.25",
|
"ckk_only_value": "777.25",
|
||||||
}
|
}
|
||||||
|
# откат, пока portal открыт: иначе при отсутствии запросов к API транзакция засева
|
||||||
|
# остаётся висеть и блокирует следующий засев
|
||||||
|
client.portal.call(client.db_session.rollback)
|
||||||
|
|||||||
@ -94,7 +94,7 @@ def test_search_matches_screen_oracle(svod2_sql, svod2_data, client, admin_token
|
|||||||
expected = [r["data"]["line_id"] for r in screen_rows(rows, search)]
|
expected = [r["data"]["line_id"] for r in screen_rows(rows, search)]
|
||||||
got = svod2_sql(
|
got = svod2_sql(
|
||||||
f"SELECT source_line_id, total_count FROM v3.{fn}(2031, NULL, NULL, NULL, 'org_name', 'asc', :s)",
|
f"SELECT source_line_id, total_count FROM v3.{fn}(2031, NULL, NULL, NULL, 'org_name', 'asc', :s)",
|
||||||
{"s": search or None},
|
{"s": search},
|
||||||
)
|
)
|
||||||
assert sorted(r.source_line_id for r in got) == sorted(expected)
|
assert sorted(r.source_line_id for r in got) == sorted(expected)
|
||||||
if got:
|
if got:
|
||||||
@ -118,3 +118,17 @@ def test_facets_match_screen_oracle(svod2_sql, svod2_data, client, admin_tokens,
|
|||||||
assert next(r.n for r in got if r.kind == "search_total") == expected["search_total"]
|
assert next(r.n for r in got if r.kind == "search_total") == expected["search_total"]
|
||||||
for col in ("smeta_type", "smeta_direction", "org_name"):
|
for col in ("smeta_type", "smeta_direction", "org_name"):
|
||||||
assert {r.value: r.n for r in got if r.kind == col} == expected[col]
|
assert {r.value: r.n for r in got if r.kind == col} == expected[col]
|
||||||
|
|
||||||
|
|
||||||
|
def test_facets_search_subplan_is_not_correlated(svod2_sql):
|
||||||
|
import re
|
||||||
|
|
||||||
|
plan = "\n".join(r[0] for r in svod2_sql(
|
||||||
|
"EXPLAIN (VERBOSE, COSTS OFF) "
|
||||||
|
"SELECT * FROM v3.v_svod2_facets('GO', 2031, NULL, 'аренда', NULL, NULL, NULL)", None))
|
||||||
|
# IN по svod2_lines должен быть hashed SubPlan (считается один раз); коррелированный
|
||||||
|
# считался бы на каждую строку. Не-hashed SubPlan в Filter внутри svod2_lines (EXISTS поиска)
|
||||||
|
# законен, поэтому смотрим только на выражения в Output.
|
||||||
|
outputs = [l for l in plan.splitlines() if "Output:" in l and "SubPlan" in l]
|
||||||
|
assert any("hashed SubPlan" in l for l in outputs), plan
|
||||||
|
assert not any(re.search(r"(?<!hashed )SubPlan \d+", l) for l in outputs), plan
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user