use super::vector_primary_insert::sql_values_to_vector;
use crate::error::{Result, SqlError};
use crate::types::*;
pub(crate) fn is_vector_primary(info: &CollectionInfo) -> bool {
info.primary == nodedb_types::PrimaryEngine::Vector && info.vector_primary.is_some()
}
pub(crate) fn refuse_vector_primary_shape(info: &CollectionInfo, shape: &str) -> Result<()> {
if !is_vector_primary(info) {
return Ok(());
}
Err(SqlError::Unsupported {
detail: format!(
"{shape} is not supported on vector-primary collection '{}'; use UPDATE ... WHERE, \
DELETE ... WHERE, or UPSERT INTO",
info.name
),
})
}
pub(crate) struct VectorPrimaryUpdateParams<'a> {
pub collection: &'a str,
pub info: &'a CollectionInfo,
pub vpc: &'a nodedb_types::VectorPrimaryConfig,
pub assignments: Vec<(String, SqlExpr)>,
pub filters: Vec<Filter>,
pub target_keys: Vec<SqlValue>,
pub returning: bool,
}
pub(crate) fn build_vector_primary_update_plan(
params: VectorPrimaryUpdateParams<'_>,
) -> Result<Vec<SqlPlan>> {
let VectorPrimaryUpdateParams {
collection,
info,
vpc,
assignments,
filters,
target_keys,
returning,
} = params;
let mut new_vector: Option<Vec<f32>> = None;
let mut payload_assignments = Vec::with_capacity(assignments.len());
for (column, expr) in assignments {
if info.primary_key.as_deref() == Some(column.as_str()) {
return Err(SqlError::Unsupported {
detail: format!(
"UPDATE of primary key '{column}' on vector-primary collection \
'{collection}' is not supported; DELETE the row and INSERT it under \
the new key"
),
});
}
if column == vpc.vector_field {
new_vector = Some(vector_literal(&column, &expr)?);
continue;
}
payload_assignments.push((column, expr));
}
Ok(vec![SqlPlan::VectorPrimaryUpdate(
VectorPrimaryUpdatePlan {
collection: collection.to_string(),
field: vpc.vector_field.clone(),
quantization: vpc.quantization,
storage_dtype: vpc.storage_dtype,
payload_indexes: vpc.payload_indexes.clone(),
new_vector,
assignments: payload_assignments,
filters,
target_keys,
returning,
primary_key: info.primary_key.clone(),
},
)])
}
pub(crate) fn build_vector_primary_delete_plan(
collection: &str,
info: &CollectionInfo,
vpc: &nodedb_types::VectorPrimaryConfig,
filters: Vec<Filter>,
target_keys: Vec<SqlValue>,
) -> Vec<SqlPlan> {
vec![SqlPlan::VectorPrimaryDelete(VectorPrimaryDeletePlan {
collection: collection.to_string(),
field: vpc.vector_field.clone(),
filters,
target_keys,
primary_key: info.primary_key.clone(),
})]
}
pub(crate) fn build_vector_primary_truncate_plan(
collection: &str,
vpc: &nodedb_types::VectorPrimaryConfig,
restart_identity: bool,
) -> SqlPlan {
SqlPlan::VectorPrimaryTruncate(VectorPrimaryTruncatePlan {
collection: collection.to_string(),
field: vpc.vector_field.clone(),
restart_identity,
})
}
fn vector_literal(field: &str, expr: &SqlExpr) -> Result<Vec<f32>> {
match expr {
SqlExpr::Literal(SqlValue::Array(items)) => sql_values_to_vector(field, items),
SqlExpr::ArrayLiteral(elems) => {
let items = elems
.iter()
.map(|e| match e {
SqlExpr::Literal(v) => Ok(v.clone()),
other => Err(SqlError::Unsupported {
detail: format!(
"vector field '{field}' must be assigned an array of numeric \
literals, got element {other:?}"
),
}),
})
.collect::<Result<Vec<SqlValue>>>()?;
sql_values_to_vector(field, &items)
}
other => Err(SqlError::Unsupported {
detail: format!(
"vector field '{field}' must be assigned an array literal, got {other:?}"
),
}),
}
}