use crate::adapter::utils::get_field_type;
use crate::error::{QuickDbError, QuickDbResult};
use crate::types::*;
use mongodb::bson::{Bson, Document, Regex, doc};
use rat_logger::debug;
fn map_field_name(field_name: &str) -> &str {
if field_name == "id" {
"_id"
} else {
field_name
}
}
pub struct MongoQueryBuilder {
conditions: Vec<QueryConditionWithConfig>,
condition_groups: Vec<QueryConditionGroup>,
condition_groups_with_config: Vec<QueryConditionGroupWithConfig>,
}
impl MongoQueryBuilder {
pub fn new() -> Self {
Self {
conditions: Vec::new(),
condition_groups: Vec::new(),
condition_groups_with_config: Vec::new(),
}
}
pub fn where_condition(mut self, condition: QueryConditionWithConfig) -> Self {
self.conditions.push(condition);
self
}
pub fn where_conditions(mut self, conditions: &[QueryConditionWithConfig]) -> Self {
self.conditions.extend_from_slice(conditions);
self
}
pub fn where_condition_groups(mut self, groups: &[QueryConditionGroup]) -> Self {
self.condition_groups.extend_from_slice(groups);
self
}
pub fn where_condition_groups_with_config(mut self, groups: &[QueryConditionGroupWithConfig]) -> Self {
self.condition_groups_with_config.extend_from_slice(groups);
self
}
pub fn build(self, table: &str, alias: &str) -> QuickDbResult<Document> {
debug!(
"[MongoDB] 开始构建查询文档,条件数量: {},表: {},别名: {}",
self.conditions.len(),
table,
alias
);
let mut query_doc = Document::new();
if !self.condition_groups_with_config.is_empty() {
let groups_doc = self.build_condition_groups_with_config_document(table, alias)?;
if !groups_doc.is_empty() {
query_doc.extend(groups_doc);
}
} else if !self.condition_groups.is_empty() {
let groups_doc = self.build_condition_groups_document(table, alias)?;
if !groups_doc.is_empty() {
query_doc.extend(groups_doc);
}
} else if !self.conditions.is_empty() {
let conditions_doc = self.build_conditions_document(table, alias)?;
if !conditions_doc.is_empty() {
query_doc.extend(conditions_doc);
}
}
debug!("[MongoDB] 完成查询文档构建: {:?}", query_doc);
Ok(query_doc)
}
fn build_conditions_document(&self, table: &str, alias: &str) -> QuickDbResult<Document> {
let mut query_doc = Document::new();
for condition in &self.conditions {
let condition_doc = self.build_single_condition_document(table, alias, condition)?;
if !condition_doc.is_empty() {
query_doc.extend(condition_doc);
}
}
Ok(query_doc)
}
fn build_condition_groups_document(&self, table: &str, alias: &str) -> QuickDbResult<Document> {
let mut group_docs = Vec::new();
for group in &self.condition_groups {
let group_doc = self.build_single_condition_group_document(table, alias, group)?;
if !group_doc.is_empty() {
group_docs.push(group_doc);
}
}
if group_docs.len() == 1 {
Ok(group_docs.into_iter().next().unwrap())
} else {
Ok(doc! { "$and": group_docs })
}
}
fn build_single_condition_group_document(
&self,
table: &str,
alias: &str,
group: &QueryConditionGroup,
) -> QuickDbResult<Document> {
match group {
QueryConditionGroup::Single(condition) => {
let condition_with_config = QueryConditionWithConfig {
field: condition.field.clone(),
operator: condition.operator.clone(),
value: condition.value.clone(),
case_insensitive: false,
};
self.build_single_condition_document(table, alias, &condition_with_config)
}
QueryConditionGroup::Group {
operator,
conditions,
} => {
if conditions.is_empty() {
return Ok(Document::new());
}
let mut condition_docs = Vec::new();
for condition in conditions {
let doc =
self.build_single_condition_group_document(table, alias, condition)?;
if !doc.is_empty() {
condition_docs.push(doc);
}
}
if condition_docs.len() == 1 {
Ok(condition_docs.into_iter().next().unwrap())
} else {
let operator_key = match operator {
LogicalOperator::And => "$and",
LogicalOperator::Or => "$or",
};
Ok(doc! { operator_key: condition_docs })
}
}
}
}
fn build_condition_groups_with_config_document(&self, table: &str, alias: &str) -> QuickDbResult<Document> {
let mut group_docs = Vec::new();
for group in &self.condition_groups_with_config {
let group_doc = self.build_single_condition_group_with_config_document(table, alias, group)?;
if !group_doc.is_empty() {
group_docs.push(group_doc);
}
}
if group_docs.len() == 1 {
Ok(group_docs.into_iter().next().unwrap())
} else {
Ok(doc! { "$and": group_docs })
}
}
fn build_single_condition_group_with_config_document(
&self,
table: &str,
alias: &str,
group: &QueryConditionGroupWithConfig,
) -> QuickDbResult<Document> {
match group {
QueryConditionGroupWithConfig::Single(condition) => {
self.build_single_condition_document(table, alias, condition)
}
QueryConditionGroupWithConfig::GroupWithConfig {
operator,
conditions,
} => {
if conditions.is_empty() {
return Ok(Document::new());
}
let mut condition_docs = Vec::new();
for condition in conditions {
let doc =
self.build_single_condition_group_with_config_document(table, alias, condition)?;
if !doc.is_empty() {
condition_docs.push(doc);
}
}
if condition_docs.len() == 1 {
Ok(condition_docs.into_iter().next().unwrap())
} else {
let operator_key = match operator {
LogicalOperator::And => "$and",
LogicalOperator::Or => "$or",
};
Ok(doc! { operator_key: condition_docs })
}
}
}
}
fn build_single_condition_document(
&self,
table: &str,
alias: &str,
condition: &QueryConditionWithConfig,
) -> QuickDbResult<Document> {
let field_name = map_field_name(&condition.field);
let field_type = get_field_type(table, alias, &condition.field);
let is_uuid_field = field_type.as_ref().map(|ft| matches!(ft, crate::model::FieldType::Uuid)).unwrap_or(false);
debug!("[MongoDB] 字段 '{}' (映射为 '{}') 类型: {:?}, is_uuid: {}",
condition.field, field_name, field_type.as_ref(), is_uuid_field);
let bson_value = if is_uuid_field {
if let DataValue::String(ref s) = condition.value {
debug!("[MongoDB] UUID字段 '{}' 使用字符串查询: {}", field_name, s);
Bson::String(s.clone())
} else {
self.data_value_to_bson(&condition.value)
}
} else {
self.data_value_to_bson(&condition.value)
};
debug!(
"[MongoDB] 处理条件: {} (原始: {}) {:?} {:?}",
field_name, condition.field, condition.operator, bson_value
);
let condition_doc = match condition.operator {
QueryOperator::Eq => {
if condition.case_insensitive {
match &bson_value {
Bson::String(s) => {
doc! { field_name: doc! { "$regex": format!("^{}$", ®ex::escape(s)), "$options": "i" } }
}
_ => {
doc! { field_name: bson_value }
}
}
} else {
doc! { field_name: bson_value }
}
}
QueryOperator::Ne => doc! { field_name: doc! { "$ne": bson_value } },
QueryOperator::Gt => doc! { field_name: doc! { "$gt": bson_value } },
QueryOperator::Gte => doc! { field_name: doc! { "$gte": bson_value } },
QueryOperator::Lt => doc! { field_name: doc! { "$lt": bson_value } },
QueryOperator::Lte => doc! { field_name: doc! { "$lte": bson_value } },
QueryOperator::Contains => {
self.build_contains_condition(field_name, table, alias, bson_value)?
}
QueryOperator::JsonContains => {
match bson_value {
Bson::String(s) => {
let json_value: serde_json::Value =
serde_json::from_str(&s).map_err(|e| {
QuickDbError::ValidationError {
field: condition.field.clone(),
message: format!("无效的JSON格式: {}", e),
}
})?;
self.flatten_json_to_query(field_name, &json_value)
}
_ => {
doc! { field_name: bson_value }
}
}
}
QueryOperator::StartsWith => {
if let Bson::String(s) = bson_value {
doc! { field_name: doc! { "$regex": format!("^{}", &s), "$options": "i" } }
} else {
return Err(QuickDbError::ValidationError {
field: condition.field.clone(),
message: "StartsWith操作符只支持字符串类型".to_string(),
});
}
}
QueryOperator::EndsWith => {
if let Bson::String(s) = bson_value {
doc! { field_name: doc! { "$regex": format!("{}$", &s), "$options": "i" } }
} else {
return Err(QuickDbError::ValidationError {
field: condition.field.clone(),
message: "EndsWith操作符只支持字符串类型".to_string(),
});
}
}
QueryOperator::In => {
if let Bson::Array(arr) = &bson_value {
if let Some(field_type) = get_field_type(table, alias, field_name) {
if matches!(field_type, crate::model::FieldType::Array { .. }) {
for bson_elem in arr {
match bson_elem {
Bson::String(_)
| Bson::Int32(_)
| Bson::Int64(_)
| Bson::Double(_) => {
}
Bson::ObjectId(_) => {
}
_ => {
return Err(QuickDbError::ValidationError {
field: field_name.to_string(),
message: format!(
"Array字段的IN操作只支持String、Int、Float、Uuid类型,不支持: {:?}",
bson_elem
),
});
}
}
}
}
}
doc! { field_name: doc! { "$in": arr.clone() } }
} else {
doc! { field_name: doc! { "$in": [bson_value] } }
}
}
QueryOperator::NotIn => {
if let Bson::Array(arr) = &bson_value {
if let Some(field_type) = get_field_type(table, alias, field_name) {
if matches!(field_type, crate::model::FieldType::Array { .. }) {
for bson_elem in arr {
match bson_elem {
Bson::String(_)
| Bson::Int32(_)
| Bson::Int64(_)
| Bson::Double(_) => {
}
Bson::ObjectId(_) => {
}
_ => {
return Err(QuickDbError::ValidationError {
field: field_name.to_string(),
message: format!(
"Array字段的NOT IN操作只支持String、Int、Float、Uuid类型,不支持: {:?}",
bson_elem
),
});
}
}
}
}
}
doc! { field_name: doc! { "$nin": arr.clone() } }
} else {
doc! { field_name: doc! { "$nin": [bson_value] } }
}
}
QueryOperator::Regex => {
if let Bson::String(s) = bson_value {
doc! { field_name: doc! { "$regex": s, "$options": "i" } }
} else {
return Err(QuickDbError::ValidationError {
field: condition.field.clone(),
message: "Regex操作符只支持字符串类型".to_string(),
});
}
}
QueryOperator::Exists => {
doc! { field_name: doc! { "$exists": true } }
}
QueryOperator::IsNull => {
doc! { field_name: doc! { "$eq": null } }
}
QueryOperator::IsNotNull => {
doc! { field_name: doc! { "$ne": null } }
}
};
Ok(condition_doc)
}
fn build_contains_condition(
&self,
field_name: &str,
table: &str,
alias: &str,
bson_value: Bson,
) -> QuickDbResult<Document> {
let field_type = get_field_type(table, alias, field_name).ok_or_else(|| {
QuickDbError::ValidationError {
field: field_name.to_string(),
message: format!(
"无法确定字段 '{}' 的类型,请确保已正确注册模型元数据 (alias={})",
field_name, alias
),
}
})?;
debug!(
"[MongoDB] Contains操作 - 字段类型: {:?}, 值: {:?}",
field_type, bson_value
);
match field_type {
crate::model::FieldType::String { .. } => {
if let Bson::String(s) = bson_value {
let regex_doc = doc! { "$regex": format!(".*{}.*", &s), "$options": "i" };
debug!(
"[MongoDB] Contains操作(字符串): {} = {:?}",
field_name, regex_doc
);
Ok(doc! { field_name: regex_doc })
} else {
return Err(QuickDbError::ValidationError {
field: field_name.to_string(),
message: "字符串字段的Contains操作符只支持字符串值".to_string(),
});
}
}
crate::model::FieldType::Array { .. } => {
debug!(
"[MongoDB] Contains操作(Array): {} = {:?}",
field_name, bson_value
);
Ok(doc! { field_name: doc! { "$in": [bson_value] } })
}
crate::model::FieldType::Json => {
match bson_value {
Bson::String(s) => {
let regex_doc = doc! { "$regex": format!(".*{}.*", &s), "$options": "i" };
Ok(doc! { field_name: regex_doc })
}
_ => Ok(doc! { field_name: doc! { "$in": [bson_value] } }),
}
}
_ => {
return Err(QuickDbError::ValidationError {
field: field_name.to_string(),
message: "Contains操作符只支持字符串、Array和JSON字段".to_string(),
});
}
}
}
fn data_value_to_bson(&self, value: &DataValue) -> Bson {
match value {
DataValue::String(s) => Bson::String(s.clone()),
DataValue::Int(i) => Bson::Int64(*i),
DataValue::UInt(u) => {
if *u <= i64::MAX as u64 {
Bson::Int64(*u as i64)
} else {
Bson::String(u.to_string())
}
}
DataValue::Float(f) => Bson::Double(*f),
DataValue::Bool(b) => Bson::Boolean(*b),
DataValue::DateTime(dt) => {
let utc_dt = chrono::DateTime::<chrono::Utc>::from(*dt);
Bson::DateTime(mongodb::bson::DateTime::from_system_time(utc_dt.into()))
}
DataValue::DateTimeUTC(dt) => {
Bson::DateTime(mongodb::bson::DateTime::from_system_time(dt.clone().into()))
}
DataValue::Uuid(uuid) => Bson::String(uuid.to_string()),
DataValue::Json(json) => {
if let Ok(doc) = mongodb::bson::to_document(json) {
Bson::Document(doc)
} else {
Bson::String(json.to_string())
}
}
DataValue::Array(arr) => {
let bson_array: Vec<Bson> =
arr.iter().map(|v| self.data_value_to_bson(v)).collect();
Bson::Array(bson_array)
}
DataValue::Object(obj) => {
let mut bson_doc = Document::new();
for (key, value) in obj {
let bson_value = self.data_value_to_bson(value);
bson_doc.insert(key, bson_value);
}
Bson::Document(bson_doc)
}
DataValue::Null => Bson::Null,
DataValue::Bytes(bytes) => Bson::Binary(mongodb::bson::Binary {
bytes: bytes.clone(),
subtype: mongodb::bson::spec::BinarySubtype::Generic,
}),
}
}
fn flatten_json_to_query(&self, field_name: &str, json_value: &serde_json::Value) -> Document {
match json_value {
serde_json::Value::Object(map) => {
if map.is_empty() {
return Document::new();
}
let mut conditions = Vec::new();
for (key, value) in map {
let dot_path = format!("{}.{}", field_name, key);
if let serde_json::Value::Object(_) = value {
let nested_condition = self.flatten_json_to_query(&dot_path, value);
for (k, v) in nested_condition {
conditions.push(doc! { k: v });
}
} else {
if let Ok(bson_value) = mongodb::bson::to_bson(value) {
conditions.push(doc! { dot_path: bson_value });
}
}
}
if conditions.len() == 1 {
conditions.into_iter().next().unwrap()
} else {
doc! { "$and": conditions }
}
}
_ => {
Document::new()
}
}
}
}
pub fn build_query_document(
table: &str,
alias: &str,
conditions: &[QueryConditionWithConfig],
) -> QuickDbResult<Document> {
MongoQueryBuilder::new()
.where_conditions(conditions)
.build(table, alias)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::QueryConditionWithConfig;
#[test]
fn test_mongo_query_builder_basic() {
}
#[test]
fn test_field_name_mapping() {
assert_eq!(map_field_name("id"), "_id");
assert_eq!(map_field_name("name"), "name");
assert_eq!(map_field_name("email"), "email");
assert_eq!(map_field_name("_id"), "_id");
}
#[test]
fn test_id_field_in_query_condition() {
let conditions = vec![QueryCondition {
field: "id".to_string(),
operator: crate::types::QueryOperator::Eq,
value: crate::types::DataValue::String("test_id".to_string()),
}];
let result = build_query_document("users", "test", &conditions);
assert!(result.is_ok());
let doc = result.unwrap();
assert!(doc.contains_key("_id"));
assert!(!doc.contains_key("id"));
}
}