diff --git a/src/database/mod.rs b/src/database/mod.rs index 120c8c3..191f9f3 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -23,6 +23,7 @@ use relation::Relation; use schema::Schema; use serde_json::Value; use std::collections::{HashMap, HashSet}; +use std::sync::Arc; use r#type::Type; pub struct Database { @@ -310,12 +311,84 @@ impl Database { } fn compile_schemas(&mut self) { - // Pass 3: compile_internals across pure structure let schema_ids: Vec = self.schemas.keys().cloned().collect(); + let mut compiled_names_map: HashMap> = HashMap::new(); + let mut compiled_props_map: HashMap>> = + HashMap::new(); + + for id in &schema_ids { + if let Some(schema) = self.schemas.get(id) { + let mut visited = HashSet::new(); + let merged = self.merged_properties(schema, &mut visited); + let mut names: Vec = merged.keys().cloned().collect(); + if !names.is_empty() { + names.sort(); + compiled_names_map.insert(id.clone(), names); + compiled_props_map.insert(id.clone(), merged); + } + } + } + for id in schema_ids { if let Some(schema) = self.schemas.get_mut(&id) { + if let Some(names) = compiled_names_map.remove(&id) { + schema.obj.compiled_property_names = Some(names); + } + if let Some(props) = compiled_props_map.remove(&id) { + schema.obj.compiled_properties = Some(props); + } schema.compile_internals(); } } } + + pub fn merged_properties( + &self, + schema: &Schema, + visited: &mut HashSet, + ) -> std::collections::BTreeMap> { + if let Some(props) = &schema.obj.compiled_properties { + return props.clone(); + } + + let mut props = std::collections::BTreeMap::new(); + + if let Some(id) = &schema.obj.id { + if !visited.insert(id.clone()) { + return props; + } + } + + if let Some(ref_id) = &schema.obj.r#ref { + if let Some(parent_schema) = self.schemas.get(ref_id) { + props.extend(self.merged_properties(parent_schema, visited)); + } + } + + if let Some(all_of) = &schema.obj.all_of { + for ao in all_of { + props.extend(self.merged_properties(ao, visited)); + } + } + + if let Some(then_schema) = &schema.obj.then_ { + props.extend(self.merged_properties(then_schema, visited)); + } + + if let Some(else_schema) = &schema.obj.else_ { + props.extend(self.merged_properties(else_schema, visited)); + } + + if let Some(local_props) = &schema.obj.properties { + for (k, v) in local_props { + props.insert(k.clone(), v.clone()); + } + } + + if let Some(id) = &schema.obj.id { + visited.remove(id); + } + + props + } } diff --git a/src/database/schema.rs b/src/database/schema.rs index 6a69a38..90ef0a9 100644 --- a/src/database/schema.rs +++ b/src/database/schema.rs @@ -167,6 +167,13 @@ pub struct SchemaObject { #[serde(skip_serializing_if = "Option::is_none")] pub extensible: Option, + #[serde(rename = "compiledProperties")] + #[serde(skip_serializing_if = "Option::is_none")] + pub compiled_property_names: Option>, + + #[serde(skip)] + pub compiled_properties: Option>>, + #[serde(skip)] pub compiled_format: Option, #[serde(skip)] diff --git a/src/queryer/compiler.rs b/src/queryer/compiler.rs index 285c924..b3dddf4 100644 --- a/src/queryer/compiler.rs +++ b/src/queryer/compiler.rs @@ -19,11 +19,7 @@ pub struct Node<'a> { impl<'a> Compiler<'a> { /// Compiles a JSON schema into a nested PostgreSQL query returning JSONB - pub fn compile( - &self, - schema_id: &str, - filter_keys: &[String], - ) -> Result { + pub fn compile(&self, schema_id: &str, filter_keys: &[String]) -> Result { let schema = self .db .schemas @@ -251,8 +247,7 @@ impl<'a> Compiler<'a> { let mut bypass_node = node.clone(); bypass_node.schema = std::sync::Arc::new(bypass_schema); - let mut bypassed_args = - self.compile_select_clause(r#type, table_aliases, bypass_node)?; + let mut bypassed_args = self.compile_select_clause(r#type, table_aliases, bypass_node)?; select_args.append(&mut bypassed_args); } else { let mut family_schemas = Vec::new(); @@ -400,7 +395,9 @@ impl<'a> Compiler<'a> { ) -> Result, String> { let mut select_args = Vec::new(); let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object()); - let merged_props = self.get_merged_properties(node.schema.as_ref()); + let merged_props = self + .db + .merged_properties(node.schema.as_ref(), &mut std::collections::HashSet::new()); let mut sorted_keys: Vec<&String> = merged_props.keys().collect(); sorted_keys.sort(); @@ -417,9 +414,9 @@ impl<'a> Compiler<'a> { _ => false, }; - let is_primitive = prop_schema.obj.r#ref.is_none() - && !is_object_or_array - && prop_schema.obj.family.is_none() + let is_primitive = prop_schema.obj.r#ref.is_none() + && !is_object_or_array + && prop_schema.obj.family.is_none() && prop_schema.obj.one_of.is_none(); if is_primitive { @@ -494,7 +491,13 @@ impl<'a> Compiler<'a> { where_clauses.push(format!("NOT {}.archived", entity_alias)); self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses); - self.compile_relation_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses)?; + self.compile_relation_conditions( + r#type, + type_aliases, + &node, + &base_alias, + &mut where_clauses, + )?; Ok(where_clauses) } @@ -509,7 +512,10 @@ impl<'a> Compiler<'a> { for (t_name, fields_val) in gf { if let Some(fields_arr) = fields_val.as_array() { if fields_arr.iter().any(|v| v.as_str() == Some(field_name)) { - return type_aliases.get(t_name).cloned().unwrap_or_else(|| base_alias.to_string()); + return type_aliases + .get(t_name) + .cloned() + .unwrap_or_else(|| base_alias.to_string()); } } } @@ -606,13 +612,31 @@ impl<'a> Compiler<'a> { )); } else { let sql_op = match op { - "$eq" => if is_ilike { "ILIKE" } else { "=" }, - "$ne" => if is_ilike { "NOT ILIKE" } else { "!=" }, + "$eq" => { + if is_ilike { + "ILIKE" + } else { + "=" + } + } + "$ne" => { + if is_ilike { + "NOT ILIKE" + } else { + "!=" + } + } "$gt" => ">", "$gte" => ">=", "$lt" => "<", "$lte" => "<=", - _ => if is_ilike { "ILIKE" } else { "=" }, + _ => { + if is_ilike { + "ILIKE" + } else { + "=" + } + } }; let param_sql = if is_ilike && (op == "$eq" || op == "$ne") { @@ -643,7 +667,9 @@ impl<'a> Compiler<'a> { let mut child_relation_alias = base_alias.to_string(); if let Some(parent_type) = node.parent_type { - let merged_props = self.get_merged_properties(node.schema.as_ref()); + let merged_props = self + .db + .merged_properties(node.schema.as_ref(), &mut std::collections::HashSet::new()); let relative_keys: Vec = merged_props.keys().cloned().collect(); let (relation, is_parent_source) = self @@ -695,25 +721,4 @@ impl<'a> Compiler<'a> { } Ok(()) } - - fn get_merged_properties( - &self, - schema: &crate::database::schema::Schema, - ) -> std::collections::BTreeMap> { - let mut props = std::collections::BTreeMap::new(); - - if let Some(ref_id) = &schema.obj.r#ref { - if let Some(parent_schema) = self.db.schemas.get(ref_id) { - props.extend(self.get_merged_properties(parent_schema)); - } - } - - if let Some(local_props) = &schema.obj.properties { - for (k, v) in local_props { - props.insert(k.clone(), v.clone()); - } - } - - props - } } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 9cfc000..cbe39e3 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -62,7 +62,8 @@ fn test_library_api() { "properties": { "name": { "type": "string" } }, - "required": ["name"] + "required": ["name"], + "compiledProperties": ["name"] } } })