Files
zclaw_openfang/crates/zclaw-saas/migrations/20260402000002_knowledge_base.sql
iven 7e4b787d5c fix(knowledge): deep audit — 18 bugs fixed across backend + frontend
CRITICAL:
- Migration permission seed WHERE name → WHERE id (matched 0 rows, all KB APIs broken)

HIGH:
- analytics_quality SQL alias + missing comma fix
- search() duplicate else block compile error
- chunk_content duplicate var declarations + type mismatch
- SQL invalid escape sequences
- delete_category missing rows_affected check

MEDIUM:
- analytics_overview hit_rate vs positive_feedback_rate separation
- analytics_quality GROUP BY kc.id,kc.name (same-name category merge)
- update_category handler trim + empty name validation
- update_item duplicate VALID_STATUSES inside transaction
- page_size max(1) lower bound in list handlers
- batch_create title/content/length validation
- embedding dispatch silent error → tracing::warn
- Version modal close clears detailItem state
- Search empty state distinguishes not-searched vs no-results
- Create modal cancel resets form
2026-04-02 19:07:42 +08:00

124 lines
4.7 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Migration: Knowledge Base tables with pgvector support
-- 5 tables: knowledge_categories, knowledge_items, knowledge_chunks,
-- knowledge_versions, knowledge_usage
-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
-- 行业分类树
CREATE TABLE IF NOT EXISTS knowledge_categories (
id TEXT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
parent_id TEXT REFERENCES knowledge_categories(id) ON DELETE RESTRICT,
icon VARCHAR(50),
sort_order INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CHECK (id != parent_id)
);
CREATE INDEX IF NOT EXISTS idx_kc_parent ON knowledge_categories(parent_id);
-- 知识条目
CREATE TABLE IF NOT EXISTS knowledge_items (
id TEXT PRIMARY KEY,
category_id TEXT NOT NULL REFERENCES knowledge_categories(id) ON DELETE RESTRICT,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
keywords TEXT[] DEFAULT '{}',
related_questions TEXT[] DEFAULT '{}',
priority INT DEFAULT 0,
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'archived', 'deprecated', 'draft')),
version INT DEFAULT 1,
source VARCHAR(50) DEFAULT 'manual',
tags TEXT[] DEFAULT '{}',
created_by TEXT NOT NULL REFERENCES accounts(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CHECK (length(content) <= 100000)
);
CREATE INDEX IF NOT EXISTS idx_ki_category ON knowledge_items(category_id);
CREATE INDEX IF NOT EXISTS idx_ki_status_updated ON knowledge_items(status, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_ki_keywords ON knowledge_items USING GIN(keywords);
-- 知识分块RAG 检索核心)
CREATE TABLE IF NOT EXISTS knowledge_chunks (
id TEXT PRIMARY KEY,
item_id TEXT NOT NULL REFERENCES knowledge_items(id) ON DELETE CASCADE,
chunk_index INT NOT NULL,
content TEXT NOT NULL,
embedding vector(1536),
keywords TEXT[] DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_kchunks_item_idx ON knowledge_chunks(item_id, chunk_index);
CREATE INDEX IF NOT EXISTS idx_kchunks_item ON knowledge_chunks(item_id);
CREATE INDEX IF NOT EXISTS idx_kchunks_keywords ON knowledge_chunks USING GIN(keywords);
-- 向量相似度索引HNSW无需预填充数据
-- 仅在有数据后创建此索引可提升性能,这里预创建
CREATE INDEX IF NOT EXISTS idx_kchunks_embedding ON knowledge_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128);
-- 版本快照
CREATE TABLE IF NOT EXISTS knowledge_versions (
id TEXT PRIMARY KEY,
item_id TEXT NOT NULL REFERENCES knowledge_items(id) ON DELETE CASCADE,
version INT NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
keywords TEXT[] DEFAULT '{}',
related_questions TEXT[] DEFAULT '{}',
change_summary TEXT,
created_by TEXT NOT NULL REFERENCES accounts(id),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_kv_item ON knowledge_versions(item_id);
-- 使用追踪
CREATE TABLE IF NOT EXISTS knowledge_usage (
id TEXT PRIMARY KEY,
item_id TEXT REFERENCES knowledge_items(id) ON DELETE SET NULL,
chunk_id TEXT REFERENCES knowledge_chunks(id) ON DELETE SET NULL,
session_id VARCHAR(100),
query_text TEXT,
relevance_score FLOAT,
was_injected BOOLEAN DEFAULT FALSE,
agent_feedback VARCHAR(20) CHECK (agent_feedback IN ('positive', 'negative')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_ku_item ON knowledge_usage(item_id) WHERE item_id IS NOT NULL;
-- BRIN 索引:追加写入的时间序列数据比 B-tree 更高效
CREATE INDEX IF NOT EXISTS idx_ku_created_brin ON knowledge_usage USING brin(created_at);
-- 权限种子数据(使用 jsonb 操作避免 REPLACE 脆弱性)
UPDATE roles
SET permissions = (
SELECT '[' || string_agg('"' || elem || '"', ', ') || ']'
FROM (
SELECT DISTINCT elem
FROM json_array_elements_text(permissions::json) AS elem
UNION ALL SELECT 'knowledge:read'
UNION ALL SELECT 'knowledge:write'
UNION ALL SELECT 'knowledge:admin'
UNION ALL SELECT 'knowledge:search'
) sub
)
WHERE id = 'super_admin'
AND permissions NOT LIKE '%knowledge:read%';
UPDATE roles
SET permissions = (
SELECT '[' || string_agg('"' || elem || '"', ', ') || ']'
FROM (
SELECT DISTINCT elem
FROM json_array_elements_text(permissions::json) AS elem
UNION ALL SELECT 'knowledge:read'
UNION ALL SELECT 'knowledge:write'
UNION ALL SELECT 'knowledge:search'
) sub
)
WHERE id = 'admin'
AND permissions NOT LIKE '%knowledge:read%';