package index import ( "database/sql" "encoding/json" "strings" ) // FTSResult 全文搜索结果 type FTSResult struct { ID int64 Path string Title string Section string Rank float64 } // CreateFTS 创建 FTS5 虚拟表 func (s *Store) CreateFTS() error { _, err := s.db.Exec(` CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5( title, content, tags, entities, content='nodes', content_rowid='id' ) `) return err } // PopulateFTS 填充 FTS 索引 func (s *Store) PopulateFTS() error { _, err := s.db.Exec(` INSERT INTO nodes_fts(rowid, title, content, tags, entities) SELECT id, title, content_fts, tags, entities FROM nodes `) return err } // FTSSearch 全文搜索 func (s *Store) FTSSearch(keywords []string, limit int) ([]FTSResult, error) { if len(keywords) == 0 { return nil, nil } // 构建 FTS5 查询 query := strings.Join(keywords, " OR ") rows, err := s.db.Query(` SELECT n.id, n.path, n.title, n.section, fts.rank FROM nodes_fts fts JOIN nodes n ON n.id = fts.rowid WHERE nodes_fts MATCH ? ORDER BY fts.rank LIMIT ? `, query, limit) if err != nil { return nil, err } defer rows.Close() var results []FTSResult for rows.Next() { var r FTSResult if err := rows.Scan(&r.ID, &r.Path, &r.Title, &r.Section, &r.Rank); err != nil { return nil, err } results = append(results, r) } return results, nil } // GetNodeContent 获取节点内容 func (s *Store) GetNodeContent(id int64) (string, []string, []string, error) { var content, tagsJSON, entitiesJSON string err := s.db.QueryRow("SELECT content_fts, tags, entities FROM nodes WHERE id = ?", id). Scan(&content, &tagsJSON, &entitiesJSON) if err == sql.ErrNoRows { return "", nil, nil, nil } if err != nil { return "", nil, nil, err } // 解析 JSON 数组 var tags, entities []string if tagsJSON != "" { if err := json.Unmarshal([]byte(tagsJSON), &tags); err != nil { tags = nil } } if entitiesJSON != "" { if err := json.Unmarshal([]byte(entitiesJSON), &entities); err != nil { entities = nil } } return content, tags, entities, nil }