Детализирована документация. Небольшие изменения имен параметров.

This commit is contained in:
mihailkudravcev
2026-07-16 15:38:16 +03:00
parent 7f9d198f79
commit 24200b401a
22 changed files with 2986 additions and 49 deletions
+287
View File
@@ -0,0 +1,287 @@
"""Поиск по единому index.sqlite проекта."""
from __future__ import annotations
import json
import re
import sqlite3
from pathlib import Path
from typing import Any
from . import db as dbmod
from .config import cache_dir
def _fts_query(raw: str) -> str:
tokens = re.findall(r"[\wА-Яа-яЁё]+", raw, flags=re.UNICODE)
if not tokens:
return raw
return " ".join(f"{t}*" for t in tokens)
def open_index(project_root: Path, *, readonly: bool = True) -> sqlite3.Connection:
path = dbmod.global_db_path(cache_dir(project_root))
if not path.is_file():
raise FileNotFoundError(
f"Индекс не найден: {path}. Запустите: python tools/index/index_1c.py reindex --full"
)
return dbmod.connect(path, readonly=readonly)
def search_objects(
conn: sqlite3.Connection,
query: str,
*,
configs: list[str] | None = None,
object_type: str | None = None,
limit: int = 30,
) -> list[dict[str, Any]]:
exact_sql = """
SELECT config, full_name, object_type, name, synonym, comment, path, '' AS snip, -100.0 AS score
FROM objects
WHERE (name = ? OR full_name = ? OR full_name LIKE ?)
"""
exact_params: list[Any] = [query, query, f"%.{query}"]
if configs:
exact_sql += f" AND config IN ({','.join('?' * len(configs))})"
exact_params.extend(configs)
if object_type:
exact_sql += " AND object_type = ?"
exact_params.append(object_type)
exact_sql += " LIMIT ?"
exact_params.append(limit)
exact = [dict(r) for r in conn.execute(exact_sql, exact_params)]
seen = {(r["config"], r["full_name"]) for r in exact}
fts = _fts_query(query)
fetch_n = max(limit * 5, 50)
sql = """
SELECT o.config, o.full_name, o.object_type, o.name, o.synonym, o.comment, o.path,
snippet(objects_fts, 0, '[', ']', '', 12) AS snip,
bm25(objects_fts, 10.0, 5.0, 2.0, 1.0) AS score
FROM objects_fts
JOIN objects o ON o.id = objects_fts.object_id
WHERE objects_fts MATCH ?
"""
params: list[Any] = [fts]
if configs:
sql += f" AND o.config IN ({','.join('?' * len(configs))})"
params.extend(configs)
if object_type:
sql += " AND o.object_type = ?"
params.append(object_type)
sql += " ORDER BY score LIMIT ?"
params.append(fetch_n)
try:
rows = [dict(r) for r in conn.execute(sql, params)]
except sqlite3.OperationalError:
like = f"%{query}%"
sql2 = """
SELECT config, full_name, object_type, name, synonym, comment, path, '' AS snip, 0 AS score
FROM objects
WHERE full_name LIKE ? OR synonym LIKE ? OR comment LIKE ? OR name LIKE ?
"""
params2: list[Any] = [like, like, like, like]
if configs:
sql2 += f" AND config IN ({','.join('?' * len(configs))})"
params2.extend(configs)
if object_type:
sql2 += " AND object_type = ?"
params2.append(object_type)
sql2 += " LIMIT ?"
params2.append(fetch_n)
rows = [dict(r) for r in conn.execute(sql2, params2)]
merged = list(exact)
for r in rows:
key = (r["config"], r["full_name"])
if key in seen:
continue
seen.add(key)
merged.append(r)
q_cf = query.casefold()
for r in merged:
name_cf = (r.get("name") or "").casefold()
full_cf = (r.get("full_name") or "").casefold()
if name_cf == q_cf or full_cf == q_cf or full_cf.endswith("." + q_cf):
r["_boost"] = 0
elif q_cf in name_cf or q_cf in full_cf:
r["_boost"] = 1
else:
r["_boost"] = 2
merged.sort(
key=lambda x: (x.get("_boost", 9), x.get("score") if x.get("score") is not None else 0)
)
return merged[:limit]
def search_modules(
conn: sqlite3.Connection,
query: str,
*,
configs: list[str] | None = None,
limit: int = 30,
) -> list[dict[str, Any]]:
fts = _fts_query(query)
sql = """
SELECT m.config, m.rel_path, m.owner, m.module_kind, m.symbols, m.line_count,
snippet(modules_fts, 4, '[', ']', '', 16) AS snip,
bm25(modules_fts, 2.0, 5.0, 2.0, 8.0, 1.0) AS score
FROM modules_fts
JOIN modules m ON m.id = modules_fts.module_id
WHERE modules_fts MATCH ?
"""
params: list[Any] = [fts]
if configs:
sql += f" AND m.config IN ({','.join('?' * len(configs))})"
params.extend(configs)
sql += " ORDER BY score LIMIT ?"
params.append(limit)
try:
return [dict(r) for r in conn.execute(sql, params)]
except sqlite3.OperationalError:
like = f"%{query}%"
sql2 = """
SELECT config, rel_path, owner, module_kind, symbols, line_count, '' AS snip, 0 AS score
FROM modules
WHERE rel_path LIKE ? OR owner LIKE ? OR symbols LIKE ?
"""
params2: list[Any] = [like, like, like]
if configs:
sql2 += f" AND config IN ({','.join('?' * len(configs))})"
params2.extend(configs)
sql2 += " LIMIT ?"
params2.append(limit)
return [dict(r) for r in conn.execute(sql2, params2)]
def find_refs_to(
conn: sqlite3.Connection,
target: str,
*,
configs: list[str] | None = None,
limit: int = 100,
) -> list[dict[str, Any]]:
kind = ""
name = target
if "." in target:
kind, name = target.split(".", 1)
kind_ref = kind
if kind and not kind.endswith("Ref"):
if kind in {
"Catalog",
"Document",
"Enum",
"ExchangePlan",
"BusinessProcess",
"Task",
"ChartOfCharacteristicTypes",
"ChartOfAccounts",
"ChartOfCalculationTypes",
}:
kind_ref = kind + "Ref"
if kind_ref:
sql = """
SELECT config, from_full_name, from_field, to_kind, to_name
FROM refs
WHERE to_name = ? AND (to_kind = ? OR to_kind = ?)
"""
params: list[Any] = [name, kind_ref, kind]
else:
sql = """
SELECT config, from_full_name, from_field, to_kind, to_name
FROM refs WHERE to_name = ?
"""
params = [name]
if configs:
sql += f" AND config IN ({','.join('?' * len(configs))})"
params.extend(configs)
sql += " ORDER BY config, from_full_name LIMIT ?"
params.append(limit)
return [dict(r) for r in conn.execute(sql, params)]
def get_object(
conn: sqlite3.Connection,
name: str,
*,
configs: list[str] | None = None,
) -> dict[str, Any] | None:
sql = "SELECT config, json FROM objects WHERE full_name = ? OR name = ?"
params: list[Any] = [name, name]
if configs:
sql += f" AND config IN ({','.join('?' * len(configs))})"
params.extend(configs)
sql += " LIMIT 1"
row = conn.execute(sql, params).fetchone()
if not row:
sql = "SELECT config, json FROM objects WHERE full_name LIKE ?"
params = [f"%.{name}"]
if configs:
sql += f" AND config IN ({','.join('?' * len(configs))})"
params.extend(configs)
sql += " LIMIT 1"
row = conn.execute(sql, params).fetchone()
if not row:
return None
data = json.loads(row["json"])
data["_config"] = row["config"]
return data
def get_module(
conn: sqlite3.Connection,
path_or_owner: str,
*,
configs: list[str] | None = None,
) -> list[dict[str, Any]]:
sql = """
SELECT config, rel_path, owner, module_kind, symbols, line_count, size
FROM modules
WHERE rel_path = ? OR rel_path LIKE ? OR owner = ? OR owner LIKE ?
"""
like = f"%{path_or_owner}%"
params: list[Any] = [path_or_owner, like, path_or_owner, like]
if configs:
sql += f" AND config IN ({','.join('?' * len(configs))})"
params.extend(configs)
sql += " ORDER BY config, rel_path LIMIT 50"
return [dict(r) for r in conn.execute(sql, params)]
def list_indexed_configs(conn_or_cache) -> list[str]:
"""Совместимость: Connection или Path cache_root."""
if isinstance(conn_or_cache, Path):
db_path = dbmod.global_db_path(conn_or_cache)
if not db_path.is_file():
# legacy: подкаталоги с manifest
return [
p.name
for p in sorted(conn_or_cache.iterdir())
if p.is_dir() and (p / "manifest.json").is_file()
]
conn = dbmod.connect(db_path, readonly=True)
try:
return [c["name"] for c in dbmod.list_configs(conn)]
finally:
conn.close()
return [c["name"] for c in dbmod.list_configs(conn_or_cache)]
# --- legacy wrappers (per-config sqlite) ---
def search_sqlite(db_path: Path, query: str, *, limit: int = 30, object_type: str | None = None):
"""Устарело: ищет в per-config sqlite; для нового индекса используйте search_objects."""
if not db_path.is_file():
return []
# если передали index.sqlite — открыть как глобальный
if db_path.name == "index.sqlite":
conn = dbmod.connect(db_path, readonly=True)
try:
return search_objects(conn, query, object_type=object_type, limit=limit)
finally:
conn.close()
return []