fixed relationship resolution in merger and queryer
This commit is contained in:
@ -23,6 +23,12 @@ To support high-throughput operations while allowing for runtime updates (e.g.,
|
|||||||
3. **Immutable AST Caching**: The `Validator` struct immutably owns the `Database` registry. Schemas themselves are frozen structurally, but utilize `OnceLock` interior mutability during the Compilation Phase to permanently cache resolved `$ref` inheritances, properties, and `compiled_edges` directly onto their AST nodes. This guarantees strict `O(1)` relationship and property validation execution at runtime without locking or recursive DB polling.
|
3. **Immutable AST Caching**: The `Validator` struct immutably owns the `Database` registry. Schemas themselves are frozen structurally, but utilize `OnceLock` interior mutability during the Compilation Phase to permanently cache resolved `$ref` inheritances, properties, and `compiled_edges` directly onto their AST nodes. This guarantees strict `O(1)` relationship and property validation execution at runtime without locking or recursive DB polling.
|
||||||
4. **Lock-Free Reads**: Incoming operations acquire a read lock just long enough to clone the `Arc` inside an `RwLock<Option<Arc<Validator>>>`, ensuring zero blocking during schema updates.
|
4. **Lock-Free Reads**: Incoming operations acquire a read lock just long enough to clone the `Arc` inside an `RwLock<Option<Arc<Validator>>>`, 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.
|
||||||
|
|
||||||
### Global API Reference
|
### Global API Reference
|
||||||
These functions operate on the global `GLOBAL_JSPG` engine instance and provide administrative boundaries:
|
These functions operate on the global `GLOBAL_JSPG` engine instance and provide administrative boundaries:
|
||||||
|
|
||||||
|
|||||||
0
agreego.sql
Normal file
0
agreego.sql
Normal file
@ -27,7 +27,9 @@
|
|||||||
{
|
{
|
||||||
"$id": "get_orders.response",
|
"$id": "get_orders.response",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "$ref": "light.order" }
|
"items": {
|
||||||
|
"$ref": "light.order"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -77,8 +79,23 @@
|
|||||||
"destination_type": "person",
|
"destination_type": "person",
|
||||||
"destination_columns": [
|
"destination_columns": [
|
||||||
"id"
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "22222222-2222-2222-2222-222222222227",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_order_counterparty_entity",
|
||||||
|
"source_type": "order",
|
||||||
|
"source_columns": [
|
||||||
|
"counterparty_id",
|
||||||
|
"counterparty_type"
|
||||||
],
|
],
|
||||||
"prefix": "customer"
|
"destination_type": "entity",
|
||||||
|
"destination_columns": [
|
||||||
|
"id",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"prefix": "counterparty"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "33333333-3333-3333-3333-333333333333",
|
"id": "33333333-3333-3333-3333-333333333333",
|
||||||
@ -91,8 +108,7 @@
|
|||||||
"destination_type": "order",
|
"destination_type": "order",
|
||||||
"destination_columns": [
|
"destination_columns": [
|
||||||
"id"
|
"id"
|
||||||
],
|
]
|
||||||
"prefix": "lines"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"types": [
|
"types": [
|
||||||
@ -713,14 +729,18 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"counterparty_id",
|
||||||
|
"counterparty_type"
|
||||||
],
|
],
|
||||||
"grouped_fields": {
|
"grouped_fields": {
|
||||||
"order": [
|
"order": [
|
||||||
"id",
|
"id",
|
||||||
"type",
|
"type",
|
||||||
"total",
|
"total",
|
||||||
"customer_id"
|
"customer_id",
|
||||||
|
"counterparty_id",
|
||||||
|
"counterparty_type"
|
||||||
],
|
],
|
||||||
"entity": [
|
"entity": [
|
||||||
"id",
|
"id",
|
||||||
@ -748,7 +768,9 @@
|
|||||||
"created_at": "timestamptz",
|
"created_at": "timestamptz",
|
||||||
"created_by": "uuid",
|
"created_by": "uuid",
|
||||||
"modified_at": "timestamptz",
|
"modified_at": "timestamptz",
|
||||||
"modified_by": "uuid"
|
"modified_by": "uuid",
|
||||||
|
"counterparty_id": "uuid",
|
||||||
|
"counterparty_type": "text"
|
||||||
},
|
},
|
||||||
"variations": [
|
"variations": [
|
||||||
"order"
|
"order"
|
||||||
|
|||||||
@ -622,7 +622,23 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !resolved {
|
||||||
|
// 1. If there's EXACTLY ONE relation with a null prefix, it's the base structural edge. Pick it.
|
||||||
|
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 !resolved && relative_keys.is_some() {
|
if !resolved && relative_keys.is_some() {
|
||||||
|
// 2. M:M Disambiguation: The child schema will explicitly define an outbound property
|
||||||
|
// matching one of the relational prefixes (e.g. "target"). We use the OTHER one (e.g. "source").
|
||||||
let keys = relative_keys.unwrap();
|
let keys = relative_keys.unwrap();
|
||||||
let mut missing_prefix_ids = Vec::new();
|
let mut missing_prefix_ids = Vec::new();
|
||||||
for (i, rel) in matching_rels.iter().enumerate() {
|
for (i, rel) in matching_rels.iter().enumerate() {
|
||||||
@ -634,6 +650,7 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
}
|
}
|
||||||
if missing_prefix_ids.len() == 1 {
|
if missing_prefix_ids.len() == 1 {
|
||||||
chosen_idx = missing_prefix_ids[0];
|
chosen_idx = missing_prefix_ids[0];
|
||||||
|
// resolved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user