mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-27 10:19:49 +00:00
- Add migration 000048: vault_documents.path_basename + vault_links.metadata (PG) - Update SQLite schema.sql: add path_basename, metadata columns + v15→v16 markers - Add schema.go backfill: loop-patch v15→v16 + basename extraction for existing docs - Update vault_documents store: SELECT/INSERT with path_basename (PG + SQLite) - Update vault_links store: SELECT/INSERT with metadata (PG + SQLite) - Add vault_source_cleanup: helper to purge orphaned sources + links by source ID - Add basename_helper.go + test: extract filename from path for DB sync - Update version.go: RequiredSchemaVersion → 48, SQLite SchemaVersion → 16 - Update vault_store.go: DeleteLinksBySource + BatchFindByDelegationIDs interface
117 lines
3.5 KiB
Go
117 lines
3.5 KiB
Go
//go:build sqlite || sqliteonly
|
|
|
|
package sqlitestore
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// DeleteLinksBySource removes vault_links rows whose metadata->>'source'
|
|
// equals the given source key. Mirrors PG variant — tenant isolation via
|
|
// subquery on vault_documents since SQLite DELETE lacks USING.
|
|
func (s *SQLiteVaultStore) DeleteLinksBySource(ctx context.Context, tenantID, source string) (int64, error) {
|
|
res, err := s.db.ExecContext(ctx, `
|
|
DELETE FROM vault_links
|
|
WHERE json_extract(metadata, '$.source') = ?
|
|
AND (
|
|
from_doc_id IN (SELECT id FROM vault_documents WHERE tenant_id = ?)
|
|
OR to_doc_id IN (SELECT id FROM vault_documents WHERE tenant_id = ?)
|
|
)
|
|
`, source, tenantID, tenantID)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("delete links by source: %w", err)
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
return n, nil
|
|
}
|
|
|
|
// BatchFindByDelegationIDs returns vault docs sharing any of the given
|
|
// delegation_ids, grouped by delegation_id, capped per bucket at `limit`.
|
|
// Uses the SQLite partial index idx_vault_docs_delegation added by the v15
|
|
// → v16 migration (json_extract based).
|
|
func (s *SQLiteVaultStore) BatchFindByDelegationIDs(
|
|
ctx context.Context,
|
|
tenantID string,
|
|
delegationIDs []string,
|
|
limit int,
|
|
excludeDocIDs []string,
|
|
) (map[string][]store.VaultDocument, error) {
|
|
if len(delegationIDs) == 0 || limit <= 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
delegPH := make([]string, len(delegationIDs))
|
|
args := []any{tenantID}
|
|
for i, d := range delegationIDs {
|
|
delegPH[i] = "?"
|
|
args = append(args, d)
|
|
}
|
|
var excludeClause string
|
|
if len(excludeDocIDs) > 0 {
|
|
excludePH := make([]string, len(excludeDocIDs))
|
|
for i, id := range excludeDocIDs {
|
|
excludePH[i] = "?"
|
|
args = append(args, id)
|
|
}
|
|
excludeClause = " AND id NOT IN (" + strings.Join(excludePH, ",") + ")"
|
|
}
|
|
args = append(args, limit)
|
|
|
|
q := fmt.Sprintf(`
|
|
WITH ranked AS (
|
|
SELECT
|
|
id, tenant_id, agent_id, team_id, scope, custom_scope, path, path_basename,
|
|
title, doc_type, content_hash, summary, metadata, created_at, updated_at,
|
|
json_extract(metadata, '$.delegation_id') AS deleg_id,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY json_extract(metadata, '$.delegation_id')
|
|
ORDER BY created_at DESC, id DESC
|
|
) AS rn
|
|
FROM vault_documents
|
|
WHERE tenant_id = ?
|
|
AND json_extract(metadata, '$.delegation_id') IN (%s)
|
|
%s
|
|
)
|
|
SELECT id, tenant_id, agent_id, team_id, scope, custom_scope, path, path_basename,
|
|
title, doc_type, content_hash, summary, metadata, created_at, updated_at, deleg_id
|
|
FROM ranked
|
|
WHERE rn <= ?
|
|
ORDER BY deleg_id, created_at DESC
|
|
`, strings.Join(delegPH, ","), excludeClause)
|
|
|
|
rows, err := s.db.QueryContext(ctx, q, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("batch find by delegation: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
out := make(map[string][]store.VaultDocument, len(delegationIDs))
|
|
for rows.Next() {
|
|
var doc store.VaultDocument
|
|
var agentID *string
|
|
var metaJSON []byte
|
|
var delegID string
|
|
ca, ua := &sqliteTime{}, &sqliteTime{}
|
|
if err := rows.Scan(
|
|
&doc.ID, &doc.TenantID, &agentID, &doc.TeamID, &doc.Scope, &doc.CustomScope,
|
|
&doc.Path, &doc.PathBasename, &doc.Title, &doc.DocType, &doc.ContentHash,
|
|
&doc.Summary, &metaJSON, ca, ua, &delegID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
doc.AgentID = agentID
|
|
doc.CreatedAt = ca.Time
|
|
doc.UpdatedAt = ua.Time
|
|
if len(metaJSON) > 2 {
|
|
_ = json.Unmarshal(metaJSON, &doc.Metadata)
|
|
}
|
|
out[delegID] = append(out[delegID], doc)
|
|
}
|
|
return out, rows.Err()
|
|
}
|