Compare commits

...

5 Commits

Author SHA1 Message Date
cad651dbd8 version: 1.0.100 2026-03-27 19:14:08 -04:00
ea9ac8469c maybe working 2026-03-27 19:14:02 -04:00
ebcdb661fa maybe working 2026-03-27 19:13:44 -04:00
c893e29c59 version: 1.0.99 2026-03-27 18:02:29 -04:00
7523431007 test pgrx no fixes 2026-03-27 18:02:24 -04:00
7 changed files with 405 additions and 337 deletions

View File

View File

@ -19,7 +19,7 @@
{ {
"id": "22222222-2222-2222-2222-222222222222", "id": "22222222-2222-2222-2222-222222222222",
"type": "relation", "type": "relation",
"constraint": "fk_order_customer", "constraint": "fk_order_customer_person",
"source_type": "order", "source_type": "order",
"source_columns": [ "source_columns": [
"customer_id" "customer_id"
@ -41,8 +41,7 @@
"destination_type": "order", "destination_type": "order",
"destination_columns": [ "destination_columns": [
"id" "id"
], ]
"prefix": "lines"
}, },
{ {
"id": "44444444-4444-4444-4444-444444444444", "id": "44444444-4444-4444-4444-444444444444",
@ -75,6 +74,20 @@
"type" "type"
], ],
"prefix": "target" "prefix": "target"
},
{
"id": "66666666-6666-6666-6666-666666666666",
"type": "relation",
"constraint": "fk_entity_organization",
"source_type": "entity",
"source_columns": [
"organization_id"
],
"destination_type": "organization",
"destination_columns": [
"id"
],
"prefix": null
} }
], ],
"types": [ "types": [
@ -283,6 +296,17 @@
} }
} }
} }
},
"email_addresses": {
"type": "array",
"items": {
"$ref": "contact",
"properties": {
"target": {
"$ref": "email_address"
}
}
}
} }
} }
} }
@ -1834,16 +1858,18 @@
"type": "contact", "type": "contact",
"is_primary": false, "is_primary": false,
"target": { "target": {
"type": "phone_number", "type": "email_address",
"number": "555-0002" "address": "test@example.com"
} }
}, }
],
"email_addresses": [
{ {
"type": "contact", "type": "contact",
"is_primary": false, "is_primary": false,
"target": { "target": {
"type": "email_address", "type": "email_address",
"address": "test@example.com" "address": "test2@example.com"
} }
} }
] ]
@ -1935,7 +1961,10 @@
" modified_by", " modified_by",
") VALUES (", ") VALUES (",
" NULL,", " NULL,",
" '{\"number\":\"555-0001\",\"type\":\"phone_number\"}',", " '{",
" \"number\":\"555-0001\",",
" \"type\":\"phone_number\"",
" }',",
" '{{uuid:phone1_id}}',", " '{{uuid:phone1_id}}',",
" '{{uuid}}',", " '{{uuid}}',",
" 'create',", " 'create',",
@ -2006,115 +2035,6 @@
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[
"INSERT INTO agreego.\"entity\" (",
" \"created_at\",",
" \"created_by\",",
" \"id\",",
" \"modified_at\",",
" \"modified_by\",",
" \"type\"",
") VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:phone2_id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'phone_number'",
")"
],
[
"INSERT INTO agreego.\"phone_number\" (",
" \"number\"",
") VALUES (",
" '555-0002'",
")"
],
[
"INSERT INTO agreego.change (",
" \"old\",",
" \"new\",",
" entity_id,",
" id,",
" kind,",
" modified_at,",
" modified_by",
") VALUES (",
" NULL,",
" '{",
" \"number\":\"555-0002\",",
" \"type\":\"phone_number\"",
" }',",
" '{{uuid:phone2_id}}',",
" '{{uuid}}',",
" 'create',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'",
")"
],
[
"INSERT INTO agreego.\"entity\" (",
" \"created_at\",",
" \"created_by\",",
" \"id\",",
" \"modified_at\",",
" \"modified_by\",",
" \"type\"",
") VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:contact2_id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'contact'",
")"
],
[
"INSERT INTO agreego.\"relationship\" (",
" \"source_id\",",
" \"source_type\",",
" \"target_id\",",
" \"target_type\"",
") VALUES (",
" '{{uuid:person_id}}',",
" 'person',",
" '{{uuid:phone2_id}}',",
" 'phone_number'",
")"
],
[
"INSERT INTO agreego.\"contact\" (",
" \"is_primary\"",
") VALUES (",
" false",
")"
],
[
"INSERT INTO agreego.change (",
" \"old\",",
" \"new\",",
" entity_id,",
" id,",
" kind,",
" modified_at,",
" modified_by",
") VALUES (",
" NULL,",
" '{",
" \"is_primary\":false,",
" \"source_id\":\"{{uuid:person_id}}\",",
" \"source_type\":\"person\",",
" \"target_id\":\"{{uuid:phone2_id}}\",",
" \"target_type\":\"phone_number\",",
" \"type\":\"contact\"",
" }',",
" '{{uuid:contact2_id}}',",
" '{{uuid}}',",
" 'create',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'",
")"
],
[ [
"INSERT INTO agreego.\"entity\" (", "INSERT INTO agreego.\"entity\" (",
" \"created_at\",", " \"created_at\",",
@ -2172,7 +2092,7 @@
") VALUES (", ") VALUES (",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',", " '00000000-0000-0000-0000-000000000000',",
" '{{uuid:contact3_id}}',", " '{{uuid:contact2_id}}',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',", " '00000000-0000-0000-0000-000000000000',",
" 'contact'", " 'contact'",
@ -2217,6 +2137,115 @@
" \"target_type\":\"email_address\",", " \"target_type\":\"email_address\",",
" \"type\":\"contact\"", " \"type\":\"contact\"",
" }',", " }',",
" '{{uuid:contact2_id}}',",
" '{{uuid}}',",
" 'create',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'",
")"
],
[
"INSERT INTO agreego.\"entity\" (",
" \"created_at\",",
" \"created_by\",",
" \"id\",",
" \"modified_at\",",
" \"modified_by\",",
" \"type\"",
") VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:email2_id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'email_address'",
")"
],
[
"INSERT INTO agreego.\"email_address\" (",
" \"address\"",
") VALUES (",
" 'test2@example.com'",
")"
],
[
"INSERT INTO agreego.change (",
" \"old\",",
" \"new\",",
" entity_id,",
" id,",
" kind,",
" modified_at,",
" modified_by",
") VALUES (",
" NULL,",
" '{",
" \"address\":\"test2@example.com\",",
" \"type\":\"email_address\"",
" }',",
" '{{uuid:email2_id}}',",
" '{{uuid}}',",
" 'create',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'",
")"
],
[
"INSERT INTO agreego.\"entity\" (",
" \"created_at\",",
" \"created_by\",",
" \"id\",",
" \"modified_at\",",
" \"modified_by\",",
" \"type\"",
") VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:contact3_id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'contact'",
")"
],
[
"INSERT INTO agreego.\"relationship\" (",
" \"source_id\",",
" \"source_type\",",
" \"target_id\",",
" \"target_type\"",
") VALUES (",
" '{{uuid:person_id}}',",
" 'person',",
" '{{uuid:email2_id}}',",
" 'email_address'",
")"
],
[
"INSERT INTO agreego.\"contact\" (",
" \"is_primary\"",
") VALUES (",
" false",
")"
],
[
"INSERT INTO agreego.change (",
" \"old\",",
" \"new\",",
" entity_id,",
" id,",
" kind,",
" modified_at,",
" modified_by",
") VALUES (",
" NULL,",
" '{",
" \"is_primary\":false,",
" \"source_id\":\"{{uuid:person_id}}\",",
" \"source_type\":\"person\",",
" \"target_id\":\"{{uuid:email2_id}}\",",
" \"target_type\":\"email_address\",",
" \"type\":\"contact\"",
" }',",
" '{{uuid:contact3_id}}',", " '{{uuid:contact3_id}}',",
" '{{uuid}}',", " '{{uuid}}',",
" 'create',", " 'create',",
@ -2249,16 +2278,16 @@
], ],
[ [
"SELECT pg_notify('entity', '{", "SELECT pg_notify('entity', '{",
" \"complete\":{", " \"complete\":{",
" \"created_at\":\"{{timestamp}}\",", " \"created_at\":\"{{timestamp}}\",",
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",", " \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"first_name\":\"Relation\",", " \"first_name\":\"Relation\",",
" \"id\":\"{{uuid:person_id}}\",", " \"id\":\"{{uuid:person_id}}\",",
" \"last_name\":\"Test\",", " \"last_name\":\"Test\",",
" \"modified_at\":\"{{timestamp}}\",", " \"modified_at\":\"{{timestamp}}\",",
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"type\":\"person\"", " \"type\":\"person\"",
" },", " },",
" \"new\":{", " \"new\":{",
" \"first_name\":\"Relation\",", " \"first_name\":\"Relation\",",
" \"last_name\":\"Test\",", " \"last_name\":\"Test\",",
@ -2268,19 +2297,19 @@
], ],
[ [
"SELECT pg_notify('entity', '{", "SELECT pg_notify('entity', '{",
" \"complete\":{", " \"complete\":{",
" \"created_at\":\"{{timestamp}}\",", " \"created_at\":\"{{timestamp}}\",",
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",", " \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"id\":\"{{uuid:contact1_id}}\",", " \"id\":\"{{uuid:contact1_id}}\",",
" \"is_primary\":true,", " \"is_primary\":true,",
" \"modified_at\":\"{{timestamp}}\",", " \"modified_at\":\"{{timestamp}}\",",
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"source_id\":\"{{uuid:person_id}}\",", " \"source_id\":\"{{uuid:person_id}}\",",
" \"source_type\":\"person\",", " \"source_type\":\"person\",",
" \"target_id\":\"{{uuid:phone1_id}}\",", " \"target_id\":\"{{uuid:phone1_id}}\",",
" \"target_type\":\"phone_number\",", " \"target_type\":\"phone_number\",",
" \"type\":\"contact\"", " \"type\":\"contact\"",
" },", " },",
" \"new\":{", " \"new\":{",
" \"is_primary\":true,", " \"is_primary\":true,",
" \"source_id\":\"{{uuid:person_id}}\",", " \"source_id\":\"{{uuid:person_id}}\",",
@ -2293,15 +2322,15 @@
], ],
[ [
"SELECT pg_notify('entity', '{", "SELECT pg_notify('entity', '{",
" \"complete\":{", " \"complete\":{",
" \"created_at\":\"{{timestamp}}\",", " \"created_at\":\"{{timestamp}}\",",
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",", " \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"id\":\"{{uuid:phone1_id}}\",", " \"id\":\"{{uuid:phone1_id}}\",",
" \"modified_at\":\"{{timestamp}}\",", " \"modified_at\":\"{{timestamp}}\",",
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"number\":\"555-0001\",", " \"number\":\"555-0001\",",
" \"type\":\"phone_number\"", " \"type\":\"phone_number\"",
" },", " },",
" \"new\":{", " \"new\":{",
" \"number\":\"555-0001\",", " \"number\":\"555-0001\",",
" \"type\":\"phone_number\"", " \"type\":\"phone_number\"",
@ -2310,87 +2339,87 @@
], ],
[ [
"SELECT pg_notify('entity', '{", "SELECT pg_notify('entity', '{",
" \"complete\":{", " \"complete\":{",
" \"created_at\":\"{{timestamp}}\",", " \"created_at\":\"{{timestamp}}\",",
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",", " \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"id\":\"{{uuid:contact2_id}}\",", " \"id\":\"{{uuid:contact2_id}}\",",
" \"is_primary\":false,", " \"is_primary\":false,",
" \"modified_at\":\"{{timestamp}}\",", " \"modified_at\":\"{{timestamp}}\",",
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"source_id\":\"{{uuid:person_id}}\",", " \"source_id\":\"{{uuid:person_id}}\",",
" \"source_type\":\"person\",", " \"source_type\":\"person\",",
" \"target_id\":\"{{uuid:phone2_id}}\",", " \"target_id\":\"{{uuid:email1_id}}\",",
" \"target_type\":\"phone_number\",", " \"target_type\":\"email_address\",",
" \"type\":\"contact\"", " \"type\":\"contact\"",
" },", " },",
" \"new\":{", " \"new\":{",
" \"is_primary\":false,", " \"is_primary\":false,",
" \"source_id\":\"{{uuid:person_id}}\",", " \"source_id\":\"{{uuid:person_id}}\",",
" \"source_type\":\"person\",", " \"source_type\":\"person\",",
" \"target_id\":\"{{uuid:phone2_id}}\",", " \"target_id\":\"{{uuid:email1_id}}\",",
" \"target_type\":\"phone_number\",", " \"target_type\":\"email_address\",",
" \"type\":\"contact\"", " \"type\":\"contact\"",
" }", " }",
" }')" "}')"
], ],
[ [
"SELECT pg_notify('entity', '{", "SELECT pg_notify('entity', '{",
" \"complete\":{", " \"complete\":{",
" \"created_at\":\"{{timestamp}}\",", " \"address\":\"test@example.com\",",
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",", " \"created_at\":\"{{timestamp}}\",",
" \"id\":\"{{uuid:phone2_id}}\",", " \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\":\"{{timestamp}}\",", " \"id\":\"{{uuid:email1_id}}\",",
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", " \"modified_at\":\"{{timestamp}}\",",
" \"number\":\"555-0002\",", " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"type\":\"phone_number\"", " \"type\":\"email_address\"",
" },", " },",
" \"new\":{", " \"new\":{",
" \"number\":\"555-0002\",", " \"address\":\"test@example.com\",",
" \"type\":\"phone_number\"", " \"type\":\"email_address\"",
" }", " }",
" }')" "}')"
], ],
[ [
"SELECT pg_notify('entity', '{", "SELECT pg_notify('entity', '{",
" \"complete\":{", " \"complete\":{",
" \"created_at\":\"{{timestamp}}\",", " \"created_at\":\"{{timestamp}}\",",
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",", " \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"id\":\"{{uuid:contact3_id}}\",", " \"id\":\"{{uuid:contact3_id}}\",",
" \"is_primary\":false,", " \"is_primary\":false,",
" \"modified_at\":\"{{timestamp}}\",", " \"modified_at\":\"{{timestamp}}\",",
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"source_id\":\"{{uuid:person_id}}\",", " \"source_id\":\"{{uuid:person_id}}\",",
" \"source_type\":\"person\",", " \"source_type\":\"person\",",
" \"target_id\":\"{{uuid:email1_id}}\",", " \"target_id\":\"{{uuid:email2_id}}\",",
" \"target_type\":\"email_address\",", " \"target_type\":\"email_address\",",
" \"type\":\"contact\"", " \"type\":\"contact\"",
" },", " },",
" \"new\":{", " \"new\":{",
" \"is_primary\":false,", " \"is_primary\":false,",
" \"source_id\":\"{{uuid:person_id}}\",", " \"source_id\":\"{{uuid:person_id}}\",",
" \"source_type\":\"person\",", " \"source_type\":\"person\",",
" \"target_id\":\"{{uuid:email1_id}}\",", " \"target_id\":\"{{uuid:email2_id}}\",",
" \"target_type\":\"email_address\",", " \"target_type\":\"email_address\",",
" \"type\":\"contact\"", " \"type\":\"contact\"",
" }", " }",
" }')" "}')"
], ],
[ [
"SELECT pg_notify('entity', '{", "SELECT pg_notify('entity', '{",
" \"complete\":{", " \"complete\":{",
" \"address\":\"test@example.com\",", " \"address\":\"test2@example.com\",",
" \"created_at\":\"{{timestamp}}\",", " \"created_at\":\"{{timestamp}}\",",
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",", " \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"id\":\"{{uuid:email1_id}}\",", " \"id\":\"{{uuid:email2_id}}\",",
" \"modified_at\":\"{{timestamp}}\",", " \"modified_at\":\"{{timestamp}}\",",
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
" \"type\":\"email_address\"", " \"type\":\"email_address\"",
" },", " },",
" \"new\":{", " \"new\":{",
" \"address\":\"test@example.com\",", " \"address\":\"test2@example.com\",",
" \"type\":\"email_address\"", " \"type\":\"email_address\"",
" }", " }",
" }')" "}')"
] ]
] ]
} }

