use ahash::AHashMap;
use crate::common::counter::hardware_counter::HardwareCounterCell;
use crate::common::types::DeferredBehavior;
use parking_lot::RwLockWriteGuard;
use crate::segment::common::operation_error::{OperationError, OperationResult};
use crate::segment::data_types::named_vectors::NamedVectors;
use crate::segment::entry::entry_point::SegmentEntry;
use crate::segment::types::{Filter, Payload, PointIdType, SeqNumberType, VectorNameBuf};
use smallvec::SmallVec;
use crate::shard::operations::point_ops::{
ConditionalInsertOperationInternal, PointInsertOperationsInternal, PointStructPersisted,
PointStructRawPersisted, UpdateMode,
};
use crate::shard::segment_holder::SegmentHolder;
use crate::shard::update::helpers::select_excluded_by_filter_ids;
const UPDATE_OP_CHUNK_SIZE: usize = 32;
pub fn upsert_points<'a, T>(
segments: &SegmentHolder,
op_num: SeqNumberType,
points: T,
hw_counter: &HardwareCounterCell,
) -> OperationResult<usize>
where
T: IntoIterator<Item = &'a PointStructPersisted>,
{
upsert_points_impl(segments, op_num, points, hw_counter)
}
pub fn upsert_points_raw<'a, T>(
segments: &SegmentHolder,
op_num: SeqNumberType,
points: T,
hw_counter: &HardwareCounterCell,
) -> OperationResult<usize>
where
T: IntoIterator<Item = &'a PointStructRawPersisted>,
{
upsert_points_impl(segments, op_num, points, hw_counter)
}
pub(crate) fn retain_conditional_upsert_points(
segments: &SegmentHolder,
points_op: &mut PointInsertOperationsInternal,
condition: Filter,
update_mode: Option<UpdateMode>,
hw_counter: &HardwareCounterCell,
) -> OperationResult<()> {
let point_ids = points_op.point_ids();
let update_mode = update_mode.unwrap_or_default();
match update_mode {
UpdateMode::Upsert => {
let points_to_exclude =
select_excluded_by_filter_ids(segments, point_ids, condition, hw_counter)?;
points_op.retain_point_ids(|idx| !points_to_exclude.contains(idx));
}
UpdateMode::InsertOnly => {
let existing_points = segments.select_existing_points(point_ids);
points_op.retain_point_ids(|idx| !existing_points.contains(idx));
}
UpdateMode::UpdateOnly => {
let points_to_exclude =
select_excluded_by_filter_ids(segments, point_ids.clone(), condition, hw_counter)?;
let existing_points = segments.select_existing_points(point_ids);
points_op.retain_point_ids(|idx| {
existing_points.contains(idx) && !points_to_exclude.contains(idx)
});
}
}
Ok(())
}
pub fn conditional_upsert(
segments: &SegmentHolder,
op_num: SeqNumberType,
operation: ConditionalInsertOperationInternal,
hw_counter: &HardwareCounterCell,
) -> OperationResult<usize> {
let ConditionalInsertOperationInternal {
mut points_op,
condition,
update_mode,
} = operation;
retain_conditional_upsert_points(segments, &mut points_op, condition, update_mode, hw_counter)?;
let points = points_op.into_point_vec();
let upserted_points = upsert_points(segments, op_num, points.iter(), hw_counter)?;
if upserted_points == 0 {
segments.bump_max_segment_version_overwrite(op_num);
}
Ok(upserted_points)
}
pub(super) trait PointToUpsert {
fn id(&self) -> PointIdType;
fn upsert_into(
&self,
segment: &mut RwLockWriteGuard<dyn SegmentEntry>,
op_num: SeqNumberType,
hw_counter: &HardwareCounterCell,
) -> OperationResult<bool>;
fn write_moved<'op>(
&'op self,
raw_vectors: &mut SmallVec<[(VectorNameBuf, Vec<u8>); 1]>,
updated_vectors: &mut NamedVectors<'op>,
payload: &mut Payload,
);
}
pub(super) fn upsert_points_impl<'a, P>(
segments: &SegmentHolder,
op_num: SeqNumberType,
points: impl IntoIterator<Item = &'a P>,
hw_counter: &HardwareCounterCell,
) -> OperationResult<usize>
where
P: PointToUpsert + 'a,
{
let points_map: AHashMap<PointIdType, &P> = points.into_iter().map(|p| (p.id(), p)).collect();
let ids: Vec<PointIdType> = points_map.keys().copied().collect();
let mut res = 0;
for ids_chunk in ids.chunks(UPDATE_OP_CHUNK_SIZE) {
let updated_points = segments.apply_points_with_conditional_move(
op_num,
ids_chunk,
|id, write_segment| points_map[&id].upsert_into(write_segment, op_num, hw_counter),
|id, raw_vectors, updated_vectors, old_payload| {
points_map[&id].write_moved(raw_vectors, updated_vectors, old_payload)
},
hw_counter,
)?;
res += updated_points.len();
let new_point_ids = ids_chunk
.iter()
.copied()
.filter(|x| !updated_points.contains(x));
{
let default_write_segment =
segments.smallest_appendable_segment().ok_or_else(|| {
OperationError::service_error(
"No appendable segments exist, expected at least one",
)
})?;
let segment_arc = default_write_segment.get();
let mut write_segment = segment_arc.write();
for point_id in new_point_ids {
res += usize::from(points_map[&point_id].upsert_into(
&mut write_segment,
op_num,
hw_counter,
)?);
}
RwLockWriteGuard::unlock_fair(write_segment);
};
}
Ok(res)
}
impl PointToUpsert for PointStructPersisted {
fn id(&self) -> PointIdType {
self.id
}
fn upsert_into(
&self,
segment: &mut RwLockWriteGuard<dyn SegmentEntry>,
op_num: SeqNumberType,
hw_counter: &HardwareCounterCell,
) -> OperationResult<bool> {
let mut res = segment.upsert_point(op_num, self.id, self.get_vectors(), hw_counter)?;
res &=
set_full_or_clear_payload(segment, op_num, self.id, self.payload.as_ref(), hw_counter)?;
Ok(res)
}
fn write_moved<'op>(
&'op self,
raw_vectors: &mut SmallVec<[(VectorNameBuf, Vec<u8>); 1]>,
updated_vectors: &mut NamedVectors<'op>,
payload: &mut Payload,
) {
raw_vectors.clear();
*updated_vectors = self.get_vectors();
*payload = self.payload.clone().unwrap_or_default();
}
}
impl PointToUpsert for PointStructRawPersisted {
fn id(&self) -> PointIdType {
self.id
}
fn upsert_into(
&self,
segment: &mut RwLockWriteGuard<dyn SegmentEntry>,
op_num: SeqNumberType,
hw_counter: &HardwareCounterCell,
) -> OperationResult<bool> {
let mut res = segment.upsert_point_raw(op_num, self.id, &self.vectors, hw_counter)?;
res &=
set_full_or_clear_payload(segment, op_num, self.id, self.payload.as_ref(), hw_counter)?;
Ok(res)
}
fn write_moved<'op>(
&'op self,
raw_vectors: &mut SmallVec<[(VectorNameBuf, Vec<u8>); 1]>,
_updated_vectors: &mut NamedVectors<'op>,
payload: &mut Payload,
) {
raw_vectors.clear();
raw_vectors.extend(self.vectors.iter().cloned());
*payload = self.payload.clone().unwrap_or_default();
}
}
fn set_full_or_clear_payload(
segment: &mut RwLockWriteGuard<dyn SegmentEntry>,
op_num: SeqNumberType,
point_id: PointIdType,
payload: Option<&Payload>,
hw_counter: &HardwareCounterCell,
) -> OperationResult<bool> {
let res = if let Some(full_payload) = payload {
segment.set_full_payload(op_num, point_id, full_payload, hw_counter)?
} else {
segment.clear_payload(op_num, point_id, hw_counter)?
};
debug_assert!(
segment.has_point(point_id, DeferredBehavior::WithDeferred),
"the point {point_id} should be present immediately after the upsert"
);
Ok(res)
}