more tests

This commit is contained in:
2026-04-10 01:06:02 -04:00
parent 3cca5ef2d5
commit be78af1507
11 changed files with 1081 additions and 965 deletions

View File

@ -4,6 +4,7 @@ pub mod executors;
pub mod formats;
pub mod page;
pub mod punc;
pub mod object;
pub mod relation;
pub mod schema;
pub mod r#type;
@ -23,7 +24,8 @@ use punc::Punc;
use relation::Relation;
use schema::Schema;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::sync::Arc;
use r#type::Type;
pub struct Database {
@ -31,8 +33,7 @@ pub struct Database {
pub types: HashMap<String, Type>,
pub puncs: HashMap<String, Punc>,
pub relations: HashMap<String, Relation>,
pub schemas: HashMap<String, Schema>,
pub depths: HashMap<String, usize>,
pub schemas: HashMap<String, Arc<Schema>>,
pub executor: Box<dyn DatabaseExecutor + Send + Sync>,
}
@ -44,7 +45,6 @@ impl Database {
relations: HashMap::new(),
puncs: HashMap::new(),
schemas: HashMap::new(),
depths: HashMap::new(),
#[cfg(not(test))]
executor: Box::new(SpiExecutor::new()),
#[cfg(test)]
@ -135,7 +135,7 @@ impl Database {
.clone()
.unwrap_or_else(|| format!("schema_{}", i));
schema.obj.id = Some(id.clone());
db.schemas.insert(id, schema);
db.schemas.insert(id, Arc::new(schema));
}
Err(e) => {
errors.push(crate::drop::Error {
@ -185,18 +185,21 @@ impl Database {
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
let mut harvested = Vec::new();
for schema in self.schemas.values_mut() {
schema.collect_schemas(None, &mut harvested, errors);
for schema_arc in self.schemas.values_mut() {
if let Some(s) = std::sync::Arc::get_mut(schema_arc) {
s.collect_schemas(None, &mut harvested, errors);
}
}
for (id, schema) in harvested {
self.schemas.insert(id, Arc::new(schema));
}
self.schemas.extend(harvested);
self.collect_schemas(errors);
self.collect_depths();
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
let mut visited = std::collections::HashSet::new();
for schema in self.schemas.values() {
schema.compile(self, &mut visited, errors);
for schema_arc in self.schemas.values() {
schema_arc.as_ref().compile(self, &mut visited, errors);
}
}
@ -222,35 +225,185 @@ impl Database {
}
for (id, schema) in to_insert {
self.schemas.insert(id, schema);
self.schemas.insert(id, Arc::new(schema));
}
}
fn collect_depths(&mut self) {
let mut depths: HashMap<String, usize> = HashMap::new();
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
/// Inspects the Postgres pg_constraint relations catalog to securely identify
/// the precise Foreign Key connecting a parent and child hierarchy path.
pub fn resolve_relation<'a>(
&'a self,
parent_type: &str,
child_type: &str,
prop_name: &str,
relative_keys: Option<&Vec<String>>,
is_array: bool,
schema_id: Option<&str>,
path: &str,
errors: &mut Vec<crate::drop::Error>,
) -> 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;
}
for id in schema_ids {
let mut current_id = id.clone();
let mut depth = 0;
let mut visited = HashSet::new();
let p_def = self.types.get(parent_type)?;
let c_def = self.types.get(child_type)?;
while let Some(schema) = self.schemas.get(&current_id) {
if !visited.insert(current_id.clone()) {
break; // Cycle detected
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).
let mut all_rels: Vec<&crate::database::relation::Relation> = self.relations.values().collect();
all_rels.sort_by(|a, b| a.constraint.cmp(&b.constraint));
for rel in all_rels {
let mut is_forward =
p_def.hierarchy.contains(&rel.source_type) && c_def.hierarchy.contains(&rel.destination_type);
let is_reverse =
p_def.hierarchy.contains(&rel.destination_type) && c_def.hierarchy.contains(&rel.source_type);
// Structural Cardinality Filtration:
// If the schema requires a collection (Array), it is mathematically impossible for a pure
// Forward scalar edge (where the parent holds exactly one UUID pointer) to fulfill a One-to-Many request.
// Thus, if it's an array, we fully reject pure Forward edges and only accept Reverse edges (or Junction edges).
if is_array && is_forward && !is_reverse {
is_forward = false;
}
if is_forward {
matching_rels.push(rel);
directions.push(true);
} else if is_reverse {
matching_rels.push(rel);
directions.push(false);
}
}
// Abort relation discovery early if no hierarchical inheritance match was found
if matching_rels.is_empty() {
let mut details = crate::drop::ErrorDetails {
path: path.to_string(),
..Default::default()
};
if let Some(sid) = schema_id {
details.schema = Some(sid.to_string());
}
errors.push(crate::drop::Error {
code: "EDGE_MISSING".to_string(),
message: format!(
"No database relation exists between '{}' and '{}' for property '{}'",
parent_type, child_type, prop_name
),
details,
});
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]));
}
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)
|| prefix.starts_with(prop_name)
|| prefix.replace("_", "") == prop_name.replace("_", "")
{
chosen_idx = i;
resolved = true;
break;
}
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ {
if !crate::database::schema::is_primitive_type(t) {
current_id = t.clone();
depth += 1;
continue;
}
}
// 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() {
// 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() {
if let Some(prefix) = &rel.prefix {
if keys.contains(prefix) {
consumed_rel_idx = Some(i);
break; // Found the routing edge explicitly consumed by the schema payload
}
}
break;
}
depths.insert(id, depth);
}
self.depths = depths;
}
// 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();
for (i, rel) in matching_rels.iter().enumerate() {
if i != used_idx
&& rel.source_type == used_rel.source_type
&& rel.destination_type == used_rel.destination_type
&& rel.prefix.is_some()
{
twin_ids.push(i);
}
}
if twin_ids.len() == 1 {
chosen_idx = twin_ids[0];
resolved = true;
}
}
}
// 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 {
let mut null_prefix_ids = Vec::new();
for (i, rel) in matching_rels.iter().enumerate() {
if rel.prefix.is_none() {
null_prefix_ids.push(i);
}
}
if null_prefix_ids.len() == 1 {
chosen_idx = null_prefix_ids[0];
resolved = true;
}
}
// If we exhausted all mathematical deduction pathways and STILL cannot isolate a single edge,
// we must abort rather than silently guessing. Returning None prevents arbitrary SQL generation
// and forces a clean structural error for the architect.
if !resolved {
let mut details = crate::drop::ErrorDetails {
path: path.to_string(),
context: serde_json::to_value(&matching_rels).ok(),
cause: Some("Multiple conflicting constraints found matching prefixes".to_string()),
..Default::default()
};
if let Some(sid) = schema_id {
details.schema = Some(sid.to_string());
}
errors.push(crate::drop::Error {
code: "AMBIGUOUS_TYPE_RELATIONS".to_string(),
message: format!(
"Ambiguous database relation between '{}' and '{}' for property '{}'",
parent_type, child_type, prop_name
),
details,
});
return None;
}
Some((matching_rels[chosen_idx], directions[chosen_idx]))
}
}