""" Knowledge Graph DB - Basis-Modul SQLite-basierte Graph-Datenbank fuer pathOS Audit """ import sqlite3 import json import os from pathlib import Path DB_PATH = Path(__file__).parent / "pathos.db" SCHEMA_PATH = Path(__file__).parent / "schema.sql" def get_db(): """Verbindung zur DB herstellen, Schema anlegen falls noetig.""" db = sqlite3.connect(str(DB_PATH)) db.row_factory = sqlite3.Row db.execute("PRAGMA journal_mode=WAL") db.execute("PRAGMA foreign_keys=ON") # Schema anlegen with open(SCHEMA_PATH, "r", encoding="utf-8") as f: db.executescript(f.read()) return db def upsert_node(db, node_id, node_type, name, summary=None, properties=None, source=None): """Node einfuegen oder aktualisieren.""" props_json = json.dumps(properties, ensure_ascii=False) if properties else None db.execute(""" INSERT INTO nodes (id, type, name, summary, properties, source, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now')) ON CONFLICT(id) DO UPDATE SET type=excluded.type, name=excluded.name, summary=excluded.summary, properties=excluded.properties, source=excluded.source, updated_at=datetime('now') """, (node_id, node_type, name, summary, props_json, source)) def add_edge(db, source_id, target_id, relation, properties=None, source=None): """Kante hinzufuegen (Duplikate werden vermieden).""" props_json = json.dumps(properties, ensure_ascii=False) if properties else None # Pruefen ob schon existiert existing = db.execute( "SELECT id FROM edges WHERE source_id=? AND target_id=? AND relation=?", (source_id, target_id, relation) ).fetchone() if existing: db.execute( "UPDATE edges SET properties=?, source=?, updated_at=datetime('now') WHERE id=?", (props_json, source, existing["id"]) ) else: db.execute( "INSERT INTO edges (source_id, target_id, relation, properties, source, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'))", (source_id, target_id, relation, props_json, source) ) def upsert_summary(db, summary_id, topic, content, detail_path=None, tags=None): """Zusammenfassung einfuegen oder aktualisieren.""" db.execute(""" INSERT INTO summaries (id, topic, content, detail_path, tags, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now')) ON CONFLICT(id) DO UPDATE SET topic=excluded.topic, content=excluded.content, detail_path=excluded.detail_path, tags=excluded.tags, updated_at=datetime('now') """, (summary_id, topic, content, detail_path, tags)) # FTS aktualisieren db.execute("INSERT INTO summaries_fts(summaries_fts) VALUES('rebuild')") def add_fact(db, subject, predicate, value, confidence="confirmed", source=None, date=None): """Fakt hinzufuegen.""" db.execute( "INSERT INTO facts (subject, predicate, value, confidence, source, date, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))", (subject, predicate, value, confidence, source, date) ) db.execute("INSERT INTO facts_fts(facts_fts) VALUES('rebuild')") def search_summaries(db, query, limit=5): """Volltextsuche in Zusammenfassungen.""" # FTS5 Query escapen: Woerter mit Anfuehrungszeichen schuetzen safe_query = " ".join(f'"{w}"' for w in query.split()) results = db.execute(""" SELECT s.id, s.topic, s.content, s.detail_path, s.tags FROM summaries_fts fts JOIN summaries s ON s.rowid = fts.rowid WHERE summaries_fts MATCH ? LIMIT ? """, (safe_query, limit)).fetchall() return [dict(r) for r in results] def search_facts(db, query, limit=10): """Volltextsuche in Fakten.""" safe_query = " ".join(f'"{w}"' for w in query.split()) results = db.execute(""" SELECT f.subject, f.predicate, f.value, f.confidence, f.source, f.date FROM facts_fts fts JOIN facts f ON f.rowid = fts.rowid WHERE facts_fts MATCH ? LIMIT ? """, (safe_query, limit)).fetchall() return [dict(r) for r in results] def get_node_context(db, node_id): """Alle Infos zu einem Node + seine Beziehungen.""" node = db.execute("SELECT * FROM nodes WHERE id=?", (node_id,)).fetchone() if not node: return None edges_out = db.execute( "SELECT e.relation, n.name, n.type, e.properties FROM edges e JOIN nodes n ON n.id=e.target_id WHERE e.source_id=?", (node_id,) ).fetchall() edges_in = db.execute( "SELECT e.relation, n.name, n.type, e.properties FROM edges e JOIN nodes n ON n.id=e.source_id WHERE e.target_id=?", (node_id,) ).fetchall() return { "node": dict(node), "outgoing": [dict(e) for e in edges_out], "incoming": [dict(e) for e in edges_in] }