package store import ( "context" "database/sql" "errors" "fmt" ) // metaKeyEmbedderID names the embedder that wrote the stored vectors. // // Why one value for the whole DB and not a column on every vector row: the // vectors are only ever rewritten all at once (one backfill re-embeds every // note and fact together), so a per-row marker would hold the same string in // every row and cost a column on two tables for nothing. const metaKeyEmbedderID = "embedder_id" // Meta reads a single value from the meta table. Missing key ⇒ empty string. func (s *Store) Meta(ctx context.Context, key string) (string, error) { var v string err := s.db.QueryRowContext(ctx, `SELECT value FROM meta WHERE key = ?`, key).Scan(&v) if errors.Is(err, sql.ErrNoRows) { return "", nil } if err != nil { return "", fmt.Errorf("read meta %s: %w", key, err) } return v, nil } // SetMeta writes (or overwrites) a single meta value. func (s *Store) SetMeta(ctx context.Context, key, value string) error { _, err := s.db.ExecContext(ctx, `INSERT INTO meta (key, value) VALUES (?,?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`, key, value) if err != nil { return fmt.Errorf("write meta %s: %w", key, err) } return nil } // CheckEmbedder compares the embedder now configured against the one that // wrote the stored vectors. // // A DB that has never recorded one is claimed for the current embedder: either // it is fresh (nothing stored yet, nothing to fix) or it predates this marker. // Returns the stored id and whether it differs from the current one. // // Vectors from two different models live in different spaces, so cosine // between them is noise rather than a low score — and both of our models are // 384-dimensional, so nothing else catches it. // // TODO(#378): on a mismatch, run the one-shot backfill here — re-embed every // stored note and fact text with the current embedder (EmbedPassage side), // write the vectors back, then SetMeta the current id. func (s *Store) CheckEmbedder(ctx context.Context, currentID string) (stored string, mismatch bool, err error) { stored, err = s.Meta(ctx, metaKeyEmbedderID) if err != nil { return "", false, err } if stored == "" { return currentID, false, s.SetMeta(ctx, metaKeyEmbedderID, currentID) } return stored, stored != currentID, nil }