View File

@ -71,7 +71,7 @@
{ {
"id": "22222222-2222-2222-2222-222222222222", "id": "22222222-2222-2222-2222-222222222222",
"type": "relation", "type": "relation",
"constraint": "fk_order_customer", "constraint": "fk_order_customer_person",
"source_type": "order", "source_type": "order",
"source_columns": [ "source_columns": [
"customer_id" "customer_id"
@ -79,7 +79,8 @@
"destination_type": "person", "destination_type": "person",
"destination_columns": [ "destination_columns": [
"id" "id"
] ],
"prefix": "customer"
}, },
{ {
"id": "22222222-2222-2222-2222-222222222227", "id": "22222222-2222-2222-2222-222222222227",

View File

@ -79,9 +79,9 @@ impl DatabaseExecutor for SpiExecutor {
} }
} }
pgrx::debug1!("JSPG_SQL: {}", sql);
self.transact(|| { self.transact(|| {
Spi::connect(|client| { Spi::connect(|client| {
pgrx::notice!("JSPG_SQL: {}", sql);
match client.select(sql, Some(args_with_oid.len() as i64), &args_with_oid) { match client.select(sql, Some(args_with_oid.len() as i64), &args_with_oid) {
Ok(tup_table) => { Ok(tup_table) => {
let mut results = Vec::new(); let mut results = Vec::new();
@ -110,9 +110,9 @@ impl DatabaseExecutor for SpiExecutor {
} }
} }
pgrx::debug1!("JSPG_SQL: {}", sql);
self.transact(|| { self.transact(|| {
Spi::connect_mut(|client| { Spi::connect_mut(|client| {
pgrx::notice!("JSPG_SQL: {}", sql);
match client.update(sql, Some(args_with_oid.len() as i64), &args_with_oid) { match client.update(sql, Some(args_with_oid.len() as i64), &args_with_oid) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => Err(format!("SPI Execution Failure: {}", e)), Err(e) => Err(format!("SPI Execution Failure: {}", e)),

View File

@ -508,7 +508,7 @@ impl Schema {
if let Some(family) = &self.obj.family { if let Some(family) = &self.obj.family {
parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string()); parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
} else if let Some(identifier) = self.obj.identifier() { } else if let Some(identifier) = self.obj.identifier() {
parent_type_name = Some(identifier); parent_type_name = Some(identifier.split('.').next_back().unwrap_or(&identifier).to_string());
} }
if let Some(p_type) = parent_type_name { if let Some(p_type) = parent_type_name {
@ -530,11 +530,11 @@ impl Schema {
if let Some(family) = &target_schema.obj.family { if let Some(family) = &target_schema.obj.family {
child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string()); child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
} else if let Some(ref_id) = target_schema.obj.identifier() { } else if let Some(ref_id) = target_schema.obj.identifier() {
child_type_name = Some(ref_id); child_type_name = Some(ref_id.split('.').next_back().unwrap_or(&ref_id).to_string());
} else if let Some(arr) = &target_schema.obj.one_of { } else if let Some(arr) = &target_schema.obj.one_of {
if let Some(first) = arr.first() { if let Some(first) = arr.first() {
if let Some(ref_id) = first.obj.identifier() { if let Some(ref_id) = first.obj.identifier() {
child_type_name = Some(ref_id); child_type_name = Some(ref_id.split('.').next_back().unwrap_or(&ref_id).to_string());
} }
} }
} }
@ -622,8 +622,43 @@ pub(crate) fn resolve_relation<'a>(
} }
} }
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.
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
}
}
}
// Then, we find its exact Twin on the same junction boundary that provides the reverse ownership.
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;
}
}
}
if !resolved { if !resolved {
// 1. If there's EXACTLY ONE relation with a null prefix, it's the base structural edge. Pick it. // 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(); let mut null_prefix_ids = Vec::new();
for (i, rel) in matching_rels.iter().enumerate() { for (i, rel) in matching_rels.iter().enumerate() {
if rel.prefix.is_none() { if rel.prefix.is_none() {
@ -632,24 +667,6 @@ pub(crate) fn resolve_relation<'a>(
} }
if null_prefix_ids.len() == 1 { if null_prefix_ids.len() == 1 {
chosen_idx = null_prefix_ids[0]; chosen_idx = null_prefix_ids[0];
resolved = true;
}
}
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 mut missing_prefix_ids = Vec::new();
for (i, rel) in matching_rels.iter().enumerate() {
if let Some(prefix) = &rel.prefix {
if !keys.contains(prefix) {
missing_prefix_ids.push(i);
}
}
}
if missing_prefix_ids.len() == 1 {
chosen_idx = missing_prefix_ids[0];
// resolved = true; // resolved = true;
} }
} }

View File

@ -3,8 +3,8 @@
pub mod cache; pub mod cache;
use crate::database::r#type::Type;
use crate::database::Database; use crate::database::Database;
use crate::database::r#type::Type;
use serde_json::Value; use serde_json::Value;
use std::sync::Arc; use std::sync::Arc;
@ -25,19 +25,19 @@ impl Merger {
let mut notifications_queue = Vec::new(); let mut notifications_queue = Vec::new();
let target_schema = match self.db.schemas.get(schema_id) { let target_schema = match self.db.schemas.get(schema_id) {
Some(s) => Arc::new(s.clone()), Some(s) => Arc::new(s.clone()),
None => { None => {
return crate::drop::Drop::with_errors(vec![crate::drop::Error { return crate::drop::Drop::with_errors(vec![crate::drop::Error {
code: "MERGE_FAILED".to_string(), code: "MERGE_FAILED".to_string(),
message: format!("Unknown schema_id: {}", schema_id), message: format!("Unknown schema_id: {}", schema_id),
details: crate::drop::ErrorDetails { details: crate::drop::ErrorDetails {
path: "".to_string(), path: "".to_string(),
cause: None, cause: None,
context: Some(data), context: Some(data),
schema: None, schema: None,
}, },
}]); }]);
} }
}; };
let result = self.merge_internal(target_schema, data.clone(), &mut notifications_queue); let result = self.merge_internal(target_schema, data.clone(), &mut notifications_queue);
@ -50,18 +50,24 @@ impl Merger {
let mut final_cause = None; let mut final_cause = None;
if let Ok(Value::Object(map)) = serde_json::from_str::<Value>(&msg) { if let Ok(Value::Object(map)) = serde_json::from_str::<Value>(&msg) {
if let (Some(Value::String(e_msg)), Some(Value::String(e_code))) = (map.get("error"), map.get("code")) { if let (Some(Value::String(e_msg)), Some(Value::String(e_code))) =
(map.get("error"), map.get("code"))
{
final_message = e_msg.clone(); final_message = e_msg.clone();
final_code = e_code.clone(); final_code = e_code.clone();
let mut cause_parts = Vec::new(); let mut cause_parts = Vec::new();
if let Some(Value::String(d)) = map.get("detail") { if let Some(Value::String(d)) = map.get("detail") {
if !d.is_empty() { cause_parts.push(d.clone()); } if !d.is_empty() {
cause_parts.push(d.clone());
}
} }
if let Some(Value::String(h)) = map.get("hint") { if let Some(Value::String(h)) = map.get("hint") {
if !h.is_empty() { cause_parts.push(h.clone()); } if !h.is_empty() {
cause_parts.push(h.clone());
}
} }
if !cause_parts.is_empty() { if !cause_parts.is_empty() {
final_cause = Some(cause_parts.join("\n")); final_cause = Some(cause_parts.join("\n"));
} }
} }
} }
@ -144,11 +150,11 @@ impl Merger {
) -> Result<Value, String> { ) -> Result<Value, String> {
let mut item_schema = schema.clone(); let mut item_schema = schema.clone();
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ { if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ {
if t == "array" { if t == "array" {
if let Some(items_def) = &schema.obj.items { if let Some(items_def) = &schema.obj.items {
item_schema = items_def.clone(); item_schema = items_def.clone();
}
} }
}
} }
let mut resolved_items = Vec::new(); let mut resolved_items = Vec::new();
@ -178,8 +184,8 @@ impl Merger {
}; };
let compiled_props = match schema.obj.compiled_properties.get() { let compiled_props = match schema.obj.compiled_properties.get() {
Some(props) => props, Some(props) => props,
None => return Err("Schema has no compiled properties for merging".to_string()), None => return Err("Schema has no compiled properties for merging".to_string()),
}; };
let mut entity_fields = serde_json::Map::new(); let mut entity_fields = serde_json::Map::new();
@ -189,37 +195,37 @@ impl Merger {
for (k, v) in obj { for (k, v) in obj {
// Always retain system and unmapped core fields natively implicitly mapped to the Postgres tables // Always retain system and unmapped core fields natively implicitly mapped to the Postgres tables
if k == "id" || k == "type" || k == "created" { if k == "id" || k == "type" || k == "created" {
entity_fields.insert(k.clone(), v.clone()); entity_fields.insert(k.clone(), v.clone());
continue; continue;
} }
if let Some(prop_schema) = compiled_props.get(&k) { if let Some(prop_schema) = compiled_props.get(&k) {
let mut is_edge = false; let mut is_edge = false;
if let Some(edges) = schema.obj.compiled_edges.get() { if let Some(edges) = schema.obj.compiled_edges.get() {
if edges.contains_key(&k) { if edges.contains_key(&k) {
is_edge = true; is_edge = true;
} }
} }
if is_edge { if is_edge {
let typeof_v = match &v { let typeof_v = match &v {
Value::Object(_) => "object", Value::Object(_) => "object",
Value::Array(_) => "array", Value::Array(_) => "array",
_ => "field", // Malformed edge data? _ => "field", // Malformed edge data?
}; };
if typeof_v == "object" { if typeof_v == "object" {
entity_objects.insert(k.clone(), (v.clone(), prop_schema.clone())); entity_objects.insert(k.clone(), (v.clone(), prop_schema.clone()));
} else if typeof_v == "array" { } else if typeof_v == "array" {
entity_arrays.insert(k.clone(), (v.clone(), prop_schema.clone())); entity_arrays.insert(k.clone(), (v.clone(), prop_schema.clone()));
} else { } else {
entity_fields.insert(k.clone(), v.clone()); entity_fields.insert(k.clone(), v.clone());
} }
} else { } else {
// Not an edge! It's a raw Postgres column (e.g., JSONB, text[]) // Not an edge! It's a raw Postgres column (e.g., JSONB, text[])
entity_fields.insert(k.clone(), v.clone());
}
} else if type_def.fields.contains(&k) {
entity_fields.insert(k.clone(), v.clone()); entity_fields.insert(k.clone(), v.clone());
}
} else if type_def.fields.contains(&k) {
entity_fields.insert(k.clone(), v.clone());
} }
} }
@ -253,12 +259,16 @@ impl Merger {
}; };
if let Some(compiled_edges) = schema.obj.compiled_edges.get() { if let Some(compiled_edges) = schema.obj.compiled_edges.get() {
println!("Compiled Edges keys for relation {}: {:?}", relation_name, compiled_edges.keys().collect::<Vec<_>>()); println!(
"Compiled Edges keys for relation {}: {:?}",
relation_name,
compiled_edges.keys().collect::<Vec<_>>()
);
if let Some(edge) = compiled_edges.get(&relation_name) { if let Some(edge) = compiled_edges.get(&relation_name) {
println!("FOUND EDGE {} -> {:?}", relation_name, edge.constraint); println!("FOUND EDGE {} -> {:?}", relation_name, edge.constraint);
if let Some(relation) = self.db.relations.get(&edge.constraint) { if let Some(relation) = self.db.relations.get(&edge.constraint) {
let parent_is_source = edge.forward; let parent_is_source = edge.forward;
if parent_is_source { if parent_is_source {
if !relative.contains_key("organization_id") { if !relative.contains_key("organization_id") {
if let Some(org_id) = entity_fields.get("organization_id") { if let Some(org_id) = entity_fields.get("organization_id") {
@ -266,15 +276,16 @@ impl Merger {
} }
} }
let mut merged_relative = match self.merge_internal(rel_schema.clone(), Value::Object(relative), notifications)? { let mut merged_relative = match self.merge_internal(
rel_schema.clone(),
Value::Object(relative),
notifications,
)? {
Value::Object(m) => m, Value::Object(m) => m,
_ => continue, _ => continue,
}; };
merged_relative.insert( merged_relative.insert("type".to_string(), Value::String(relative_type_name));
"type".to_string(),
Value::String(relative_type_name),
);
Self::apply_entity_relation( Self::apply_entity_relation(
&mut entity_fields, &mut entity_fields,
@ -297,7 +308,11 @@ impl Merger {
&entity_fields, &entity_fields,
); );
let merged_relative = match self.merge_internal(rel_schema.clone(), Value::Object(relative), notifications)? { let merged_relative = match self.merge_internal(
rel_schema.clone(),
Value::Object(relative),
notifications,
)? {
Value::Object(m) => m, Value::Object(m) => m,
_ => continue, _ => continue,
}; };
@ -360,19 +375,24 @@ impl Merger {
); );
let mut item_schema = rel_schema.clone(); let mut item_schema = rel_schema.clone();
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &rel_schema.obj.type_ { if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) =
if t == "array" { &rel_schema.obj.type_
if let Some(items_def) = &rel_schema.obj.items { {
item_schema = items_def.clone(); if t == "array" {
} if let Some(items_def) = &rel_schema.obj.items {
item_schema = items_def.clone();
} }
}
} }
let merged_relative = let merged_relative = match self.merge_internal(
match self.merge_internal(item_schema, Value::Object(relative_item), notifications)? { item_schema,
Value::Object(m) => m, Value::Object(relative_item),
_ => continue, notifications,
}; )? {
Value::Object(m) => m,
_ => continue,
};
relative_responses.push(Value::Object(merged_relative)); relative_responses.push(Value::Object(merged_relative));
} }
@ -433,8 +453,8 @@ impl Merger {
// An anchor is STRICTLY a struct containing merely an `id` and `type`. // An anchor is STRICTLY a struct containing merely an `id` and `type`.
// We aggressively bypass Database SPI `SELECT` fetches because there are no primitive // We aggressively bypass Database SPI `SELECT` fetches because there are no primitive
// mutations to apply to the row. PostgreSQL inherently protects relationships via Foreign Keys downstream. // mutations to apply to the row. PostgreSQL inherently protects relationships via Foreign Keys downstream.
let is_anchor = entity_fields.len() == 2 let is_anchor = entity_fields.len() == 2
&& entity_fields.contains_key("id") && entity_fields.contains_key("id")
&& entity_fields.contains_key("type"); && entity_fields.contains_key("type");
let has_valid_id = entity_fields let has_valid_id = entity_fields
@ -450,13 +470,13 @@ impl Merger {
let mut replaces_id = None; let mut replaces_id = None;
if let Some(ref fetched_row) = entity_fetched { if let Some(ref fetched_row) = entity_fetched {
let provided_id = entity_fields.get("id").and_then(|v| v.as_str()); let provided_id = entity_fields.get("id").and_then(|v| v.as_str());
let fetched_id = fetched_row.get("id").and_then(|v| v.as_str()); let fetched_id = fetched_row.get("id").and_then(|v| v.as_str());
if let (Some(pid), Some(fid)) = (provided_id, fetched_id) { if let (Some(pid), Some(fid)) = (provided_id, fetched_id) {
if !pid.is_empty() && pid != fid { if !pid.is_empty() && pid != fid {
replaces_id = Some(pid.to_string()); replaces_id = Some(pid.to_string());
}
} }
}
} }
let system_keys = vec![ let system_keys = vec![
@ -548,7 +568,12 @@ impl Merger {
entity_fields = new_fields; entity_fields = new_fields;
} }
Ok((entity_fields, entity_change_kind, entity_fetched, replaces_id)) Ok((
entity_fields,
entity_change_kind,
entity_fetched,
replaces_id,
))
} }
fn fetch_entity( fn fetch_entity(
@ -735,9 +760,7 @@ impl Merger {
columns.join(", "), columns.join(", "),
values.join(", ") values.join(", ")
); );
self self.db.execute(&sql, None)?;
.db
.execute(&sql, None)?;
} else if change_kind == "update" || change_kind == "delete" { } else if change_kind == "update" || change_kind == "delete" {
entity_pairs.remove("id"); entity_pairs.remove("id");
entity_pairs.remove("type"); entity_pairs.remove("type");
@ -769,9 +792,7 @@ impl Merger {
set_clauses.join(", "), set_clauses.join(", "),
Self::quote_literal(&Value::String(id_str.to_string())) Self::quote_literal(&Value::String(id_str.to_string()))
); );
self self.db.execute(&sql, None)?;
.db
.execute(&sql, None)?;
} }
} }
@ -857,13 +878,13 @@ impl Merger {
let mut notification = serde_json::Map::new(); let mut notification = serde_json::Map::new();
notification.insert("complete".to_string(), Value::Object(complete)); notification.insert("complete".to_string(), Value::Object(complete));
notification.insert("new".to_string(), new_val_obj.clone()); notification.insert("new".to_string(), new_val_obj.clone());
if old_val_obj != Value::Null { if old_val_obj != Value::Null {
notification.insert("old".to_string(), old_val_obj.clone()); notification.insert("old".to_string(), old_val_obj.clone());
} }
if let Some(rep) = replaces_id { if let Some(rep) = replaces_id {
notification.insert("replaces".to_string(), Value::String(rep.to_string())); notification.insert("replaces".to_string(), Value::String(rep.to_string()));
} }
let mut notify_sql = None; let mut notify_sql = None;

View File

@ -1 +1 @@
1.0.98 1.0.100