mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix unresolved links on sync
This commit is contained in:
@@ -70,6 +70,7 @@ async def get_sync_service(db_type=DatabaseType.FILESYSTEM):
|
||||
entity_sync_service=knowledge_sync_service,
|
||||
entity_parser=entity_parser,
|
||||
entity_repository=entity_repository,
|
||||
relation_repository=relation_repository,
|
||||
search_service=search_service,
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ from sqlalchemy import (
|
||||
Text,
|
||||
ForeignKey,
|
||||
UniqueConstraint,
|
||||
text,
|
||||
DateTime,
|
||||
Index,
|
||||
JSON,
|
||||
@@ -185,7 +184,10 @@ class Relation(Base):
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
from_id: Mapped[int] = mapped_column(Integer, ForeignKey("entity.id", ondelete="CASCADE"))
|
||||
to_id: Mapped[int] = mapped_column(Integer, ForeignKey("entity.id", ondelete="CASCADE"))
|
||||
to_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("entity.id", ondelete="CASCADE"), nullable=True
|
||||
)
|
||||
to_name: Mapped[str] = mapped_column(String)
|
||||
relation_type: Mapped[str] = mapped_column(String)
|
||||
context: Mapped[str] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime)
|
||||
@@ -206,7 +208,9 @@ class Relation(Base):
|
||||
|
||||
return generate_permalink(
|
||||
f"{self.from_entity.permalink}/{self.relation_type}/{self.to_entity.permalink}"
|
||||
if self.to_entity
|
||||
else f"{self.from_entity.permalink}/{self.relation_type}"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Relation(id={self.id}, from_id={self.from_id}, to_id={self.to_id}, type='{self.relation_type}')"
|
||||
return f"Relation(id={self.id}, from_id={self.from_id}, to_id={self.to_id}, type='{self.relation_type}')"
|
||||
|
||||
@@ -67,5 +67,12 @@ class RelationRepository(Repository[Relation]):
|
||||
async with db.scoped_session(self.session_maker) as session:
|
||||
await session.execute(delete(Relation).where(Relation.from_id == entity_id))
|
||||
|
||||
async def find_unresolved_relations(self) -> Sequence[Relation]:
|
||||
"""Find all unresolved relations, where to_id is null."""
|
||||
query = select(Relation).filter(Relation.to_id.is_(None))
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
def get_load_options(self) -> List[LoaderOption]:
|
||||
return [selectinload(Relation.from_entity), selectinload(Relation.to_entity)]
|
||||
|
||||
@@ -47,6 +47,7 @@ class RelationService(BaseService[RelationRepository]):
|
||||
relation = RelationModel(
|
||||
from_id=from_entity.id,
|
||||
to_id=to_entity.id,
|
||||
to_name=to_entity.title,
|
||||
relation_type=rs.relation_type,
|
||||
context=rs.context,
|
||||
)
|
||||
|
||||
@@ -177,7 +177,7 @@ class SearchService:
|
||||
# Only index outgoing relations (ones defined in this file)
|
||||
for rel in entity.outgoing_relations:
|
||||
# Create descriptive title showing the relationship
|
||||
relation_title = f"{rel.from_entity.title} → {rel.to_entity.title}"
|
||||
relation_title = f"{rel.from_entity.title} → {rel.to_entity.title}" if rel.to_entity else f"{rel.from_entity.title}"
|
||||
|
||||
await self._do_index(
|
||||
SearchIndexRow(
|
||||
|
||||
@@ -159,15 +159,16 @@ class EntitySyncService:
|
||||
rel.target,
|
||||
)
|
||||
|
||||
# Look up target entity
|
||||
if not target_entity:
|
||||
logger.warning(f"Skipping relation in {file_path}: target not found: {rel.target}")
|
||||
continue
|
||||
# if the target is found, store the id
|
||||
target_id = target_entity.id if target_entity else None
|
||||
# if the target is found, store the title, otherwise add the target for a "forward link"
|
||||
target_name = target_entity.title if target_entity else rel.target
|
||||
|
||||
# Create the relation
|
||||
relation = Relation(
|
||||
from_id=db_entity.id,
|
||||
to_id=target_entity.id,
|
||||
to_id=target_id,
|
||||
to_name=target_name,
|
||||
relation_type=rel.type,
|
||||
context=rel.context,
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Dict
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory.markdown import EntityParser, EntityMarkdown
|
||||
from basic_memory.repository import EntityRepository
|
||||
from basic_memory.repository import EntityRepository, RelationRepository
|
||||
from basic_memory.services.search_service import SearchService
|
||||
from basic_memory.sync import FileChangeScanner
|
||||
from basic_memory.sync.entity_sync_service import EntitySyncService
|
||||
@@ -27,12 +27,14 @@ class SyncService:
|
||||
entity_sync_service: EntitySyncService,
|
||||
entity_parser: EntityParser,
|
||||
entity_repository: EntityRepository,
|
||||
relation_repository: RelationRepository,
|
||||
search_service: SearchService,
|
||||
):
|
||||
self.scanner = scanner
|
||||
self.entity_sync_service = entity_sync_service
|
||||
self.entity_parser = entity_parser
|
||||
self.entity_repository = entity_repository
|
||||
self.relation_repository = relation_repository
|
||||
self.search_service = search_service
|
||||
|
||||
async def handle_entity_deletion(self, file_path: str):
|
||||
@@ -119,4 +121,20 @@ class SyncService:
|
||||
# Set final checksum to mark sync complete
|
||||
await self.entity_repository.update(entity.id, {"checksum": checksum})
|
||||
|
||||
# Third pass: Try to resolve any forward references
|
||||
logger.debug("Attempting to resolve forward references")
|
||||
for relation in await self.relation_repository.find_unresolved_relations():
|
||||
target_entity = await self.entity_sync_service.link_resolver.resolve_link(relation.to_name)
|
||||
# check we found a link that is not the source
|
||||
if target_entity and target_entity.id != relation.from_id:
|
||||
logger.debug(f"Resolved forward reference: {relation.to_name} -> {target_entity.permalink}")
|
||||
await self.relation_repository.update(relation.id, {
|
||||
"to_id": target_entity.id,
|
||||
"to_name": target_entity.title # Update to actual title
|
||||
})
|
||||
|
||||
# update search index
|
||||
await self.search_service.index_entity(target_entity)
|
||||
|
||||
|
||||
return changes
|
||||
|
||||
Reference in New Issue
Block a user