Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ca9017cc4 | |||
| 10c57e59ec | |||
| ef4571767c | |||
| 29bd25eaff |
@ -1163,7 +1163,7 @@
|
|||||||
"$eq": true,
|
"$eq": true,
|
||||||
"$ne": false
|
"$ne": false
|
||||||
},
|
},
|
||||||
"contacts.#.is_primary": {
|
"contacts/is_primary": {
|
||||||
"$eq": true
|
"$eq": true
|
||||||
},
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
@ -1203,7 +1203,7 @@
|
|||||||
"$eq": "%Doe%",
|
"$eq": "%Doe%",
|
||||||
"$ne": "%Smith%"
|
"$ne": "%Smith%"
|
||||||
},
|
},
|
||||||
"phone_numbers.#.target.number": {
|
"phone_numbers/target/number": {
|
||||||
"$eq": "555-1234"
|
"$eq": "555-1234"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -64,11 +64,7 @@ impl<'a> Compiler<'a> {
|
|||||||
|
|
||||||
fn compile_array(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
fn compile_array(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
||||||
if let Some(items) = &node.schema.obj.items {
|
if let Some(items) = &node.schema.obj.items {
|
||||||
let next_path = if node.ast_path.is_empty() {
|
let next_path = node.ast_path.clone();
|
||||||
String::from("#")
|
|
||||||
} else {
|
|
||||||
format!("{}.#", node.ast_path)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(ref_id) = &items.obj.r#ref {
|
if let Some(ref_id) = &items.obj.r#ref {
|
||||||
if let Some(type_def) = self.db.types.get(ref_id) {
|
if let Some(type_def) = self.db.types.get(ref_id) {
|
||||||
@ -448,22 +444,21 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut child_node = node.clone();
|
let child_node = Node {
|
||||||
child_node.parent_alias = owner_alias.clone();
|
schema: std::sync::Arc::clone(prop_schema),
|
||||||
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
|
parent_alias: owner_alias.clone(),
|
||||||
child_node.parent_type_aliases = Some(arc_aliases);
|
parent_type_aliases: Some(std::sync::Arc::new(table_aliases.clone())),
|
||||||
child_node.parent_type = Some(r#type);
|
parent_type: Some(r#type),
|
||||||
child_node.parent_schema = Some(std::sync::Arc::clone(&node.schema));
|
parent_schema: Some(std::sync::Arc::clone(&node.schema)),
|
||||||
child_node.property_name = Some(prop_key.clone());
|
property_name: Some(prop_key.clone()),
|
||||||
child_node.depth += 1;
|
depth: node.depth + 1,
|
||||||
let next_path = if node.ast_path.is_empty() {
|
ast_path: if node.ast_path.is_empty() {
|
||||||
prop_key.clone()
|
prop_key.clone()
|
||||||
} else {
|
} else {
|
||||||
format!("{}.{}", node.ast_path, prop_key)
|
format!("{}/{}", node.ast_path, prop_key)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
child_node.ast_path = next_path;
|
|
||||||
child_node.schema = std::sync::Arc::clone(prop_schema);
|
|
||||||
|
|
||||||
let (val_sql, val_type) = self.compile_node(child_node)?;
|
let (val_sql, val_type) = self.compile_node(child_node)?;
|
||||||
|
|
||||||
@ -491,7 +486,14 @@ impl<'a> Compiler<'a> {
|
|||||||
.unwrap_or_else(|| base_alias.clone());
|
.unwrap_or_else(|| base_alias.clone());
|
||||||
|
|
||||||
let mut where_clauses = Vec::new();
|
let mut where_clauses = Vec::new();
|
||||||
where_clauses.push(format!("NOT {}.archived", entity_alias));
|
|
||||||
|
// Dynamically apply the 'active-only' default ONLY if the client
|
||||||
|
// didn't explicitly request to filter on 'archived' themselves!
|
||||||
|
let has_archived_override = self.filter_keys.iter().any(|k| k == "archived");
|
||||||
|
|
||||||
|
if !has_archived_override {
|
||||||
|
where_clauses.push(format!("NOT {}.archived", entity_alias));
|
||||||
|
}
|
||||||
|
|
||||||
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
|
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
|
||||||
self.compile_relation_conditions(
|
self.compile_relation_conditions(
|
||||||
@ -580,15 +582,15 @@ impl<'a> Compiler<'a> {
|
|||||||
let op = parts.next().unwrap_or("$eq");
|
let op = parts.next().unwrap_or("$eq");
|
||||||
|
|
||||||
let field_name = if node.ast_path.is_empty() {
|
let field_name = if node.ast_path.is_empty() {
|
||||||
if full_field_path.contains('.') || full_field_path.contains('#') {
|
if full_field_path.contains('/') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
full_field_path
|
full_field_path
|
||||||
} else {
|
} else {
|
||||||
let prefix = format!("{}.", node.ast_path);
|
let prefix = format!("{}/", node.ast_path);
|
||||||
if full_field_path.starts_with(&prefix) {
|
if full_field_path.starts_with(&prefix) {
|
||||||
let remainder = &full_field_path[prefix.len()..];
|
let remainder = &full_field_path[prefix.len()..];
|
||||||
if remainder.contains('.') || remainder.contains('#') {
|
if remainder.contains('/') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
remainder
|
remainder
|
||||||
|
|||||||
@ -54,6 +54,45 @@ impl Queryer {
|
|||||||
self.execute_sql(schema_id, &sql, &args)
|
self.execute_sql(schema_id, &sql, &args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_filters(
|
||||||
|
prefix: String,
|
||||||
|
val: &serde_json::Value,
|
||||||
|
entries: &mut Vec<(String, serde_json::Value)>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
if let Some(obj) = val.as_object() {
|
||||||
|
let mut is_op_obj = false;
|
||||||
|
if let Some(first_key) = obj.keys().next() {
|
||||||
|
if first_key.starts_with('$') {
|
||||||
|
is_op_obj = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_op_obj {
|
||||||
|
for (op, op_val) in obj {
|
||||||
|
if !op.starts_with('$') {
|
||||||
|
return Err(format!("Filter operator must start with '$', got: {}", op));
|
||||||
|
}
|
||||||
|
entries.push((format!("{}:{}", prefix, op), op_val.clone()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (k, v) in obj {
|
||||||
|
let next_prefix = if prefix.is_empty() {
|
||||||
|
k.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}/{}", prefix, k)
|
||||||
|
};
|
||||||
|
Self::extract_filters(next_prefix, v, entries)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"Filter for path '{}' must be an operator object like {{$eq: ...}} or a nested map.",
|
||||||
|
prefix
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_filter_entries(
|
fn parse_filter_entries(
|
||||||
&self,
|
&self,
|
||||||
filters_map: Option<&serde_json::Map<String, serde_json::Value>>,
|
filters_map: Option<&serde_json::Map<String, serde_json::Value>>,
|
||||||
@ -61,19 +100,7 @@ impl Queryer {
|
|||||||
let mut filter_entries: Vec<(String, serde_json::Value)> = Vec::new();
|
let mut filter_entries: Vec<(String, serde_json::Value)> = Vec::new();
|
||||||
if let Some(fm) = filters_map {
|
if let Some(fm) = filters_map {
|
||||||
for (key, val) in fm {
|
for (key, val) in fm {
|
||||||
if let Some(obj) = val.as_object() {
|
Self::extract_filters(key.clone(), val, &mut filter_entries)?;
|
||||||
for (op, op_val) in obj {
|
|
||||||
if !op.starts_with('$') {
|
|
||||||
return Err(format!("Filter operator must start with '$', got: {}", op));
|
|
||||||
}
|
|
||||||
filter_entries.push((format!("{}:{}", key, op), op_val.clone()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(format!(
|
|
||||||
"Filter for field '{}' must be an object with operators like $eq, $in, etc.",
|
|
||||||
key
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filter_entries.sort_by(|a, b| a.0.cmp(&b.0));
|
filter_entries.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|||||||
Reference in New Issue
Block a user