Files
smartdb/rust/crates/rustdb-query/src/projection.rs
T

169 lines
4.6 KiB
Rust

use bson::{Bson, Document};
use crate::field_path::get_nested_value;
/// Apply a projection to a document.
/// Inclusion mode: only specified fields + _id.
/// Exclusion mode: all fields except specified ones.
/// _id can be explicitly excluded in either mode.
pub fn apply_projection(doc: &Document, projection: &Document) -> Document {
if projection.is_empty() {
return doc.clone();
}
// Determine mode: inclusion or exclusion
let mut has_inclusion = false;
let mut id_explicitly_set = false;
for (key, value) in projection {
if key == "_id" {
id_explicitly_set = true;
continue;
}
match value {
Bson::Int32(0) | Bson::Int64(0) | Bson::Boolean(false) => {}
_ => has_inclusion = true,
}
}
if has_inclusion {
apply_inclusion(doc, projection, id_explicitly_set)
} else {
apply_exclusion(doc, projection)
}
}
fn apply_inclusion(doc: &Document, projection: &Document, id_explicitly_set: bool) -> Document {
let mut result = Document::new();
// Include _id by default unless explicitly excluded
let include_id = if id_explicitly_set {
is_truthy(projection.get("_id"))
} else {
true
};
if include_id {
if let Some(id) = doc.get("_id") {
result.insert("_id", id.clone());
}
}
for (key, value) in projection {
if key == "_id" {
continue;
}
if !is_truthy(Some(value)) {
continue;
}
if key.contains('.') {
if let Some(val) = get_nested_value(doc, key) {
// Rebuild nested structure
set_nested_in_result(&mut result, key, val);
}
} else if let Some(val) = doc.get(key) {
result.insert(key.clone(), val.clone());
}
}
result
}
fn apply_exclusion(doc: &Document, projection: &Document) -> Document {
let mut result = doc.clone();
for (key, value) in projection {
if !is_truthy(Some(value)) {
if key.contains('.') {
// Remove nested field
remove_nested_from_result(&mut result, key);
} else {
result.remove(key);
}
}
}
result
}
fn is_truthy(value: Option<&Bson>) -> bool {
match value {
None => false,
Some(Bson::Int32(0)) | Some(Bson::Int64(0)) | Some(Bson::Boolean(false)) => false,
_ => true,
}
}
fn set_nested_in_result(doc: &mut Document, path: &str, value: Bson) {
let parts: Vec<&str> = path.split('.').collect();
set_nested_recursive(doc, &parts, value);
}
fn set_nested_recursive(doc: &mut Document, parts: &[&str], value: Bson) {
if parts.len() == 1 {
doc.insert(parts[0].to_string(), value);
return;
}
let key = parts[0];
if !doc.contains_key(key) {
doc.insert(key.to_string(), Bson::Document(Document::new()));
}
if let Some(Bson::Document(ref mut nested)) = doc.get_mut(key) {
set_nested_recursive(nested, &parts[1..], value);
}
}
fn remove_nested_from_result(doc: &mut Document, path: &str) {
let parts: Vec<&str> = path.split('.').collect();
remove_nested_recursive(doc, &parts);
}
fn remove_nested_recursive(doc: &mut Document, parts: &[&str]) {
if parts.len() == 1 {
doc.remove(parts[0]);
return;
}
let key = parts[0];
if let Some(Bson::Document(ref mut nested)) = doc.get_mut(key) {
remove_nested_recursive(nested, &parts[1..]);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inclusion_projection() {
let doc = bson::doc! { "_id": 1, "name": "Alice", "age": 30, "email": "a@b.c" };
let proj = bson::doc! { "name": 1, "age": 1 };
let result = apply_projection(&doc, &proj);
assert!(result.contains_key("_id"));
assert!(result.contains_key("name"));
assert!(result.contains_key("age"));
assert!(!result.contains_key("email"));
}
#[test]
fn test_exclusion_projection() {
let doc = bson::doc! { "_id": 1, "name": "Alice", "age": 30 };
let proj = bson::doc! { "age": 0 };
let result = apply_projection(&doc, &proj);
assert!(result.contains_key("_id"));
assert!(result.contains_key("name"));
assert!(!result.contains_key("age"));
}
#[test]
fn test_exclude_id() {
let doc = bson::doc! { "_id": 1, "name": "Alice" };
let proj = bson::doc! { "name": 1, "_id": 0 };
let result = apply_projection(&doc, &proj);
assert!(!result.contains_key("_id"));
assert!(result.contains_key("name"));
}
}