fix: MongoDB ifNoneExist search correctness - #917
Conversation
|
|
Addressed here bc07bdf |
Also, because $group precedes $limit, MongoDB still groups the entire broad match set before returning 1,001 IDs.
|
|
Could something like this work instead? This keeps both the application memory and every distinct() response bounded by the candidate batch size. Unlike $limit: 1001 before intersection, paging continues until the complete driver result has been examined or two live matches are found, so broad parameters cannot cause false negatives. The production version should select the driver using a small bounded probe and retain the existing direct handling for _id and _lastUpdated. const CANDIDATE_BATCH_SIZE: i64 = 128;
// Select this using a small bounded probe in the full implementation.
let driver_index = 0;
let driver_filter =
self.build_search_index_filter(tenant_id, resource_type, &query.parameters[driver_index])?;
let mut last_index_id: Option<Bson> = None;
let mut matches = Vec::with_capacity(2);
let mut matched_ids = HashSet::new();
loop {
let page_filter = match &last_index_id {
Some(last_id) => doc! {
"$and": [
driver_filter.clone(),
{ "_id": { "$gt": last_id.clone() } }
]
},
None => driver_filter.clone(),
};
let mut cursor = search_index
.find(page_filter)
.sort(doc! { "_id": 1 })
.projection(doc! { "_id": 1, "resource_id": 1 })
.limit(CANDIDATE_BATCH_SIZE)
.session(&mut *session)
.await?;
let mut candidate_ids = HashSet::new();
let mut documents_read = 0;
while cursor.advance(&mut *session).await? {
let document = cursor.deserialize_current()?;
last_index_id = document.get("_id").cloned();
documents_read += 1;
if let Ok(resource_id) = document.get_str("resource_id") {
candidate_ids.insert(resource_id.to_string());
}
}
if documents_read == 0 {
break;
}
// Intersect only this bounded candidate batch.
for (index, parameter) in query.parameters.iter().enumerate() {
if index == driver_index
|| matches!(parameter.name.as_str(), "_id" | "_lastUpdated")
{
continue;
}
let parameter_filter =
self.build_search_index_filter(tenant_id, resource_type, parameter)?;
let bounded_filter = doc! {
"$and": [
parameter_filter,
{
"resource_id": {
"$in": candidate_ids.iter().cloned().collect::<Vec<_>>()
}
}
]
};
let matching_ids = search_index
.distinct("resource_id", bounded_filter)
.session(&mut *session)
.await?
.into_iter()
.filter_map(|value| value.as_str().map(str::to_string))
.collect::<HashSet<_>>();
candidate_ids.retain(|id| matching_ids.contains(id));
if candidate_ids.is_empty() {
break;
}
}
if !candidate_ids.is_empty() {
let remaining = 2 - matches.len();
let mut cursor = resources
.find(doc! {
"tenant_id": tenant_id,
"resource_type": resource_type,
"is_deleted": false,
"id": { "$in": candidate_ids.into_iter().collect::<Vec<_>>() }
})
.limit(remaining as i64)
.session(&mut *session)
.await?;
while cursor.advance(&mut *session).await? {
let document = cursor.deserialize_current()?;
let resource =
document_to_stored_resource(&document, tenant, resource_type)?;
if matched_ids.insert(resource.id().to_string()) {
matches.push(resource);
}
}
}
if matches.len() == 2 {
break;
}
if documents_read < CANDIDATE_BATCH_SIZE {
break;
}
}
return Ok(matches); |
Fixes #709 replace MongoDB's broken in-Rust ifNoneExist matcher with the real session-scoped search machinery so conditional bundle entries match correctly.