"""SQL-нормализаторы свода 2 (миграция 0053): регистр, обрезка, запись числа как в JS, компактная проекция.""" import json from pathlib import Path import pytest from tests.integration.conftest import legacy_full from tests.svod2_oracle import compact_projection NUM = json.loads( (Path(__file__).resolve().parent.parent / "fixtures" / "js_number_text_vectors.json").read_text( encoding="utf-8" ) ) def test_js_number_text_matches_node_vectors(svod2_sql): for v in NUM: got = svod2_sql( "SELECT v3.svod2_js_number_text((CAST(:x AS jsonb) #>> '{}')::float8) AS t", {"x": v["input"]}, )[0][0] assert got == v["js"], v def test_js_number_text_ignores_session_extra_float_digits(svod2_sql): svod2_sql("SET LOCAL extra_float_digits = 0", None) got = svod2_sql("SELECT v3.svod2_js_number_text(0.1::float8 + 0.2::float8) AS t", None)[0][0] assert got == "0.30000000000000004" @pytest.mark.parametrize( "src", ["АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "ЄІЇЎЂЏҐ", "ABCxyz", "ÀÉÖÞ", "×", "аРЕНДА 1"], ) def test_casefold_matches_js_lower(svod2_sql, src): assert svod2_sql("SELECT v3.svod2_casefold(:s) AS t", {"s": src})[0][0] == src.lower() def test_casefold_independent_of_collation(svod2_sql): got = svod2_sql("""SELECT v3.svod2_casefold('ЁЛКА' COLLATE "C") AS t""", None)[0][0] assert got == "ёлка" def test_js_trim_ecmascript_whitespace(svod2_sql): assert svod2_sql("SELECT v3.svod2_js_trim(:s) AS t", {"s": "   x \t"})[0][0] == "x" assert svod2_sql("SELECT v3.svod2_js_trim(:s) AS t", {"s": "​ x"})[0][0] == "​ x" def test_compact_matches_python_projection(svod2_sql, svod2_data, client, admin_tokens, auth_headers): full = legacy_full(client, "RF", auth_headers(admin_tokens))[0]["data"] got = svod2_sql("SELECT v3.svod2_compact(CAST(:d AS jsonb)) AS c", {"d": json.dumps(full)})[0][0] assert got == compact_projection(full) def test_single_signature_without_overloads(svod2_sql): rows = svod2_sql( "SELECT proname, pronargs FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace " "WHERE nspname = 'v3' AND proname IN ('v_svod2_go', 'v_svod2_rf', 'v_svod2_go_export')", None, ) assert sorted((r.proname, r.pronargs) for r in rows) == [ ("v_svod2_go", 11), ("v_svod2_go_export", 4), ("v_svod2_rf", 11)] @pytest.mark.parametrize("fn", ["v_svod2_go", "v_svod2_rf"]) def test_page_total_count_and_order(svod2_sql, svod2_data, admin_tokens, fn): # admin_tokens: запрос к API нужен, чтобы откат сессии клиента сработал (loop_ref задаётся при обращении к get_db) full = svod2_sql(f"SELECT source_line_id FROM v3.{fn}(2031)", None) page = svod2_sql( f"SELECT source_line_id, total_count FROM v3.{fn}(2031, NULL, 0, 2, 'org_name', 'asc', " "NULL, NULL, NULL, NULL, false)", None) assert [r.source_line_id for r in page] == [r.source_line_id for r in full][:2] assert {r.total_count for r in page} == {len(full)} def test_facets_totals(svod2_sql, svod2_data, admin_tokens): rows = {(r.kind, r.value): r.n for r in svod2_sql( "SELECT * FROM v3.v_svod2_facets('GO', 2031, NULL, NULL, NULL, NULL, NULL)", None)} assert rows[("total", None)] == rows[("search_total", None)] == len( svod2_sql("SELECT 1 FROM v3.v_svod2_go(2031)", None)) SEARCHES = ["", " ", "аренда", "АРЕНДА", "ё", "1234.5", "1234.50", " кв ", "1", "true", "777.25", "нет такого"] @pytest.mark.parametrize("sheet,fn", [("GO", "v_svod2_go"), ("RF", "v_svod2_rf")]) @pytest.mark.parametrize("search", SEARCHES) def test_search_matches_screen_oracle(svod2_sql, svod2_data, client, admin_tokens, auth_headers, sheet, fn, search): from tests.svod2_oracle import screen_rows rows = legacy_full(client, sheet, auth_headers(admin_tokens)) expected = [r["data"]["line_id"] for r in screen_rows(rows, search)] got = svod2_sql( f"SELECT source_line_id, total_count FROM v3.{fn}(2031, NULL, NULL, NULL, 'org_name', 'asc', :s)", {"s": search}, ) assert sorted(r.source_line_id for r in got) == sorted(expected) if got: assert {r.total_count for r in got} == {len(expected)} @pytest.mark.parametrize("sheet", ["GO", "RF"]) @pytest.mark.parametrize("search,smeta_type", [("", None), ("1", "кв "), ("аренда", "АХР"), ("", "Операц")]) def test_facets_match_screen_oracle(svod2_sql, svod2_data, client, admin_tokens, auth_headers, sheet, search, smeta_type): from tests.svod2_oracle import screen_facets rows = legacy_full(client, sheet, auth_headers(admin_tokens)) filters = {"smeta_type": smeta_type} if smeta_type else {} expected = screen_facets(rows, search, filters) got = svod2_sql( "SELECT kind, value, n FROM v3.v_svod2_facets(:sh, 2031, NULL, :s, :t, NULL, NULL)", {"sh": sheet, "s": search or None, "t": smeta_type}, ) assert next(r.n for r in got if r.kind == "total") == expected["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"): 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"(?