package vector import ( "strconv" "strings" ) // 本文件是「按 id 取已存向量」的共用件:增量向量化(memory 包的 // embedIncremental)靠它跳过已经算过的行,从而不再每轮把整个分区重新嵌一遍 // ——那是 M15(2026-10-13)登记的请求放大源之一。 // // 只读、参数化:id 一律走绑定参数,查询里不出现任何拼接的取值。 // vectorsQueryChunk 是单条 IN 查询携带的 id 上限。SQLite 的绑定变量上限 // (历史默认 999)之下留出余量,使 2000 条的分区不会因为一条查询太长而 // 失败;超过它的 id 分多条查询,结果仍按入参顺序对齐。 const vectorsQueryChunk = 256 // placeholders 返回 n 个占位符:encode 决定形式(SQLite 的 "?"、PostgreSQL // 的 "$i")。它只拼占位符,不拼任何取值。 func placeholders(n int, encode func(i int) string) string { var b strings.Builder for i := 0; i < n; i++ { if i > 0 { b.WriteByte(',') } b.WriteString(encode(i)) } return b.String() } // sqlitePlaceholders 返回 "?,?,?"。 func sqlitePlaceholders(n int) string { return placeholders(n, func(int) string { return "?" }) } // pgPlaceholders 返回 "$1,$2,..."。 func pgPlaceholders(n int) string { return placeholders(n, func(i int) string { return "$" + strconv.Itoa(i+1) }) } // idArgs 把 ids 转成绑定参数([]any 是 database/sql 要求的入参形态)。 func idArgs(ids []string) []any { args := make([]any, len(ids)) for i, id := range ids { args[i] = id } return args } // alignVectors 把「id → 向量」按入参顺序摊开:未命中的位置(含同一 id 在 // 入参里出现多次时)一律为 nil。因此调用方拿到的切片长度恒等于 ids。 func alignVectors(ids []string, found map[string]Vector) []Vector { out := make([]Vector, len(ids)) if len(found) == 0 { return out } pos := make(map[string][]int, len(ids)) for i, id := range ids { pos[id] = append(pos[id], i) } for id, vec := range found { for _, i := range pos[id] { out[i] = vec } } return out }