177 lines
6.3 KiB
Python
177 lines
6.3 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
class SymbolIndexQueryOverviewMixin:
|
|
async def search_symbols(
|
|
self,
|
|
*,
|
|
query: str,
|
|
language: Optional[str] = None,
|
|
symbol_type: Optional[str] = None,
|
|
limit: int = 30,
|
|
) -> dict[str, Any]:
|
|
text = str(query or "").strip()
|
|
if not text:
|
|
return {"count": 0, "items": []}
|
|
|
|
like = f"%{text}%"
|
|
stmt = select(SymbolDefinition).where(
|
|
and_(
|
|
SymbolDefinition.tenant_id == self.tenant_id,
|
|
SymbolDefinition.status == "active",
|
|
or_(
|
|
SymbolDefinition.symbol_name.ilike(like),
|
|
SymbolDefinition.qualified_name.ilike(like),
|
|
SymbolDefinition.file_path.ilike(like),
|
|
),
|
|
)
|
|
)
|
|
if language:
|
|
stmt = stmt.where(SymbolDefinition.language == str(language).strip().lower()[:20])
|
|
if symbol_type:
|
|
stmt = stmt.where(SymbolDefinition.symbol_type == str(symbol_type).strip().lower()[:32])
|
|
rows = (
|
|
await self.db.execute(
|
|
stmt.order_by(SymbolDefinition.updated_at.desc(), SymbolDefinition.id.desc()).limit(max(1, min(int(limit), 200)))
|
|
)
|
|
).scalars().all()
|
|
return {
|
|
"count": len(rows),
|
|
"items": [self._serialize_symbol(row) for row in rows],
|
|
}
|
|
|
|
async def get_symbol_detail(self, *, symbol_id: int) -> dict[str, Any]:
|
|
symbol = (
|
|
await self.db.execute(
|
|
select(SymbolDefinition).where(
|
|
and_(
|
|
SymbolDefinition.tenant_id == self.tenant_id,
|
|
SymbolDefinition.id == int(symbol_id),
|
|
)
|
|
)
|
|
)
|
|
).scalar_one_or_none()
|
|
if symbol is None:
|
|
raise ValueError("symbol_not_found")
|
|
|
|
refs = (
|
|
await self.db.execute(
|
|
select(SymbolReference).where(
|
|
and_(
|
|
SymbolReference.tenant_id == self.tenant_id,
|
|
SymbolReference.source_symbol_id == int(symbol.id),
|
|
)
|
|
)
|
|
.order_by(SymbolReference.id.desc())
|
|
.limit(120)
|
|
)
|
|
).scalars().all()
|
|
|
|
inbound_refs = (
|
|
await self.db.execute(
|
|
select(SymbolReference)
|
|
.where(
|
|
and_(
|
|
SymbolReference.tenant_id == self.tenant_id,
|
|
or_(
|
|
SymbolReference.target_symbol_name == str(symbol.symbol_name),
|
|
SymbolReference.target_qualified_name == str(symbol.qualified_name or ""),
|
|
),
|
|
)
|
|
)
|
|
.order_by(SymbolReference.id.desc())
|
|
.limit(120)
|
|
)
|
|
).scalars().all()
|
|
|
|
return {
|
|
"symbol": self._serialize_symbol(symbol),
|
|
"outbound_refs": [self._serialize_ref(row) for row in refs],
|
|
"inbound_refs": [self._serialize_ref(row) for row in inbound_refs],
|
|
}
|
|
|
|
async def get_overview(
|
|
self,
|
|
*,
|
|
top_n_files: int = 10,
|
|
auto_sync_if_empty: bool = False,
|
|
base_dir: Optional[str] = None,
|
|
) -> dict[str, Any]:
|
|
total = int(
|
|
(
|
|
await self.db.execute(
|
|
select(func.count(SymbolDefinition.id)).where(SymbolDefinition.tenant_id == self.tenant_id)
|
|
)
|
|
).scalar_one()
|
|
or 0
|
|
)
|
|
if total == 0 and auto_sync_if_empty:
|
|
await self.sync_codebase_symbols(base_dir=base_dir, max_files=300, force_reindex=False)
|
|
total = int(
|
|
(
|
|
await self.db.execute(
|
|
select(func.count(SymbolDefinition.id)).where(SymbolDefinition.tenant_id == self.tenant_id)
|
|
)
|
|
).scalar_one()
|
|
or 0
|
|
)
|
|
|
|
language_groups = (
|
|
await self.db.execute(
|
|
select(SymbolDefinition.language, func.count(SymbolDefinition.id))
|
|
.where(SymbolDefinition.tenant_id == self.tenant_id)
|
|
.group_by(SymbolDefinition.language)
|
|
)
|
|
).all()
|
|
type_groups = (
|
|
await self.db.execute(
|
|
select(SymbolDefinition.symbol_type, func.count(SymbolDefinition.id))
|
|
.where(SymbolDefinition.tenant_id == self.tenant_id)
|
|
.group_by(SymbolDefinition.symbol_type)
|
|
)
|
|
).all()
|
|
file_groups = (
|
|
await self.db.execute(
|
|
select(SymbolDefinition.file_path, func.count(SymbolDefinition.id))
|
|
.where(SymbolDefinition.tenant_id == self.tenant_id)
|
|
.group_by(SymbolDefinition.file_path)
|
|
.order_by(func.count(SymbolDefinition.id).desc())
|
|
.limit(max(1, min(int(top_n_files), 50)))
|
|
)
|
|
).all()
|
|
ref_total = int(
|
|
(
|
|
await self.db.execute(
|
|
select(func.count(SymbolReference.id)).where(SymbolReference.tenant_id == self.tenant_id)
|
|
)
|
|
).scalar_one()
|
|
or 0
|
|
)
|
|
indexed_files_total = int(
|
|
(
|
|
await self.db.execute(
|
|
select(func.count(func.distinct(SymbolDefinition.file_path))).where(
|
|
SymbolDefinition.tenant_id == self.tenant_id
|
|
)
|
|
)
|
|
).scalar_one()
|
|
or 0
|
|
)
|
|
return {
|
|
"tenant_id": self.tenant_id,
|
|
"indexed_files": indexed_files_total,
|
|
"symbol_total": total,
|
|
"reference_total": ref_total,
|
|
"python_symbols": int(dict(language_groups).get("python", 0) or 0),
|
|
"typescript_symbols": int(dict(language_groups).get("typescript", 0) or 0),
|
|
"symbols_by_language": {str(k): int(v or 0) for k, v in language_groups},
|
|
"symbols_by_type": {str(k): int(v or 0) for k, v in type_groups},
|
|
"top_modules": [
|
|
{"module": str(path), "symbols": int(cnt or 0)}
|
|
for path, cnt in file_groups
|
|
],
|
|
"snapshot_at": now_cn_aware().isoformat(),
|
|
}
|
|
|