From 5b36ecf06cbb6ff84d530099a0a17cfc9e1eee43 Mon Sep 17 00:00:00 2001 From: Alex Groleau Date: Fri, 27 Mar 2026 19:25:15 -0400 Subject: [PATCH] doc update and more code comments --- GEMINI.md | 8 ++++---- src/database/schema.rs | 39 ++++++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/GEMINI.md b/GEMINI.md index e6dacb6..99701b2 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -24,10 +24,10 @@ To support high-throughput operations while allowing for runtime updates (e.g., 4. **Lock-Free Reads**: Incoming operations acquire a read lock just long enough to clone the `Arc` inside an `RwLock>>`, ensuring zero blocking during schema updates. ### Relational Edge Resolution -When compiling nested object graphs or arrays, the JSPG engine must dynamically infer which Postgres Foreign Key constraint correctly bridges the parent to the nested schema. It utilizes a strict 3-step hierarchical resolution: -1. **Direct Prefix Match**: If an explicitly prefixed Foreign Key (e.g. `fk_invoice_counterparty_entity` -> `prefix: "counterparty"`) matches the exact name of the requested schema property (e.g. `{"counterparty": {...}}`), it is instantly selected. -2. **Base Edge Fallback (1:M)**: If no explicit prefix directly matches the property name, the compiler filters for explicitly one remaining relation with a `null` prefix (e.g. `fk_invoice_line_invoice` -> `prefix: null`). A `null` prefix mathematically denotes the standard structural parent-child ownership edge (bypassing any M:M ambiguity) and is safely picked over explicit (but unmatched) property edges. -3. **Ambiguity Elimination (M:M)**: If multiple explicitly prefixed relations remain (which happens by design in Many-to-Many junction tables like `contact` utilizing `fk_relationship_source` and `fk_relationship_target`), the compiler uses a process of elimination. It checks which of the prefix names the child schema *natively consumes* as an outbound property (e.g. `contact` defines `{ "target": ... }`). It considers that prefix "used up" and mathematically deduces the *remaining* explicitly prefixed relation (`"source"`) must be the inbound link from the parent. +When compiling nested object graphs or arrays, the JSPG engine must dynamically infer which Postgres Foreign Key constraint correctly bridges the parent to the nested schema. It utilizes a strict 3-step hierarchical resolution applied during the `OnceLock` Compilation phase: +1. **Exact Prefix Match**: If an explicitly prefixed Foreign Key (e.g. `fk_invoice_counterparty_entity` -> `prefix: "counterparty"`) directly matches the name of the requested schema property (e.g. `{"counterparty": {...}}`), it is instantly selected. +2. **Ambiguity Elimination (M:M Twin Deduction)**: If multiple explicitly prefixed relations remain (which happens by design in Many-to-Many junction tables like `contact` or `role`), the compiler uses a process of elimination. It inspects the compiled child JSON schema AST to see which of the relational prefixes the child *natively consumes* as an explicit outbound property (e.g. `contact` natively defines `{ "target": ... }`). It considers that prefix arrow "used up" by the child, and mathematically deduces that its exact twin providing reverse ownership (`"source"`) MUST be the inbound link mapping from the parent. This logic relies on `OnceLock` recursive compilation to accurately peek at child structures. +3. **Implicit Base Fallback (1:M)**: If no explicit prefix matches, and M:M deduction fails, the compiler filters for exactly one remaining relation with a `null` prefix (e.g. `fk_invoice_line_invoice` -> `prefix: null`). A `null` prefix mathematically denotes the core structural parent-child ownership edge and is safely used as a fallback. ### Global API Reference These functions operate on the global `GLOBAL_JSPG` engine instance and provide administrative boundaries: diff --git a/src/database/schema.rs b/src/database/schema.rs index d0c20c6..1f75f29 100644 --- a/src/database/schema.rs +++ b/src/database/schema.rs @@ -497,6 +497,10 @@ impl Schema { Ok(()) } + /// Dynamically infers and compiles all structural database relationships between this Schema + /// and its nested children. This functions recursively traverses the JSON Schema abstract syntax + /// tree, identifies physical PostgreSQL table boundaries, and locks the resulting relation + /// constraint paths directly onto the `compiled_edges` map in O(1) memory. pub fn compile_edges( &self, db: &crate::database::Database, @@ -504,6 +508,9 @@ impl Schema { props: &std::collections::BTreeMap>, ) -> std::collections::BTreeMap { let mut schema_edges = std::collections::BTreeMap::new(); + + // Determine the physical Database Table Name this schema structurally represents + // Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person") let mut parent_type_name = None; if let Some(family) = &self.obj.family { parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string()); @@ -512,11 +519,14 @@ impl Schema { } if let Some(p_type) = parent_type_name { + // Proceed only if the resolved table physically exists within the Postgres Type hierarchy if db.types.contains_key(&p_type) { + // Iterate over all discovered schema boundaries mapped inside the object for (prop_name, prop_schema) in props { let mut child_type_name = None; let mut target_schema = prop_schema.clone(); + // Structurally unpack the inner target entity if the object maps to an array list if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &prop_schema.obj.type_ { @@ -527,6 +537,7 @@ impl Schema { } } + // Determine the physical Postgres table backing the nested child schema recursively if let Some(family) = &target_schema.obj.family { child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string()); } else if let Some(ref_id) = target_schema.obj.identifier() { @@ -541,10 +552,14 @@ impl Schema { if let Some(c_type) = child_type_name { if db.types.contains_key(&c_type) { + // Ensure the child Schema's AST has accurately compiled its own physical property keys so we can + // inject them securely for Many-to-Many Twin Deduction disambiguation matching. target_schema.compile(db, visited); if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() { let keys_for_ambiguity: Vec = compiled_target_props.keys().cloned().collect(); + + // Interrogate the Database catalog graph to discover the exact Foreign Key Constraint connecting the components if let Some((relation, is_forward)) = resolve_relation(db, &p_type, &c_type, prop_name, Some(&keys_for_ambiguity)) { @@ -566,6 +581,8 @@ impl Schema { } } +/// Inspects the Postgres pg_constraint relations catalog to securely identify +/// the precise Foreign Key connecting a parent and child hierarchy path. pub(crate) fn resolve_relation<'a>( db: &'a crate::database::Database, parent_type: &str, @@ -573,6 +590,8 @@ pub(crate) fn resolve_relation<'a>( prop_name: &str, relative_keys: Option<&Vec>, ) -> Option<(&'a crate::database::relation::Relation, bool)> { + + // Enforce graph locality by ensuring we don't accidentally crawl to pure structural entity boundaries if parent_type == "entity" && child_type == "entity" { return None; } @@ -583,6 +602,9 @@ pub(crate) fn resolve_relation<'a>( let mut matching_rels = Vec::new(); let mut directions = Vec::new(); + // Scour the complete catalog for any Edge matching the inheritance scope of the two objects + // This automatically binds polymorphic structures (e.g. recognizing a relationship targeting User + // also natively binds instances specifically typed as Person). for rel in db.relations.values() { let is_forward = p_def.hierarchy.contains(&rel.source_type) && c_def.hierarchy.contains(&rel.destination_type); @@ -598,10 +620,12 @@ pub(crate) fn resolve_relation<'a>( } } + // Abort relation discovery early if no hierarchical inheritance match was found if matching_rels.is_empty() { return None; } + // Ideal State: The objects only share a solitary structural relation, resolving ambiguity instantly. if matching_rels.len() == 1 { return Some((matching_rels[0], directions[0])); } @@ -609,6 +633,8 @@ pub(crate) fn resolve_relation<'a>( let mut chosen_idx = 0; let mut resolved = false; + // Exact Prefix Disambiguation: Determine if the database specifically names this constraint + // directly mapping to the JSON Schema property name (e.g., `fk_{child}_{property_name}`) for (i, rel) in matching_rels.iter().enumerate() { if let Some(prefix) = &rel.prefix { if prop_name.starts_with(prefix) @@ -622,9 +648,11 @@ pub(crate) fn resolve_relation<'a>( } } + // Complex Subgraph Resolution: The database contains multiple equally explicit foreign key constraints + // linking these objects (such as pointing to `source` and `target` in Many-to-Many junction models). if !resolved && relative_keys.is_some() { - // 1. M:M Disambiguation: The child schema explicitly defines an outbound property - // matching one of the relational prefixes (e.g. "target"). We first identify that consumed relation. + // Twin Deduction Pass 1: We inspect the exact properties structurally defined inside the compiled payload + // to observe which explicit relation arrow the child payload natively consumes. let keys = relative_keys.unwrap(); let mut consumed_rel_idx = None; for (i, rel) in matching_rels.iter().enumerate() { @@ -636,7 +664,8 @@ pub(crate) fn resolve_relation<'a>( } } - // Then, we find its exact Twin on the same junction boundary that provides the reverse ownership. + // Twin Deduction Pass 2: Knowing which arrow points outbound, we can mathematically deduce its twin + // providing the reverse ownership on the same junction boundary must be the incoming Edge to the parent. if let Some(used_idx) = consumed_rel_idx { let used_rel = matching_rels[used_idx]; let mut twin_ids = Vec::new(); @@ -657,8 +686,9 @@ pub(crate) fn resolve_relation<'a>( } } + // Implicit Base Fallback: If no complex explicit paths resolve, but exactly one relation + // sits entirely naked (without a constraint prefix), it must be the core structural parent ownership. if !resolved { - // 2. Base 1:M Fallback. If there's EXACTLY ONE relation with a null prefix, it's the base structural edge. let mut null_prefix_ids = Vec::new(); for (i, rel) in matching_rels.iter().enumerate() { if rel.prefix.is_none() { @@ -667,7 +697,6 @@ pub(crate) fn resolve_relation<'a>( } if null_prefix_ids.len() == 1 { chosen_idx = null_prefix_ids[0]; - // resolved = true; } }