///|
/// CoreStore: contiguous vector storage + id mapping.
/// Provides a compact foundation for both bruteforce and approximate search.
/// Normalizes vectors for cosine metric, maintains id->index mapping,
/// and supports O(1) compaction on remove.
pub struct CoreStore {
dim : Int
metric : @types.Metric
mut ids : Array[@types.VectorId]
mut data : Array[Double] // flattened: data[i*dim..(i+1)*dim] is vector i
mut attrs : Array[@types.Attrs]
pos : Map[@types.VectorId, Int] // id -> index mapping
mut capacity : Int
mut count : Int
}
///|
/// Create a new CoreStore
pub fn CoreStore::new(
dim : Int,
metric : @types.Metric,
capacity? : Int = 1024,
) -> CoreStore {
guard dim > 0 else { abort("dim must be positive") }
let cap = if capacity < 1 { 1 } else { capacity }
{
dim,
metric,
ids: Array::make(cap, @types.Int64Id(0L)),
data: Array::make(cap * dim, 0.0),
attrs: Array::make(cap, @types.empty_attrs()),
pos: {},
capacity: cap,
count: 0,
}
}
///|
/// Get the current size (number of vectors)
pub fn CoreStore::size(self : CoreStore) -> Int {
self.count
}
///|
/// Get the current capacity
pub fn CoreStore::capacity(self : CoreStore) -> Int {
self.capacity
}
///|
/// Check if an id exists in the store
pub fn CoreStore::has(self : CoreStore, id : @types.VectorId) -> Bool {
self.pos.contains(id)
}
///|
/// Ensure capacity for additional elements
pub fn CoreStore::ensure_capacity(self : CoreStore, extra : Int) -> Bool {
if self.count + extra <= self.capacity {
return false
}
// Grow capacity
let mut new_cap = self.capacity
while new_cap < self.count + extra {
new_cap = new_cap * 2
}
// Resize ids
let new_ids = Array::make(new_cap, @types.Int64Id(0L))
for i in 0.. Unit {
let base = index * self.dim
for i in 0.. 0.0 { 1.0 / sum.sqrt() } else { 1.0 }
for i in 0.. Int? {
self.pos.get(id)
}
///|
/// Get vector and attrs by index
pub fn CoreStore::get_by_index(
self : CoreStore,
index : Int,
) -> @types.VectorRecord {
let base = index * self.dim
let vec = Array::make(self.dim, 0.0)
for i in 0.. @types.VectorId? {
if index < 0 || index >= self.count {
None
} else {
Some(self.ids[index])
}
}
///|
/// Get vector and attrs by id
pub fn CoreStore::get(
self : CoreStore,
id : @types.VectorId,
) -> @types.VectorRecord? {
match self.get_index(id) {
Some(idx) => Some(self.get_by_index(idx))
None => None
}
}
///|
/// Add or update a vector. Returns (index, created).
/// If upsert is false and id exists, raises error.
pub fn CoreStore::add_or_update(
self : CoreStore,
id : @types.VectorId,
vector : Array[Double],
attrs : @types.Attrs,
upsert? : Bool = false,
) -> (Int, Bool) {
guard vector.length() == self.dim else {
abort(
"dim mismatch: got " +
vector.length().to_string() +
", want " +
self.dim.to_string(),
)
}
match self.get_index(id) {
Some(idx) => {
guard upsert else {
abort("id already exists: " + id.to_display_string())
}
self.write_vector_at(idx, vector)
self.attrs[idx] = attrs.copy() // Defensive copy to prevent external mutation
(idx, false)
}
None => {
let _ = self.ensure_capacity(1)
let idx = self.count
self.ids[idx] = id
self.attrs[idx] = attrs.copy() // Defensive copy to prevent external mutation
self.pos.set(id, idx)
self.write_vector_at(idx, vector)
self.count = self.count + 1
(idx, true)
}
}
}
///|
/// Move info returned by remove
pub struct MoveInfo {
moved_id : @types.VectorId?
moved_from : Int?
moved_to : Int?
} derive(Debug)
///|
pub impl Show for MoveInfo with fn output(self, logger) {
logger.write_string("{ moved_id: ")
Debug::to_repr(self.moved_id).output(logger)
logger.write_string(", moved_from: ")
Debug::to_repr(self.moved_from).output(logger)
logger.write_string(", moved_to: ")
Debug::to_repr(self.moved_to).output(logger)
logger.write_string(" }")
}
///|
/// Remove a vector by id. Returns move info if compaction occurred.
pub fn CoreStore::remove_by_id(
self : CoreStore,
id : @types.VectorId,
) -> MoveInfo? {
match self.get_index(id) {
None => None
Some(idx) => {
let last = self.count - 1
if idx != last {
// Swap with last element
self.ids[idx] = self.ids[last]
self.attrs[idx] = self.attrs[last]
let src_base = last * self.dim
let dst_base = idx * self.dim
for i in 0.. Bool {
match self.get_index(id) {
Some(idx) => {
self.attrs[idx] = attrs.copy() // Defensive copy to prevent external mutation
true
}
None => false
}
}
///|
/// Shrink backing arrays to fit current count
pub fn CoreStore::shrink_to_fit(self : CoreStore) -> Unit {
let n = if self.count < 1 { 1 } else { self.count }
if n >= self.capacity {
return
}
// Shrink ids
let new_ids = Array::make(n, @types.Int64Id(0L))
for i in 0.. Array[Double] {
if self.metric != @types.Cosine {
return q
}
@vecmath.normalize(q)
}
///|
/// Serialize CoreStore to bytes
pub fn CoreStore::serialize(self : CoreStore) -> Bytes {
let w = @binary.BinaryWriter::new()
// Store version: magic 0x53544F52 ("STOR") + version 2
w.push_u32(0x53544F52U)
w.push_u32(2U)
// Write header: dim, metric, count
w.push_u32(self.dim.reinterpret_as_uint())
w.push_u32(
match self.metric {
@types.Cosine => 0U
@types.L2 => 1U
@types.Dot => 2U
},
)
w.push_u32(self.count.reinterpret_as_uint())
// Write ids: length-prefixed wire bytes via VectorId::to_wire_bytes.
for i in 0.. w.push_u32(0U)
Some(@types.Null) => w.push_u32(0U)
Some(@types.Bool(b)) => {
w.push_u32(1U)
w.push_u32(if b { 1U } else { 0U })
}
Some(@types.AttrValue::Int(n)) => {
w.push_u32(2U)
w.push_i64(n)
}
Some(@types.Float(f)) => {
w.push_u32(3U)
w.push_f64(f)
}
Some(@types.AttrValue::String(s)) => {
w.push_u32(4U)
let s_chars = s.to_array()
w.push_u32(s_chars.length().reinterpret_as_uint())
for c in s_chars {
w.push_u32(c.to_uint())
}
}
}
}
}
w.concat()
}
///|
/// Deserialize CoreStore from bytes
pub fn CoreStore::deserialize(data : Bytes) -> CoreStore {
let r = @binary.BinaryReader::new(data)
// Detect format: v2+ starts with magic 0x53544F52 ("STOR")
let first_u32 = r.read_u32()
let store_version : UInt = if first_u32 == 0x53544F52U {
r.read_u32()
} else {
1U // legacy format — first_u32 is dim
}
// Read header
let dim = if first_u32 == 0x53544F52U {
r.read_u32().reinterpret_as_int()
} else {
first_u32.reinterpret_as_int()
}
let metric = match r.read_u32() {
0U => @types.Cosine
1U => @types.L2
_ => @types.Dot
}
let count = r.read_u32().reinterpret_as_int()
let store = CoreStore::new(dim, metric, capacity=count)
// Read ids
for i in 0.. v
None => @types.Int64Id(0L)
}
} else {
@types.Int64Id(
// Int64Id: 8 bytes
r.read_u64().reinterpret_as_int64(),
)
}
} else {
// v2+: length-prefixed wire bytes via VectorId::from_wire_bytes.
let wire_len = r.read_u32().reinterpret_as_int()
let wire = r.read_bytes(wire_len)
match @types.VectorId::from_wire_bytes(wire) {
Some(v) => v
None => @types.Int64Id(0L)
}
}
store.ids[i] = id
store.pos.set(id, i)
}
// Read data
for i in 0..<(count * dim) {
store.data[i] = r.read_f64()
}
// Read attrs
for i in 0.. @types.Null
1U => @types.Bool(r.read_u32() != 0U)
2U => @types.AttrValue::Int(r.read_i64())
3U => @types.Float(r.read_f64())
_ => {
let s_len = r.read_u32().reinterpret_as_int()
let s_chars : Array[Char] = []
for _ in 0..