- 只翻注释/文档字符串:820 增 / 820 删 严格 1:1;剥掉整行注释与行尾注释尾巴后, 逐文件与 HEAD 比对「代码部分差异 = 0」;裁决/design 编号计数逐项与 HEAD 一致 - 保留可执行 SQL 样例、SQLite 报错原文、pgvector/HNSW/ANN/BLOB 等技术词原文 - 复核:go build 绿 + memory/projectindex/vector 三包测试绿;gofmt 仅剩 2 个既有未格式化文件(改动前即如此,未顺手修)
93 lines
2.5 KiB
Go
93 lines
2.5 KiB
Go
package vector
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewPG_InvalidURL_异常(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := NewPG(context.Background(), " ", 4, testLogger()); !errors.Is(err, ErrInvalidPGURL) {
|
|
t.Fatalf("NewPG(blank url) err = %v, want ErrInvalidPGURL", err)
|
|
}
|
|
}
|
|
|
|
func TestNewPG_InvalidDim_异常(t *testing.T) {
|
|
t.Parallel()
|
|
// dim 在任何连接打开前就校验,因此这里没有网络 I/O。
|
|
if _, err := NewPG(context.Background(), "postgres://host/db", 0, testLogger()); !errors.Is(err, ErrInvalidDim) {
|
|
t.Fatalf("NewPG(dim 0) err = %v, want ErrInvalidDim", err)
|
|
}
|
|
}
|
|
|
|
func TestPGVectorLiteral(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
v Vector
|
|
want string
|
|
}{
|
|
{name: "single", v: Vector{1}, want: "[1]"},
|
|
{name: "three", v: Vector{1.5, 2, -0.25}, want: "[1.5,2,-0.25]"},
|
|
{name: "empty", v: Vector{}, want: "[]"},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if got := pgVectorLiteral(tt.v); got != tt.want {
|
|
t.Fatalf("pgVectorLiteral(%v) = %q, want %q", tt.v, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPG_RequiresEnv 在设置了 DB_PG_URL 时跑一次真实的
|
|
// PostgreSQL+pgvector 往返,否则跳过。本环境默认没有 PG
|
|
// 服务,因此由 pgvector 支撑的 Add/Search 路径只有在
|
|
// 操作者提供带 pgvector 扩展的活数据库时才端到端验证。
|
|
// SQLite 路径(默认)由 sqlite_test.go 套件完整覆盖。
|
|
func TestPG_RequiresEnv(t *testing.T) {
|
|
url := os.Getenv("DB_PG_URL")
|
|
if url == "" {
|
|
t.Skip("DB_PG_URL not set; skipping real PostgreSQL+pgvector integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
idx, err := NewPG(ctx, url, 4, testLogger())
|
|
if err != nil {
|
|
t.Fatalf("NewPG: %v", err)
|
|
}
|
|
idx.withEmbedder(&mockEmbedder{dim: 4})
|
|
defer func() { _ = idx.Close() }()
|
|
|
|
if err := idx.Add(ctx, "a", Vector{1, 0, 0, 0}); err != nil {
|
|
t.Fatalf("Add a: %v", err)
|
|
}
|
|
if err := idx.Add(ctx, "b", Vector{0, 1, 0, 0}); err != nil {
|
|
t.Fatalf("Add b: %v", err)
|
|
}
|
|
|
|
hits, err := idx.Search(ctx, Vector{1, 0, 0, 0}, 1)
|
|
if err != nil {
|
|
t.Fatalf("Search: %v", err)
|
|
}
|
|
if len(hits) != 1 || hits[0].ID != "a" {
|
|
t.Fatalf("pg top hit = %+v, want single a", hits)
|
|
}
|
|
if hits[0].Score < 0.99 {
|
|
t.Fatalf("pg a score = %v, want ~1.0", hits[0].Score)
|
|
}
|
|
|
|
// embedder 已接入,因此 Index.Embed 可用。
|
|
vs, err := idx.Embed(ctx, []string{"hello"})
|
|
if err != nil {
|
|
t.Fatalf("Embed: %v", err)
|
|
}
|
|
if len(vs) != 1 || len(vs[0]) != 4 {
|
|
t.Fatalf("Embed => %d vectors, want one 4-dim", len(vs))
|
|
}
|
|
}
|