use crate::error::{Result, SqlError};
use crate::types::*;
pub(crate) struct VectorPrimaryInsertParams<'a> {
pub collection: &'a str,
pub vpc: &'a nodedb_types::VectorPrimaryConfig,
pub rows: Vec<Vec<(String, SqlValue)>>,
pub volatile_defaults: bool,
pub intent: VectorPrimaryInsertIntent,
pub on_conflict_updates: Vec<(String, SqlExpr)>,
pub primary_key: Option<String>,
}
pub(crate) fn sql_values_to_vector(field: &str, items: &[SqlValue]) -> Result<Vec<f32>> {
items
.iter()
.map(|v| match v {
SqlValue::Float(f) => Ok(*f as f32),
SqlValue::Int(i) => Ok(*i as f32),
SqlValue::Decimal(d) => {
use rust_decimal::prelude::ToPrimitive;
d.to_f32().ok_or_else(|| SqlError::Parse {
detail: format!("vector element decimal '{d}' is out of f32 range"),
})
}
other => Err(SqlError::Parse {
detail: format!("vector field '{field}' must contain numbers, got {other:?}"),
}),
})
.collect()
}
pub(crate) fn sql_value_to_vector(field: &str, value: &SqlValue) -> Result<Vec<f32>> {
match value {
SqlValue::Array(items) => sql_values_to_vector(field, items),
other => Err(SqlError::Parse {
detail: format!("vector field '{field}' must be an array literal, got {other:?}"),
}),
}
}
pub(crate) fn build_vector_primary_insert_plan(
params: VectorPrimaryInsertParams<'_>,
) -> Result<Vec<SqlPlan>> {
let VectorPrimaryInsertParams {
collection,
vpc,
rows,
volatile_defaults,
intent,
on_conflict_updates,
primary_key,
} = params;
let mut result_rows = Vec::with_capacity(rows.len());
for row in rows {
let mut vector: Option<Vec<f32>> = None;
let mut payload_fields = std::collections::HashMap::new();
for (col, val) in row {
if col == vpc.vector_field {
vector = Some(sql_value_to_vector(&vpc.vector_field, &val)?);
} else {
payload_fields.insert(col, val);
}
}
let vector = vector.ok_or_else(|| SqlError::Parse {
detail: format!(
"vector-primary INSERT missing required vector field '{}'",
vpc.vector_field
),
})?;
result_rows.push(VectorPrimaryRow {
vector,
payload_fields,
});
}
Ok(vec![SqlPlan::VectorPrimaryInsert(
VectorPrimaryInsertPlan {
collection: collection.to_string(),
field: vpc.vector_field.clone(),
quantization: vpc.quantization,
storage_dtype: vpc.storage_dtype,
payload_indexes: vpc.payload_indexes.clone(),
rows: result_rows,
volatile_defaults,
intent,
on_conflict_updates,
primary_key,
},
)])
}