use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
use akar_common::types::Value;
use akar_common::vector::DataChunk;
use akar_storage::table::TableCatalog;
use std::sync::Arc;
pub struct PhysicalVectorSimilarityScan {
pub index_name: String,
pub index_id: u64,
pub query_vector: Vec<f64>,
pub top_k: u64,
pub table_name: String,
pub table_catalog: Option<Arc<TableCatalog>>,
}
impl PhysicalOperatorExec for PhysicalVectorSimilarityScan {
fn operator_type(&self) -> &str {
"vector_similarity_scan"
}
fn execute(&self, _input: Vec<DataChunk>) -> OperatorResult {
let tc = self
.table_catalog
.clone()
.ok_or_else(|| "No table catalog available for VectorSimilarityScan".to_string())?;
let vi = if self.index_name.is_empty() {
let index_name = {
let mut found_name = String::new();
for entry in tc.all_vector_indexes() {
if entry.table_name == self.table_name {
found_name = entry.name.clone();
break;
}
}
if found_name.is_empty() {
return Err(format!("No vector index found on table '{}'", self.table_name).into());
}
found_name
};
tc.get_vector_index_by_name(&index_name)
.ok_or_else(|| format!("Vector index '{}' not found", index_name))?
} else {
tc.get_vector_index_by_name(&self.index_name)
.ok_or_else(|| format!("Vector index '{}' not found", self.index_name))?
};
let results = vi.hnsw().search(&self.query_vector, self.top_k as usize);
drop(vi);
if results.is_empty() {
return Ok(vec![DataChunk::new(vec![], vec![])]);
}
let node_table = tc
.get_node_table_by_name(&self.table_name)
.ok_or_else(|| format!("Node table '{}' not found", self.table_name))?;
let num_cols = node_table.columns.len();
let num_results = results.len();
let mut output_columns: Vec<Vec<Value>> = vec![Vec::with_capacity(num_results); num_cols + 2];
for (dist, row_id) in &results {
output_columns[num_cols].push(Value::Double(*dist));
output_columns[num_cols + 1].push(Value::Int64(*row_id as i64));
for (col_idx, out_col) in output_columns.iter_mut().enumerate().take(num_cols) {
match node_table.get_value(*row_id, col_idx) {
Some(val) => out_col.push(val.clone()),
None => out_col.push(Value::Null),
}
}
}
use akar_common::arrow_vector::{ArrowVector, arrow_array_from_values};
use akar_common::types::{PhysicalTypeID, physical_type_from_logical};
let mut fields = Vec::with_capacity(num_cols + 2);
let mut field_types = Vec::with_capacity(num_cols + 2);
for col_idx in 0..num_cols {
let phys_type = physical_type_from_logical(node_table.columns[col_idx].logical_type);
fields.push(ArrowVector::new(
arrow_array_from_values(&output_columns[col_idx]),
phys_type,
));
field_types.push(phys_type);
}
fields.push(ArrowVector::new(
arrow_array_from_values(&output_columns[num_cols]),
PhysicalTypeID::Double,
));
field_types.push(PhysicalTypeID::Double);
fields.push(ArrowVector::new(
arrow_array_from_values(&output_columns[num_cols + 1]),
PhysicalTypeID::Int64,
));
field_types.push(PhysicalTypeID::Int64);
let arrow_fields = fields.iter().map(|v| v.array.clone()).collect::<Vec<_>>();
let arrow_field_types = field_types;
let mut field_names: Vec<String> = node_table.columns.iter().map(|c| c.name.clone()).collect();
field_names.push("distance".to_string());
field_names.push("_id".to_string());
Ok(vec![DataChunk {
fields: arrow_fields,
field_types: arrow_field_types,
size: num_results,
field_names,
sel_vector: None,
}])
}
}