f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
156 lines
5.1 KiB
Go
156 lines
5.1 KiB
Go
package vector
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
kitvec "go.kenn.io/kit/vector"
|
|
"go.kenn.io/kit/vector/sqlitevec"
|
|
)
|
|
|
|
// auxSpec is a second, minimal store used to prove two IndexSpecs can share
|
|
// one vectors.db without touching each other's state.
|
|
func auxSpec() IndexSpec {
|
|
return IndexSpec{
|
|
Name: "aux",
|
|
DocsTable: "vector_aux_docs",
|
|
MetaTable: "vector_aux_meta",
|
|
VectorsPrefix: "aux_vectors",
|
|
MirrorDDL: `
|
|
CREATE TABLE IF NOT EXISTS vector_aux_docs (
|
|
doc_key TEXT PRIMARY KEY,
|
|
content TEXT NOT NULL,
|
|
content_hash TEXT NOT NULL,
|
|
embed_gen TEXT
|
|
);
|
|
CREATE TABLE IF NOT EXISTS vector_aux_meta (
|
|
key TEXT PRIMARY KEY, value TEXT NOT NULL
|
|
);
|
|
`,
|
|
MirrorSchemaVersion: "1",
|
|
}
|
|
}
|
|
|
|
func openSpecT(t *testing.T, path string, spec IndexSpec, readOnly bool) *Index {
|
|
t.Helper()
|
|
ix, err := OpenSpec(context.Background(), path, spec, readOnly, 8192)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = ix.Close() })
|
|
return ix
|
|
}
|
|
|
|
func testGen(model string) kitvec.Generation {
|
|
return kitvec.Generation{Model: model, Dimensions: 4, Params: map[string]string{"p": "1"}}
|
|
}
|
|
|
|
// Two stores in one vectors.db: generations and metadata stay disjoint.
|
|
func TestSpecCoexistenceGenerationsAndMetaAreDisjoint(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "vectors.db")
|
|
ctx := context.Background()
|
|
|
|
msg := openSpecT(t, path, MessageIndexSpec(), false)
|
|
aux := openSpecT(t, path, auxSpec(), false)
|
|
|
|
_, err := msg.EnsureGeneration(ctx, testGen("model-msg"), sqlitevec.StateActive)
|
|
require.NoError(t, err)
|
|
_, err = aux.EnsureGeneration(ctx, testGen("model-aux"), sqlitevec.StateActive)
|
|
require.NoError(t, err)
|
|
|
|
msgGens, err := msg.Generations(ctx)
|
|
require.NoError(t, err)
|
|
auxGens, err := aux.Generations(ctx)
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, msgGens, 1)
|
|
require.Len(t, auxGens, 1)
|
|
assert.Equal(t, "model-msg", msgGens[0].Model)
|
|
assert.Equal(t, "model-aux", auxGens[0].Model)
|
|
|
|
// gen_model metadata landed in each store's own meta table.
|
|
_, ok, err := aux.metaGet(ctx, "gen_model:"+msgGens[0].Fingerprint)
|
|
require.NoError(t, err)
|
|
assert.False(t, ok, "message-store gen_model key leaked into aux meta table")
|
|
}
|
|
|
|
// A schema-version reset of one store must leave the other store's tables,
|
|
// metadata, and generations intact.
|
|
func TestSpecResetLeavesOtherStoreIntact(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "vectors.db")
|
|
ctx := context.Background()
|
|
|
|
msg := openSpecT(t, path, MessageIndexSpec(), false)
|
|
aux := openSpecT(t, path, auxSpec(), false)
|
|
_, err := msg.EnsureGeneration(ctx, testGen("model-msg"), sqlitevec.StateActive)
|
|
require.NoError(t, err)
|
|
_, err = aux.EnsureGeneration(ctx, testGen("model-aux"), sqlitevec.StateActive)
|
|
require.NoError(t, err)
|
|
require.NoError(t, msg.Close())
|
|
|
|
// Re-open the message store as if built by an older schema version:
|
|
// the write path must drop and recreate ONLY message-store tables.
|
|
stale := MessageIndexSpec()
|
|
stale.MirrorSchemaVersion = "999"
|
|
msg = openSpecT(t, path, stale, false)
|
|
|
|
msgGens, err := msg.Generations(ctx)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, msgGens, "message store should have been reset")
|
|
|
|
auxGens, err := aux.Generations(ctx)
|
|
require.NoError(t, err)
|
|
require.Len(t, auxGens, 1, "aux store must survive the message-store reset")
|
|
assert.Equal(t, "model-aux", auxGens[0].Model)
|
|
}
|
|
|
|
// The reset's prefix match must be literal, not LIKE: "_" in a prefix such
|
|
// as "message_vectors" is a single-character LIKE wildcard, so a wildcard
|
|
// match would drop a table like "messageXvectors_generations" (X at the
|
|
// wildcard position) that belongs to no store owned by the spec.
|
|
func TestSpecResetPrefixMatchIsLiteral(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "vectors.db")
|
|
ctx := context.Background()
|
|
|
|
msg := openSpecT(t, path, MessageIndexSpec(), false)
|
|
_, err := msg.db.ExecContext(ctx,
|
|
`CREATE TABLE "messageXvectors_generations" (id INTEGER PRIMARY KEY)`)
|
|
require.NoError(t, err)
|
|
require.NoError(t, msg.Close())
|
|
|
|
stale := MessageIndexSpec()
|
|
stale.MirrorSchemaVersion = "999"
|
|
msg = openSpecT(t, path, stale, false)
|
|
|
|
var n int
|
|
require.NoError(t, msg.db.QueryRowContext(ctx, `
|
|
SELECT COUNT(*) FROM sqlite_master
|
|
WHERE type = 'table' AND name = 'messageXvectors_generations'`).Scan(&n))
|
|
assert.Equal(t, 1, n, "wildcard-position decoy table must survive a message-store reset")
|
|
}
|
|
|
|
// The read path fails closed per store: a version mismatch on one store
|
|
// must not affect reads on the other.
|
|
func TestSpecReadPathMismatchIsPerStore(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "vectors.db")
|
|
ctx := context.Background()
|
|
|
|
msg := openSpecT(t, path, MessageIndexSpec(), false)
|
|
aux := openSpecT(t, path, auxSpec(), false)
|
|
_, err := aux.EnsureGeneration(ctx, testGen("model-aux"), sqlitevec.StateActive)
|
|
require.NoError(t, err)
|
|
require.NoError(t, msg.Close())
|
|
|
|
stale := MessageIndexSpec()
|
|
stale.MirrorSchemaVersion = "999"
|
|
msgRO := openSpecT(t, path, stale, true)
|
|
|
|
_, err = msgRO.Generations(ctx)
|
|
assert.ErrorIs(t, err, ErrMirrorVersionMismatch)
|
|
|
|
auxGens, err := aux.Generations(ctx)
|
|
require.NoError(t, err)
|
|
assert.Len(t, auxGens, 1)
|
|
}
|