knowledge_* namespace reserved for future agentic learning tables. memory_* namespace used for facts, events, and entity graph. - knowledge_node → memory_entity (reverted) - knowledge_edge → memory_edge (reverted) - Old provenance DAG (child_sha/parent_sha) renamed to memory_edge_provenance via migration 009 - Kept: runtime queries in versioning.rs, UUID casts, init_schema additions
This commit is contained in:
@@ -53,7 +53,7 @@ CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_community_fts
|
||||
-- ============================================
|
||||
-- STEP 3: Create entity table
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS knowledge_node (
|
||||
CREATE TABLE IF NOT EXISTS memory_entity (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(500) NOT NULL,
|
||||
@@ -75,40 +75,40 @@ CREATE TABLE IF NOT EXISTS knowledge_node (
|
||||
);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_project
|
||||
ON knowledge_node(project_id);
|
||||
ON memory_entity(project_id);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_name_embedding
|
||||
ON knowledge_node USING hnsw (name_embedding vector_cosine_ops)
|
||||
ON memory_entity USING hnsw (name_embedding vector_cosine_ops)
|
||||
WITH (m = 16, ef_construction = 200);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_summary_embedding
|
||||
ON knowledge_node USING hnsw (summary_embedding vector_cosine_ops)
|
||||
ON memory_entity USING hnsw (summary_embedding vector_cosine_ops)
|
||||
WITH (m = 16, ef_construction = 200);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_type
|
||||
ON knowledge_node(project_id, entity_type);
|
||||
ON memory_entity(project_id, entity_type);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_community
|
||||
ON knowledge_node(community_id)
|
||||
ON memory_entity(community_id)
|
||||
WHERE community_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_active
|
||||
ON knowledge_node(project_id)
|
||||
ON memory_entity(project_id)
|
||||
WHERE t_expired IS NULL;
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_fts
|
||||
ON knowledge_node USING gin(
|
||||
ON memory_entity USING gin(
|
||||
to_tsvector('english', COALESCE(name, '') || ' ' || COALESCE(summary, ''))
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- STEP 4: Create edge (relationship) table
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS knowledge_edge (
|
||||
CREATE TABLE IF NOT EXISTS memory_edge (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
source_entity_id UUID NOT NULL REFERENCES knowledge_node(id) ON DELETE CASCADE,
|
||||
target_entity_id UUID NOT NULL REFERENCES knowledge_node(id) ON DELETE CASCADE,
|
||||
source_entity_id UUID NOT NULL REFERENCES memory_entity(id) ON DELETE CASCADE,
|
||||
target_entity_id UUID NOT NULL REFERENCES memory_entity(id) ON DELETE CASCADE,
|
||||
relation_type VARCHAR(100) NOT NULL,
|
||||
fact TEXT NOT NULL,
|
||||
fact_embedding VECTOR(768),
|
||||
@@ -123,7 +123,7 @@ CREATE TABLE IF NOT EXISTS knowledge_edge (
|
||||
|
||||
-- Provenance
|
||||
source_episode_id BIGINT REFERENCES memory_node(id) ON DELETE SET NULL,
|
||||
invalidated_by UUID REFERENCES knowledge_edge(id) ON DELETE SET NULL,
|
||||
invalidated_by UUID REFERENCES memory_edge(id) ON DELETE SET NULL,
|
||||
|
||||
-- Contradiction handling
|
||||
contradiction_status VARCHAR(20) DEFAULT 'active'
|
||||
@@ -141,34 +141,34 @@ CREATE TABLE IF NOT EXISTS knowledge_edge (
|
||||
);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_project
|
||||
ON knowledge_edge(project_id);
|
||||
ON memory_edge(project_id);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_source
|
||||
ON knowledge_edge(source_entity_id);
|
||||
ON memory_edge(source_entity_id);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_target
|
||||
ON knowledge_edge(target_entity_id);
|
||||
ON memory_edge(target_entity_id);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_entity_pair
|
||||
ON knowledge_edge(source_entity_id, target_entity_id);
|
||||
ON memory_edge(source_entity_id, target_entity_id);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_embedding
|
||||
ON knowledge_edge USING hnsw (fact_embedding vector_cosine_ops)
|
||||
ON memory_edge USING hnsw (fact_embedding vector_cosine_ops)
|
||||
WITH (m = 16, ef_construction = 200);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_validity
|
||||
ON knowledge_edge(t_valid, t_invalid)
|
||||
ON memory_edge(t_valid, t_invalid)
|
||||
WHERE t_expired IS NULL;
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_active
|
||||
ON knowledge_edge(project_id, contradiction_status)
|
||||
ON memory_edge(project_id, contradiction_status)
|
||||
WHERE t_expired IS NULL AND contradiction_status = 'active';
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_fts
|
||||
ON knowledge_edge USING gin(to_tsvector('english', fact));
|
||||
ON memory_edge USING gin(to_tsvector('english', fact));
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_relation
|
||||
ON knowledge_edge(project_id, relation_type);
|
||||
ON memory_edge(project_id, relation_type);
|
||||
|
||||
-- ============================================
|
||||
-- STEP 5: Compaction audit log
|
||||
@@ -202,8 +202,8 @@ CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_compaction_project
|
||||
CREATE TABLE IF NOT EXISTS contradiction_review_queue (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
new_edge_id UUID NOT NULL REFERENCES knowledge_edge(id) ON DELETE CASCADE,
|
||||
existing_edge_id UUID NOT NULL REFERENCES knowledge_edge(id) ON DELETE CASCADE,
|
||||
new_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE,
|
||||
existing_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE,
|
||||
confidence FLOAT NOT NULL,
|
||||
explanation TEXT,
|
||||
queued_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
|
||||
@@ -41,53 +41,53 @@ CREATE INDEX IF NOT EXISTS idx_memory_projects_visibility
|
||||
-- 2. Add attribution columns to entities
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
ALTER TABLE knowledge_node ADD COLUMN IF NOT EXISTS (
|
||||
ALTER TABLE memory_entity ADD COLUMN IF NOT EXISTS (
|
||||
contributed_by VARCHAR(255), -- JWT "sub" who added this
|
||||
contribution_type VARCHAR(50), -- "extracted" | "manual" | "inferred"
|
||||
contribution_date TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Populate contributed_by with defaults (assume "system" if not present)
|
||||
UPDATE knowledge_node
|
||||
UPDATE memory_entity
|
||||
SET contributed_by = 'system'
|
||||
WHERE contributed_by IS NULL;
|
||||
|
||||
ALTER TABLE knowledge_node
|
||||
ALTER TABLE memory_entity
|
||||
ALTER COLUMN contributed_by SET NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_knowledge_node_contributed_by
|
||||
ON knowledge_node(contributed_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_entity_contributed_by
|
||||
ON memory_entity(contributed_by);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_knowledge_node_contribution_date
|
||||
ON knowledge_node(contribution_date)
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_entity_contribution_date
|
||||
ON memory_entity(contribution_date)
|
||||
WHERE contribution_date IS NOT NULL;
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- 3. Add attribution columns to edges
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
ALTER TABLE knowledge_edge ADD COLUMN IF NOT EXISTS (
|
||||
ALTER TABLE memory_edge ADD COLUMN IF NOT EXISTS (
|
||||
contributed_by VARCHAR(255), -- JWT "sub" who added this
|
||||
contribution_type VARCHAR(50) -- "extracted" | "inferred"
|
||||
);
|
||||
|
||||
-- Populate contributed_by with defaults
|
||||
UPDATE knowledge_edge
|
||||
UPDATE memory_edge
|
||||
SET contributed_by = 'system'
|
||||
WHERE contributed_by IS NULL;
|
||||
|
||||
ALTER TABLE knowledge_edge
|
||||
ALTER TABLE memory_edge
|
||||
ALTER COLUMN contributed_by SET NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_knowledge_edge_contributed_by
|
||||
ON knowledge_edge(contributed_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_edge_contributed_by
|
||||
ON memory_edge(contributed_by);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- 4. Add project_id to entities and edges (optional, for faster filtering)
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
-- ALTER TABLE knowledge_node ADD COLUMN IF NOT EXISTS project_id VARCHAR(255);
|
||||
-- ALTER TABLE knowledge_edge ADD COLUMN IF NOT EXISTS project_id VARCHAR(255);
|
||||
-- ALTER TABLE memory_entity ADD COLUMN IF NOT EXISTS project_id VARCHAR(255);
|
||||
-- ALTER TABLE memory_edge ADD COLUMN IF NOT EXISTS project_id VARCHAR(255);
|
||||
--
|
||||
-- Note: Deferred. Can use project info from path/source instead.
|
||||
|
||||
@@ -102,7 +102,7 @@ SELECT
|
||||
COUNT(*) as entity_count,
|
||||
MAX(contribution_date) as last_contribution,
|
||||
ARRAY_AGG(DISTINCT contribution_type) as contribution_types
|
||||
FROM knowledge_node
|
||||
FROM memory_entity
|
||||
WHERE contribution_date IS NOT NULL
|
||||
GROUP BY contributed_by;
|
||||
|
||||
@@ -112,7 +112,7 @@ SELECT
|
||||
contributed_by,
|
||||
COUNT(*) as count,
|
||||
MAX(contribution_date) as latest
|
||||
FROM knowledge_node
|
||||
FROM memory_entity
|
||||
WHERE contribution_date > CURRENT_TIMESTAMP - INTERVAL '7 days'
|
||||
GROUP BY contributed_by;
|
||||
|
||||
@@ -129,12 +129,12 @@ GROUP BY contributed_by;
|
||||
-- DROP INDEX IF EXISTS idx_memory_projects_visibility;
|
||||
-- DROP TABLE IF EXISTS memory_projects;
|
||||
--
|
||||
-- DROP INDEX IF EXISTS idx_knowledge_node_contributed_by;
|
||||
-- DROP INDEX IF EXISTS idx_knowledge_node_contribution_date;
|
||||
-- ALTER TABLE knowledge_node DROP COLUMN IF EXISTS contributed_by;
|
||||
-- ALTER TABLE knowledge_node DROP COLUMN IF EXISTS contribution_type;
|
||||
-- ALTER TABLE knowledge_node DROP COLUMN IF EXISTS contribution_date;
|
||||
-- DROP INDEX IF EXISTS idx_memory_entity_contributed_by;
|
||||
-- DROP INDEX IF EXISTS idx_memory_entity_contribution_date;
|
||||
-- ALTER TABLE memory_entity DROP COLUMN IF EXISTS contributed_by;
|
||||
-- ALTER TABLE memory_entity DROP COLUMN IF EXISTS contribution_type;
|
||||
-- ALTER TABLE memory_entity DROP COLUMN IF EXISTS contribution_date;
|
||||
--
|
||||
-- DROP INDEX IF EXISTS idx_knowledge_edge_contributed_by;
|
||||
-- ALTER TABLE knowledge_edge DROP COLUMN IF EXISTS contributed_by;
|
||||
-- ALTER TABLE knowledge_edge DROP COLUMN IF EXISTS contribution_type;
|
||||
-- DROP INDEX IF EXISTS idx_memory_edge_contributed_by;
|
||||
-- ALTER TABLE memory_edge DROP COLUMN IF EXISTS contributed_by;
|
||||
-- ALTER TABLE memory_edge DROP COLUMN IF EXISTS contribution_type;
|
||||
|
||||
@@ -18,8 +18,8 @@ CREATE TABLE IF NOT EXISTS temporal_workflow_links (
|
||||
run_id VARCHAR(255), -- Temporal run ID
|
||||
|
||||
-- Memory Entity References (Optional - NULL until linked)
|
||||
entity_id UUID REFERENCES knowledge_node(id) ON DELETE SET NULL,
|
||||
edge_id UUID REFERENCES knowledge_edge(id) ON DELETE SET NULL,
|
||||
entity_id UUID REFERENCES memory_entity(id) ON DELETE SET NULL,
|
||||
edge_id UUID REFERENCES memory_edge(id) ON DELETE SET NULL,
|
||||
node_id BIGINT REFERENCES memory_node(id) ON DELETE SET NULL,
|
||||
|
||||
-- Sync Status
|
||||
|
||||
@@ -47,7 +47,7 @@ CREATE TABLE IF NOT EXISTS community_member_map (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
community_id UUID NOT NULL REFERENCES memory_community(id) ON DELETE CASCADE,
|
||||
entity_id UUID NOT NULL REFERENCES knowledge_node(id) ON DELETE CASCADE,
|
||||
entity_id UUID NOT NULL REFERENCES memory_entity(id) ON DELETE CASCADE,
|
||||
label_propagation_run_id UUID REFERENCES label_propagation_run(id) ON DELETE SET NULL,
|
||||
|
||||
-- Label strength (0-1, higher = stronger membership)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
BEGIN;
|
||||
|
||||
-- Entity version snapshots (immutable)
|
||||
CREATE TABLE IF NOT EXISTS knowledge_node_version (
|
||||
CREATE TABLE IF NOT EXISTS memory_entity_version (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
entity_id VARCHAR(255) NOT NULL,
|
||||
version_num INTEGER NOT NULL,
|
||||
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS knowledge_node_version (
|
||||
);
|
||||
|
||||
-- Edge version snapshots
|
||||
CREATE TABLE IF NOT EXISTS knowledge_edge_version (
|
||||
CREATE TABLE IF NOT EXISTS memory_edge_version (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
edge_id UUID NOT NULL,
|
||||
version_num INTEGER NOT NULL,
|
||||
@@ -52,22 +52,22 @@ CREATE TABLE IF NOT EXISTS knowledge_edge_version (
|
||||
|
||||
-- Indexes for efficient lookups
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_version_entity_id
|
||||
ON knowledge_node_version(entity_id, version_num DESC);
|
||||
ON memory_entity_version(entity_id, version_num DESC);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_version_changed_at
|
||||
ON knowledge_node_version(changed_at DESC);
|
||||
ON memory_entity_version(changed_at DESC);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_version_changed_by
|
||||
ON knowledge_node_version(changed_by);
|
||||
ON memory_entity_version(changed_by);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_version_edge_id
|
||||
ON knowledge_edge_version(edge_id, version_num DESC);
|
||||
ON memory_edge_version(edge_id, version_num DESC);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_version_changed_at
|
||||
ON knowledge_edge_version(changed_at DESC);
|
||||
ON memory_edge_version(changed_at DESC);
|
||||
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_version_changed_by
|
||||
ON knowledge_edge_version(changed_by);
|
||||
ON memory_edge_version(changed_by);
|
||||
|
||||
-- Immutability enforcement: version tables are append-only
|
||||
CREATE OR REPLACE FUNCTION prevent_version_table_modification()
|
||||
@@ -77,19 +77,19 @@ BEGIN
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER knowledge_node_version_immutable
|
||||
BEFORE UPDATE OR DELETE ON knowledge_node_version
|
||||
CREATE TRIGGER memory_entity_version_immutable
|
||||
BEFORE UPDATE OR DELETE ON memory_entity_version
|
||||
FOR EACH ROW EXECUTE FUNCTION prevent_version_table_modification();
|
||||
|
||||
CREATE TRIGGER knowledge_edge_version_immutable
|
||||
BEFORE UPDATE OR DELETE ON knowledge_edge_version
|
||||
CREATE TRIGGER memory_edge_version_immutable
|
||||
BEFORE UPDATE OR DELETE ON memory_edge_version
|
||||
FOR EACH ROW EXECUTE FUNCTION prevent_version_table_modification();
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- Rollback (for reference):
|
||||
-- DROP TRIGGER knowledge_node_version_immutable ON knowledge_node_version;
|
||||
-- DROP TRIGGER knowledge_edge_version_immutable ON knowledge_edge_version;
|
||||
-- DROP TRIGGER memory_entity_version_immutable ON memory_entity_version;
|
||||
-- DROP TRIGGER memory_edge_version_immutable ON memory_edge_version;
|
||||
-- DROP FUNCTION prevent_version_table_modification();
|
||||
-- DROP TABLE knowledge_node_version;
|
||||
-- DROP TABLE knowledge_edge_version;
|
||||
-- DROP TABLE memory_entity_version;
|
||||
-- DROP TABLE memory_edge_version;
|
||||
|
||||
@@ -9,8 +9,8 @@ CREATE TABLE IF NOT EXISTS exact_dedup_record (
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Source and target edges
|
||||
source_edge_id UUID NOT NULL REFERENCES knowledge_edge(id) ON DELETE CASCADE,
|
||||
target_edge_id UUID NOT NULL REFERENCES knowledge_edge(id) ON DELETE CASCADE,
|
||||
source_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE,
|
||||
target_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE,
|
||||
|
||||
-- Match criteria (all must match for exact dedup)
|
||||
source_match BOOLEAN NOT NULL,
|
||||
@@ -51,8 +51,8 @@ CREATE TABLE IF NOT EXISTS stale_gc_record (
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Entity or edge marked for GC
|
||||
entity_id UUID REFERENCES knowledge_node(id) ON DELETE CASCADE,
|
||||
edge_id UUID REFERENCES knowledge_edge(id) ON DELETE CASCADE,
|
||||
entity_id UUID REFERENCES memory_entity(id) ON DELETE CASCADE,
|
||||
edge_id UUID REFERENCES memory_edge(id) ON DELETE CASCADE,
|
||||
|
||||
-- Staleness criteria
|
||||
age_days INT NOT NULL,
|
||||
@@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS semantic_dedup_record (
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Source and target edges
|
||||
source_edge_id UUID NOT NULL REFERENCES knowledge_edge(id) ON DELETE CASCADE,
|
||||
target_edge_id UUID NOT NULL REFERENCES knowledge_edge(id) ON DELETE CASCADE,
|
||||
source_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE,
|
||||
target_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE,
|
||||
|
||||
-- Pre-filter score (0-1, eliminates 60-70% of candidates)
|
||||
prefilter_score FLOAT NOT NULL,
|
||||
@@ -119,7 +119,7 @@ CREATE TABLE IF NOT EXISTS semantic_dedup_record (
|
||||
|
||||
-- Merge strategy (if auto-merged)
|
||||
merge_strategy VARCHAR(50), -- 'keep_superset', 'keep_newer', 'keep_higher_confidence'
|
||||
merged_edge_id UUID REFERENCES knowledge_edge(id) ON DELETE SET NULL,
|
||||
merged_edge_id UUID REFERENCES memory_edge(id) ON DELETE SET NULL,
|
||||
|
||||
-- Metadata
|
||||
detected_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
-- Migration 009: Temporal knowledge graph edge schema (Zep paper §2.2.2)
|
||||
-- Creates knowledge_edge table for entity relationships.
|
||||
-- The old memory_edge (child_sha/parent_sha provenance DAG) is kept as-is.
|
||||
-- Replaces old memory_edge (child_sha/parent_sha provenance DAG)
|
||||
-- with temporal edge schema for the knowledge graph.
|
||||
-- Idempotent: safe to run multiple times.
|
||||
|
||||
-- Create knowledge graph edge table (separate from provenance memory_edge)
|
||||
CREATE TABLE IF NOT EXISTS knowledge_edge (
|
||||
-- Rename old provenance DAG table if it still has child_sha columns
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'memory_edge'
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'memory_edge' AND column_name = 'child_sha'))
|
||||
THEN
|
||||
ALTER TABLE memory_edge RENAME TO memory_edge_provenance;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Create temporal knowledge graph edge table
|
||||
CREATE TABLE IF NOT EXISTS memory_edge (
|
||||
id TEXT PRIMARY KEY,
|
||||
project_id TEXT NOT NULL DEFAULT 'default',
|
||||
source_id TEXT NOT NULL,
|
||||
@@ -27,30 +38,31 @@ CREATE TABLE IF NOT EXISTS knowledge_edge (
|
||||
-- Ensure app user owns the table
|
||||
DO $$ BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app') THEN
|
||||
ALTER TABLE knowledge_edge OWNER TO app;
|
||||
ALTER TABLE memory_edge OWNER TO app;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_knowledge_edge_source ON knowledge_edge(source_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_knowledge_edge_target ON knowledge_edge(target_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_knowledge_edge_project ON knowledge_edge(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_knowledge_edge_relation ON knowledge_edge(relation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_edge_source ON memory_edge(source_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_edge_target ON memory_edge(target_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_edge_project ON memory_edge(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_edge_relation ON memory_edge(relation_type);
|
||||
|
||||
-- Ensure knowledge_node has all columns code expects
|
||||
ALTER TABLE knowledge_node ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE knowledge_node ADD COLUMN IF NOT EXISTS source_count INTEGER DEFAULT 1;
|
||||
-- Ensure memory_entity has all columns code expects
|
||||
ALTER TABLE memory_entity ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE memory_entity ADD COLUMN IF NOT EXISTS source_count INTEGER DEFAULT 1;
|
||||
|
||||
-- Unique constraint for entity upsert dedup
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Dedup existing rows before creating unique index
|
||||
DELETE FROM knowledge_node a USING knowledge_node b
|
||||
DELETE FROM memory_entity a USING memory_entity b
|
||||
WHERE a.project_id = b.project_id AND a.name = b.name
|
||||
AND a.t_created < b.t_created;
|
||||
EXCEPTION WHEN OTHERS THEN NULL;
|
||||
END $$;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_node_project_name ON knowledge_node(project_id, name);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_entity_project_name ON memory_entity(project_id, name);
|
||||
|
||||
-- ROLLBACK instructions:
|
||||
-- DROP TABLE IF EXISTS knowledge_edge;
|
||||
-- DROP INDEX IF EXISTS idx_knowledge_node_project_name;
|
||||
-- DROP TABLE IF EXISTS memory_edge;
|
||||
-- ALTER TABLE IF EXISTS memory_edge_provenance RENAME TO memory_edge;
|
||||
-- DROP INDEX IF EXISTS idx_memory_entity_project_name;
|
||||
|
||||
@@ -25,7 +25,7 @@ impl AuditLogger {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO knowledge_node_version
|
||||
INSERT INTO memory_entity_version
|
||||
(entity_id, version_num, operation, snapshot, changed_by, fields_changed)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
"#,
|
||||
@@ -54,7 +54,7 @@ impl AuditLogger {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO knowledge_edge_version
|
||||
INSERT INTO memory_edge_version
|
||||
(edge_id, version_num, operation, snapshot, changed_by, fields_changed)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
"#,
|
||||
@@ -87,7 +87,7 @@ impl AuditLogger {
|
||||
changed_at,
|
||||
changed_by,
|
||||
COALESCE(fields_changed, '{}') as "fields_changed"
|
||||
FROM knowledge_node_version
|
||||
FROM memory_entity_version
|
||||
WHERE entity_id = $1
|
||||
ORDER BY version_num DESC
|
||||
"#,
|
||||
@@ -113,7 +113,7 @@ impl AuditLogger {
|
||||
changed_at,
|
||||
changed_by,
|
||||
COALESCE(fields_changed, '{}') as "fields_changed"
|
||||
FROM knowledge_edge_version
|
||||
FROM memory_edge_version
|
||||
WHERE edge_id = $1
|
||||
ORDER BY version_num DESC
|
||||
"#,
|
||||
|
||||
@@ -80,7 +80,7 @@ impl PersistentEntityRepo {
|
||||
/// Save entity to database (idempotent)
|
||||
pub async fn save(&self, entity: &Entity) -> Result<String, DbError> {
|
||||
let query = r#"
|
||||
INSERT INTO knowledge_node (id, entity_type, name, description, embedding, created_at, updated_at)
|
||||
INSERT INTO memory_entity (id, entity_type, name, description, embedding, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
@@ -114,7 +114,7 @@ impl PersistentEntityRepo {
|
||||
pub async fn get(&self, id: &str) -> Result<Option<Entity>, DbError> {
|
||||
let query = r#"
|
||||
SELECT id, entity_type, name, description, embedding, created_at, updated_at
|
||||
FROM knowledge_node
|
||||
FROM memory_entity
|
||||
WHERE id = $1 AND deleted_at IS NULL;
|
||||
"#;
|
||||
|
||||
@@ -139,7 +139,7 @@ impl PersistentEntityRepo {
|
||||
pub async fn list(&self, limit: i64, offset: i64) -> Result<Vec<Entity>, DbError> {
|
||||
let query = r#"
|
||||
SELECT id, entity_type, name, description, embedding, created_at, updated_at
|
||||
FROM knowledge_node
|
||||
FROM memory_entity
|
||||
WHERE deleted_at IS NULL
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1 OFFSET $2;
|
||||
@@ -166,7 +166,7 @@ impl PersistentEntityRepo {
|
||||
/// Soft delete entity
|
||||
pub async fn delete(&self, id: &str) -> Result<(), DbError> {
|
||||
let query = r#"
|
||||
UPDATE knowledge_node
|
||||
UPDATE memory_entity
|
||||
SET deleted_at = $1
|
||||
WHERE id = $2;
|
||||
"#;
|
||||
@@ -195,7 +195,7 @@ impl PersistentEdgeRepo {
|
||||
/// Save edge to database (idempotent)
|
||||
pub async fn save(&self, edge: &Edge) -> Result<String, DbError> {
|
||||
let query = r#"
|
||||
INSERT INTO knowledge_edge (id, source_id, target_id, relation_type, fact, strength, t_valid, t_invalid, t_created, t_expired)
|
||||
INSERT INTO memory_edge (id, source_id, target_id, relation_type, fact, strength, t_valid, t_invalid, t_created, t_expired)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
strength = EXCLUDED.strength,
|
||||
@@ -232,7 +232,7 @@ impl PersistentEdgeRepo {
|
||||
pub async fn get(&self, id: &str) -> Result<Option<Edge>, DbError> {
|
||||
let query = r#"
|
||||
SELECT id, source_id, target_id, relation_type, fact, strength, t_valid, t_invalid, t_created, t_expired
|
||||
FROM knowledge_edge
|
||||
FROM memory_edge
|
||||
WHERE id = $1 AND t_expired IS NULL;
|
||||
"#;
|
||||
|
||||
@@ -260,7 +260,7 @@ impl PersistentEdgeRepo {
|
||||
pub async fn list_from(&self, source_id: &str, limit: i64) -> Result<Vec<Edge>, DbError> {
|
||||
let query = r#"
|
||||
SELECT id, source_id, target_id, relation_type, fact, strength, t_valid, t_invalid, t_created, t_expired
|
||||
FROM knowledge_edge
|
||||
FROM memory_edge
|
||||
WHERE source_id = $1 AND t_expired IS NULL AND t_invalid IS NULL
|
||||
ORDER BY t_created DESC
|
||||
LIMIT $2;
|
||||
@@ -290,7 +290,7 @@ impl PersistentEdgeRepo {
|
||||
/// Mark edge as contradicted (soft delete)
|
||||
pub async fn invalidate(&self, id: &str) -> Result<(), DbError> {
|
||||
let query = r#"
|
||||
UPDATE knowledge_edge
|
||||
UPDATE memory_edge
|
||||
SET t_invalid = $1
|
||||
WHERE id = $2;
|
||||
"#;
|
||||
|
||||
@@ -221,7 +221,7 @@ pub async fn init_schema(pool: &PgPool) -> Result<()> {
|
||||
// Memory entity table (temporal knowledge graph)
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS knowledge_node (
|
||||
CREATE TABLE IF NOT EXISTS memory_entity (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(500) NOT NULL,
|
||||
@@ -245,17 +245,17 @@ pub async fn init_schema(pool: &PgPool) -> Result<()> {
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_entity_project ON knowledge_node(project_id)")
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_entity_project ON memory_entity(project_id)")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_entity_type ON knowledge_node(project_id, entity_type)")
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_entity_type ON memory_entity(project_id, entity_type)")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
// Memory edge table (temporal knowledge graph)
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS knowledge_edge (
|
||||
CREATE TABLE IF NOT EXISTS memory_edge (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id VARCHAR(255) NOT NULL,
|
||||
source_id UUID NOT NULL,
|
||||
@@ -276,16 +276,16 @@ pub async fn init_schema(pool: &PgPool) -> Result<()> {
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_edge_project ON knowledge_edge(project_id)")
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_edge_project ON memory_edge(project_id)")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_edge_source ON knowledge_edge(source_id)")
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_edge_source ON memory_edge(source_id)")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_edge_target ON knowledge_edge(target_id)")
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_edge_target ON memory_edge(target_id)")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
tracing::info!("Database schema initialized (including knowledge_node + knowledge_edge)");
|
||||
tracing::info!("Database schema initialized (including memory_entity + memory_edge)");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ impl EntityVersioningService {
|
||||
changed_at,
|
||||
changed_by,
|
||||
COALESCE(fields_changed, '{}') as fields_changed
|
||||
FROM knowledge_node_version
|
||||
FROM memory_entity_version
|
||||
WHERE entity_id = $1
|
||||
ORDER BY version_num DESC
|
||||
"#,
|
||||
@@ -75,7 +75,7 @@ impl EntityVersioningService {
|
||||
changed_at,
|
||||
changed_by,
|
||||
COALESCE(fields_changed, '{}') as fields_changed
|
||||
FROM knowledge_node_version
|
||||
FROM memory_entity_version
|
||||
WHERE entity_id = $1 AND version_num = $2
|
||||
"#,
|
||||
)
|
||||
@@ -112,7 +112,7 @@ impl EntityVersioningService {
|
||||
changed_at,
|
||||
changed_by,
|
||||
COALESCE(fields_changed, '{}') as fields_changed
|
||||
FROM knowledge_node_version
|
||||
FROM memory_entity_version
|
||||
WHERE entity_id = $1 AND changed_at <= $2
|
||||
ORDER BY version_num DESC
|
||||
LIMIT 1
|
||||
@@ -146,7 +146,7 @@ impl EdgeVersioningService {
|
||||
changed_at,
|
||||
changed_by,
|
||||
COALESCE(fields_changed, '{}') as fields_changed
|
||||
FROM knowledge_edge_version
|
||||
FROM memory_edge_version
|
||||
WHERE edge_id = $1
|
||||
ORDER BY version_num DESC
|
||||
"#,
|
||||
@@ -172,7 +172,7 @@ impl EdgeVersioningService {
|
||||
changed_at,
|
||||
changed_by,
|
||||
COALESCE(fields_changed, '{}') as fields_changed
|
||||
FROM knowledge_edge_version
|
||||
FROM memory_edge_version
|
||||
WHERE edge_id = $1 AND version_num = $2
|
||||
"#,
|
||||
)
|
||||
@@ -190,7 +190,7 @@ impl EdgeVersioningService {
|
||||
changed_at,
|
||||
changed_by,
|
||||
COALESCE(fields_changed, '{}') as fields_changed
|
||||
FROM knowledge_edge_version
|
||||
FROM memory_edge_version
|
||||
WHERE edge_id = $1 AND version_num = $2
|
||||
"#,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user