Compare commits

...

3 Commits

Author SHA1 Message Date
abf1d12e43 test: mock matcher understands UNION existence reads; revive dead update-path mocks
parse_and_match_mocks matched mock.type against only the first FROM table —
always 'entity' for hierarchy existence reads — so every merger update-case
mock was silently inert and those cases snapshotted the create path. Now each
UNION arm is matched independently (a mock matching any arm returns once,
mirroring UNION dedup) and the type check accepts any FROM/JOIN table.

Regenerated the five revived cases (their snapshots now show real update /
no-op replace / archive semantics) and added the two previously untestable
probe-disagreement cases: same-row dedup (updates, no error) and id-vs-lookup
conflict (TOO_MANY_LOOKUP_ROWS).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:05:21 -04:00
a885713563 version: 1.0.170 2026-07-06 13:05:16 -04:00
d9b4f417f6 merger existence read: UNION the id and lk-lookup probes instead of OR-ing them across the hierarchy join — the OR is un-indexable and full-scans the subtype table on every child merge (one scan per relationship-edge write; surfaced by Castleberry onboarding); each UNION arm uses its own index (pk / lk_) and dedup preserves TOO_MANY_LOOKUP_ROWS semantics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:52:29 -04:00
5 changed files with 362 additions and 470 deletions

View File

@ -1320,62 +1320,17 @@
" AND \"pronouns\" = 'they/them'))" " AND \"pronouns\" = 'they/them'))"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "UPDATE agreego.\"entity\" SET",
" \"created_at\",", " modified_at = '{{timestamp}}',",
" \"created_by\",", " modified_by = '00000000-0000-0000-0000-000000000000'",
" \"id\",", "WHERE",
" \"modified_at\",", " id = '{{uuid:mocks.0.id}}'"
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:generated_0}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.\"organization\" (", "UPDATE agreego.\"person\" SET",
" \"id\",", " contact_id = 'abc-contact'",
" \"type\"", "WHERE",
")", " id = '{{uuid:mocks.0.id}}'"
"VALUES (",
" '{{uuid:generated_0}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:generated_0}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"contact_id\",",
" \"date_of_birth\",",
" \"first_name\",",
" \"id\",",
" \"last_name\",",
" \"pronouns\",",
" \"type\"",
")",
"VALUES (",
" 'abc-contact',",
" '{{timestamp}}',",
" 'LookupFirst',",
" '{{uuid:generated_0}}',",
" 'LookupLast',",
" 'they/them',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.change (", "INSERT INTO agreego.change (",
@ -1388,45 +1343,40 @@
" \"modified_by\"", " \"modified_by\"",
")", ")",
"VALUES (", "VALUES (",
" NULL,",
" '{", " '{",
" \"first_name\": \"LookupFirst\",", " \"contact_id\": \"old-contact\"",
" \"last_name\": \"LookupLast\",", " }',",
" \"date_of_birth\": \"{{timestamp}}\",", " '{",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" }',", " }',",
" '{{uuid:mocks.0.id}}',",
" '{{uuid:generated_0}}',", " '{{uuid:generated_0}}',",
" '{{uuid:generated_1}}',", " 'update',",
" 'create',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"update\",",
" \"complete\": {", " \"complete\": {",
" \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",",
" \"first_name\": \"LookupFirst\",", " \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",", " \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",", " \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",", " \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"id\": \"{{uuid:generated_0}}\",",
" \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"created_at\": \"{{timestamp}}\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
" \"new\": {", " \"new\": {",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" },",
" \"old\": {",
" \"contact_id\": \"old-contact\"",
" }", " }",
"}'))" "}'))"
] ]
@ -1461,75 +1411,20 @@
"success": true, "success": true,
"sql": [ "sql": [
[ [
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", "(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE t1.id = '{{uuid:data.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE (\"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '{{timestamp}}' AND \"pronouns\" = 'they/them'))"
"FROM agreego.\"entity\" t1", ],
"JOIN agreego.\"organization\" t2 ON ", [
"JOIN agreego.\"user\" t3 ON ", "UPDATE agreego.\"entity\" SET",
"JOIN agreego.\"person\" t4 ON ", " modified_at = '{{timestamp}}',",
" modified_by = '00000000-0000-0000-0000-000000000000'",
"WHERE", "WHERE",
" t1.id = '{{uuid:data.id}}'", " id = '{{uuid:mocks.0.id}}'"
" OR (\"first_name\" = 'LookupFirst'",
" AND \"last_name\" = 'LookupLast'",
" AND \"date_of_birth\" = '{{timestamp}}'",
" AND \"pronouns\" = 'they/them'))"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "UPDATE agreego.\"person\" SET",
" \"created_at\",", " contact_id = 'abc-contact'",
" \"created_by\",", "WHERE",
" \"id\",", " id = '{{uuid:mocks.0.id}}'"
" \"modified_at\",",
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:data.id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"organization\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:data.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:data.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"contact_id\",",
" \"date_of_birth\",",
" \"first_name\",",
" \"id\",",
" \"last_name\",",
" \"pronouns\",",
" \"type\"",
")",
"VALUES (",
" 'abc-contact',",
" '{{timestamp}}',",
" 'LookupFirst',",
" '{{uuid:data.id}}',",
" 'LookupLast',",
" 'they/them',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.change (", "INSERT INTO agreego.change (",
@ -1542,46 +1437,42 @@
" \"modified_by\"", " \"modified_by\"",
")", ")",
"VALUES (", "VALUES (",
" NULL,",
" '{", " '{",
" \"first_name\": \"LookupFirst\",", " \"contact_id\": \"old-contact\"",
" \"last_name\": \"LookupLast\",", " }',",
" \"date_of_birth\": \"{{timestamp}}\",", " '{",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" }',", " }',",
" '{{uuid:data.id}}',", " '{{uuid:mocks.0.id}}',",
" '{{uuid:generated_0}}',", " '{{uuid:generated_0}}',",
" 'create',", " 'update',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"update\",",
" \"complete\": {", " \"complete\": {",
" \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",",
" \"first_name\": \"LookupFirst\",", " \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",", " \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",", " \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",", " \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"id\": \"{{uuid:data.id}}\",",
" \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"created_at\": \"{{timestamp}}\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
" \"new\": {", " \"new\": {",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" }", " },",
" \"old\": {",
" \"contact_id\": \"old-contact\"",
" },",
" \"replaces\": \"{{uuid:data.id}}\"",
"}'))" "}'))"
] ]
] ]
@ -1614,122 +1505,26 @@
"success": true, "success": true,
"sql": [ "sql": [
[ [
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", "(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE t1.id = '{{uuid:data.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE (\"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '{{timestamp}}' AND \"pronouns\" = 'they/them'))"
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"organization\" t2 ON ",
"JOIN agreego.\"user\" t3 ON ",
"JOIN agreego.\"person\" t4 ON ",
"WHERE",
" t1.id = '{{uuid:data.id}}'",
" OR (\"first_name\" = 'LookupFirst'",
" AND \"last_name\" = 'LookupLast'",
" AND \"date_of_birth\" = '{{timestamp}}'",
" AND \"pronouns\" = 'they/them'))"
],
[
"INSERT INTO agreego.\"entity\" (",
" \"created_at\",",
" \"created_by\",",
" \"id\",",
" \"modified_at\",",
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:data.id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"organization\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:data.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:data.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"date_of_birth\",",
" \"first_name\",",
" \"id\",",
" \"last_name\",",
" \"pronouns\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" 'LookupFirst',",
" '{{uuid:data.id}}',",
" 'LookupLast',",
" 'they/them',",
" 'person'",
")"
],
[
"INSERT INTO agreego.change (",
" \"old\",",
" \"new\",",
" \"entity_id\",",
" \"id\",",
" \"kind\",",
" \"modified_at\",",
" \"modified_by\"",
")",
"VALUES (",
" NULL,",
" '{",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"type\": \"person\"",
" }',",
" '{{uuid:data.id}}',",
" '{{uuid:generated_0}}',",
" 'create',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'",
")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"replace\",",
" \"complete\": {", " \"complete\": {",
" \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",",
" \"first_name\": \"LookupFirst\",", " \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",", " \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",", " \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",", " \"pronouns\": \"they/them\",",
" \"id\": \"{{uuid:data.id}}\",", " \"contact_id\": \"old-contact\",",
" \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"created_at\": \"{{timestamp}}\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
" \"new\": {", " \"new\": {",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" }", " },",
" \"replaces\": \"{{uuid:data.id}}\"",
"}'))" "}'))"
] ]
] ]
@ -1766,56 +1561,18 @@
" t1.id = '{{uuid:mocks.0.id}}')" " t1.id = '{{uuid:mocks.0.id}}')"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "UPDATE agreego.\"entity\" SET",
" \"created_at\",", " modified_at = '{{timestamp}}',",
" \"created_by\",", " modified_by = '00000000-0000-0000-0000-000000000000'",
" \"id\",", "WHERE",
" \"modified_at\",", " id = '{{uuid:mocks.0.id}}'"
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:mocks.0.id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.\"organization\" (", "UPDATE agreego.\"person\" SET",
" \"id\",", " first_name = 'NewFirst',",
" \"type\"", " last_name = 'NewLast'",
")", "WHERE",
"VALUES (", " id = '{{uuid:mocks.0.id}}'"
" '{{uuid:mocks.0.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:mocks.0.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"first_name\",",
" \"id\",",
" \"last_name\",",
" \"type\"",
")",
"VALUES (",
" 'NewFirst',",
" '{{uuid:mocks.0.id}}',",
" 'NewLast',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.change (", "INSERT INTO agreego.change (",
@ -1828,7 +1585,10 @@
" \"modified_by\"", " \"modified_by\"",
")", ")",
"VALUES (", "VALUES (",
" NULL,", " '{",
" \"first_name\": \"OldFirst\",",
" \"last_name\": \"OldLast\"",
" }',",
" '{", " '{",
" \"first_name\": \"NewFirst\",", " \"first_name\": \"NewFirst\",",
" \"last_name\": \"NewLast\",", " \"last_name\": \"NewLast\",",
@ -1836,21 +1596,19 @@
" }',", " }',",
" '{{uuid:mocks.0.id}}',", " '{{uuid:mocks.0.id}}',",
" '{{uuid:generated_0}}',", " '{{uuid:generated_0}}',",
" 'create',", " 'update',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"update\",",
" \"complete\": {", " \"complete\": {",
" \"first_name\": \"NewFirst\",",
" \"last_name\": \"NewLast\",",
" \"id\": \"{{uuid:mocks.0.id}}\",", " \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",", " \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",", " \"first_name\": \"NewFirst\",",
" \"created_at\": \"{{timestamp}}\",", " \"last_name\": \"NewLast\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
@ -1858,6 +1616,10 @@
" \"first_name\": \"NewFirst\",", " \"first_name\": \"NewFirst\",",
" \"last_name\": \"NewLast\",", " \"last_name\": \"NewLast\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" },",
" \"old\": {",
" \"first_name\": \"OldFirst\",",
" \"last_name\": \"OldLast\"",
" }", " }",
"}'))" "}'))"
] ]
@ -2219,12 +1981,7 @@
"success": true, "success": true,
"sql": [ "sql": [
[ [
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*)", "(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"order\" t2 ON t2.id = t1.id WHERE t1.id = 'abc' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"order\" t2 ON t2.id = t1.id WHERE (\"id\" = 'abc'))"
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"order\" t2 ON ",
"WHERE",
" t1.id = 'abc'",
" OR (\"id\" = 'abc'))"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "INSERT INTO agreego.\"entity\" (",
@ -3031,54 +2788,12 @@
" t1.id = 'abc-archived')" " t1.id = 'abc-archived')"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "UPDATE agreego.\"entity\" SET",
" \"archived\",", " archived = true,",
" \"created_at\",", " modified_at = '{{timestamp}}',",
" \"created_by\",", " modified_by = '00000000-0000-0000-0000-000000000000'",
" \"id\",", "WHERE",
" \"modified_at\",", " id = 'abc-archived'"
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" true,",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'abc-archived',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"organization\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" 'abc-archived',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" 'abc-archived',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" 'abc-archived',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.change (", "INSERT INTO agreego.change (",
@ -3091,33 +2806,38 @@
" \"modified_by\"", " \"modified_by\"",
")", ")",
"VALUES (", "VALUES (",
" NULL,", " '{",
" \"archived\": false",
" }',",
" '{", " '{",
" \"archived\": true,", " \"archived\": true,",
" \"type\": \"person\"", " \"type\": \"person\"",
" }',", " }',",
" 'abc-archived',", " 'abc-archived',",
" '{{uuid:generated_0}}',", " '{{uuid:generated_0}}',",
" 'create',", " 'delete',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"delete\",",
" \"complete\": {", " \"complete\": {",
" \"archived\": true,",
" \"id\": \"abc-archived\",", " \"id\": \"abc-archived\",",
" \"type\": \"person\",", " \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",", " \"first_name\": \"ArchivedFirst\",",
" \"created_at\": \"{{timestamp}}\",", " \"last_name\": \"ArchivedLast\",",
" \"archived\": true,",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
" \"new\": {", " \"new\": {",
" \"archived\": true,", " \"archived\": true,",
" \"type\": \"person\"", " \"type\": \"person\"",
" },",
" \"old\": {",
" \"archived\": false",
" }", " }",
"}'))" "}'))"
] ]
@ -3499,12 +3219,7 @@
"success": true, "success": true,
"sql": [ "sql": [
[ [
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*)", "(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"invoice\" t2 ON t2.id = t1.id WHERE t1.id = '{{uuid:data.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"invoice\" t2 ON t2.id = t1.id WHERE (\"id\" = '{{uuid:data.id}}'))"
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"invoice\" t2 ON ",
"WHERE",
" t1.id = '{{uuid:data.id}}'",
" OR (\"id\" = '{{uuid:data.id}}'))"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "INSERT INTO agreego.\"entity\" (",
@ -4057,6 +3772,143 @@
] ]
] ]
} }
},
{
"description": "Existence probes hit the same row via id and lookup key (dedup, updates)",
"action": "merge",
"data": {
"id": "22222222-2222-2222-2222-222222222222",
"type": "person",
"first_name": "LookupFirst",
"last_name": "LookupLast",
"date_of_birth": "1990-01-01T00:00:00Z",
"pronouns": "they/them",
"contact_id": "new-contact"
},
"mocks": [
{
"id": "22222222-2222-2222-2222-222222222222",
"type": "person",
"first_name": "LookupFirst",
"last_name": "LookupLast",
"date_of_birth": "1990-01-01T00:00:00Z",
"pronouns": "they/them",
"contact_id": "old-contact"
}
],
"schema_id": "person",
"expect": {
"success": true,
"sql": [
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE t1.id = '{{uuid:mocks.0.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE (\"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '{{timestamp}}' AND \"pronouns\" = 'they/them'))"
],
[
"UPDATE agreego.\"entity\" SET",
" modified_at = '{{timestamp}}',",
" modified_by = '00000000-0000-0000-0000-000000000000'",
"WHERE",
" id = '{{uuid:mocks.0.id}}'"
],
[
"UPDATE agreego.\"person\" SET",
" contact_id = 'new-contact'",
"WHERE",
" id = '{{uuid:mocks.0.id}}'"
],
[
"INSERT INTO agreego.change (",
" \"old\",",
" \"new\",",
" \"entity_id\",",
" \"id\",",
" \"kind\",",
" \"modified_at\",",
" \"modified_by\"",
")",
"VALUES (",
" '{",
" \"contact_id\": \"old-contact\"",
" }',",
" '{",
" \"contact_id\": \"new-contact\",",
" \"type\": \"person\"",
" }',",
" '{{uuid:mocks.0.id}}',",
" '{{uuid:generated_0}}',",
" 'update',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'",
")"
],
[
"(SELECT pg_notify('entity', '{",
" \"kind\": \"update\",",
" \"complete\": {",
" \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"new-contact\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"",
" },",
" \"new\": {",
" \"contact_id\": \"new-contact\",",
" \"type\": \"person\"",
" },",
" \"old\": {",
" \"contact_id\": \"old-contact\"",
" }",
"}'))"
]
]
}
},
{
"description": "Id probe and lookup probe find different rows (TOO_MANY_LOOKUP_ROWS)",
"action": "merge",
"data": {
"id": "22222222-2222-2222-2222-222222222222",
"type": "person",
"first_name": "OtherFirst",
"last_name": "OtherLast",
"date_of_birth": "1991-02-02T00:00:00Z",
"pronouns": "she/her"
},
"mocks": [
{
"id": "22222222-2222-2222-2222-222222222222",
"type": "person",
"first_name": "LookupFirst",
"last_name": "LookupLast",
"date_of_birth": "1990-01-01T00:00:00Z",
"pronouns": "they/them"
},
{
"id": "44444444-4444-4444-4444-444444444444",
"type": "person",
"first_name": "OtherFirst",
"last_name": "OtherLast",
"date_of_birth": "1991-02-02T00:00:00Z",
"pronouns": "she/her"
}
],
"schema_id": "person",
"expect": {
"success": false,
"errors": [
{
"code": "MERGE_FAILED",
"values": {
"error": "TOO_MANY_LOOKUP_ROWS: Lookup for person found too many existing rows"
},
"details": {}
}
]
}
} }
] ]
} }

View File

@ -116,99 +116,17 @@ fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
return None; return None;
} }
// 1. Extract table name // The merger's existence read emits one SELECT per probe (id / lookup key)
let table_regex = Regex::new(r#"(?i)\s+FROM\s+(?:[a-zA-Z_]\w*\.)?"?([a-zA-Z_]\w*)"?"#).ok()?; // joined by UNION. Match each arm independently against its own tables and
let table = if let Some(caps) = table_regex.captures(sql) { // WHERE clause; a mock matching any arm is returned once, mirroring UNION's
caps.get(1)?.as_str() // dedup semantics.
} else { let union_regex = Regex::new(r"(?i)\s+UNION\s+(?:ALL\s+)?").ok()?;
return None; let arms: Vec<&str> = union_regex.split(sql).collect();
};
// 2. Extract WHERE conditions string
let mut where_clause = String::new();
if let Some(where_idx) = sql_upper.find(" WHERE ") {
let mut where_end = sql_upper.find(" ORDER BY ").unwrap_or(sql_upper.len());
if let Some(limit_idx) = sql_upper.find(" LIMIT ") {
if limit_idx < where_end {
where_end = limit_idx;
}
}
where_clause = sql[where_idx + 7..where_end].to_string();
}
// 3. Find matching mocks
let mut matches = Vec::new(); let mut matches = Vec::new();
let or_regex = Regex::new(r"(?i)\s+OR\s+").ok()?;
let and_regex = Regex::new(r"(?i)\s+AND\s+").ok()?;
for mock in mocks { for mock in mocks {
if let Some(mock_obj) = mock.as_object() { if let Some(mock_obj) = mock.as_object() {
if let Some(t) = mock_obj.get("type") { if arms.iter().any(|arm| arm_matches_mock(arm, mock_obj)) {
if t.as_str() != Some(table) {
continue;
}
}
if where_clause.is_empty() {
matches.push(mock.clone());
continue;
}
let or_parts = or_regex.split(&where_clause);
let mut any_branch_matched = false;
for or_part in or_parts {
let branch_str = or_part.replace('(', "").replace(')', "");
let mut branch_matches = true;
for part in and_regex.split(&branch_str) {
if let Some(eq_idx) = part.find('=') {
let left = part[..eq_idx]
.trim()
.split('.')
.last()
.unwrap_or("")
.trim_matches('"');
let right = part[eq_idx + 1..].trim().trim_matches('\'');
let mock_val_str = match mock_obj.get(left) {
Some(Value::String(s)) => s.clone(),
Some(Value::Number(n)) => n.to_string(),
Some(Value::Bool(b)) => b.to_string(),
Some(Value::Null) => "null".to_string(),
_ => "".to_string(),
};
if mock_val_str != right {
branch_matches = false;
break;
}
} else if part.to_uppercase().contains(" IS NULL") {
let left = part[..part.to_uppercase().find(" IS NULL").unwrap()]
.trim()
.split('.')
.last()
.unwrap_or("")
.trim_matches('"');
let mock_val_str = match mock_obj.get(left) {
Some(Value::Null) => "null".to_string(),
_ => "".to_string(),
};
if mock_val_str != "null" {
branch_matches = false;
break;
}
}
}
if branch_matches {
any_branch_matched = true;
break;
}
}
if any_branch_matched {
matches.push(mock.clone()); matches.push(mock.clone());
} }
} }
@ -216,3 +134,103 @@ fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
Some(matches) Some(matches)
} }
#[cfg(test)]
fn arm_matches_mock(arm: &str, mock_obj: &serde_json::Map<String, Value>) -> bool {
let arm_upper = arm.to_uppercase();
// 1. The mock's type must name one of the arm's tables. Hierarchy reads
// SELECT FROM the root (entity) and JOIN the subtype tables, so every
// FROM/JOIN table counts, not just the first.
let table_regex = match Regex::new(r#"(?i)\b(?:FROM|JOIN)\s+(?:[a-zA-Z_]\w*\.)?"?([a-zA-Z_]\w*)"?"#) {
Ok(r) => r,
Err(_) => return false,
};
let tables: Vec<&str> = table_regex.captures_iter(arm).filter_map(|c| c.get(1)).map(|m| m.as_str()).collect();
if tables.is_empty() {
return false;
}
if let Some(t) = mock_obj.get("type") {
if !tables.iter().any(|table| t.as_str() == Some(table)) {
return false;
}
}
// 2. Extract this arm's WHERE conditions
let mut where_clause = String::new();
if let Some(where_idx) = arm_upper.find(" WHERE ") {
let mut where_end = arm_upper.find(" ORDER BY ").unwrap_or(arm_upper.len());
if let Some(limit_idx) = arm_upper.find(" LIMIT ") {
if limit_idx < where_end {
where_end = limit_idx;
}
}
where_clause = arm[where_idx + 7..where_end].to_string();
}
if where_clause.is_empty() {
return true;
}
// 3. Match the mock against the conditions
let or_regex = match Regex::new(r"(?i)\s+OR\s+") {
Ok(r) => r,
Err(_) => return false,
};
let and_regex = match Regex::new(r"(?i)\s+AND\s+") {
Ok(r) => r,
Err(_) => return false,
};
for or_part in or_regex.split(&where_clause) {
let branch_str = or_part.replace('(', "").replace(')', "");
let mut branch_matches = true;
for part in and_regex.split(&branch_str) {
if let Some(eq_idx) = part.find('=') {
let left = part[..eq_idx]
.trim()
.split('.')
.last()
.unwrap_or("")
.trim_matches('"');
let right = part[eq_idx + 1..].trim().trim_matches('\'');
let mock_val_str = match mock_obj.get(left) {
Some(Value::String(s)) => s.clone(),
Some(Value::Number(n)) => n.to_string(),
Some(Value::Bool(b)) => b.to_string(),
Some(Value::Null) => "null".to_string(),
_ => "".to_string(),
};
if mock_val_str != right {
branch_matches = false;
break;
}
} else if part.to_uppercase().contains(" IS NULL") {
let left = part[..part.to_uppercase().find(" IS NULL").unwrap()]
.trim()
.split('.')
.last()
.unwrap_or("")
.trim_matches('"');
let mock_val_str = match mock_obj.get(left) {
Some(Value::Null) => "null".to_string(),
_ => "".to_string(),
};
if mock_val_str != "null" {
branch_matches = false;
break;
}
}
}
if branch_matches {
return true;
}
}
false
}

View File

@ -744,9 +744,19 @@ impl Merger {
return Ok(None); return Ok(None);
} }
let where_clause = format!("WHERE {}", where_parts.join(" OR ")); // An OR across the hierarchy join is un-indexable — it forces a full scan
// of the subtype table on every child merge. UNION lets each arm use its
let final_sql = format!("{} {}", fetch_sql_template, where_clause); // own index (pk for the id arm, the lk_ unique index for the lookup arm)
// and still dedups, so TOO_MANY_LOOKUP_ROWS semantics are preserved.
let final_sql = if where_parts.len() == 1 {
format!("{} WHERE {}", fetch_sql_template, where_parts[0])
} else {
where_parts
.iter()
.map(|p| format!("{} WHERE {}", fetch_sql_template, p))
.collect::<Vec<_>>()
.join(" UNION ")
};
let fetched = match self.db.query(&final_sql, None) { let fetched = match self.db.query(&final_sql, None) {
Ok(Value::Array(table)) => { Ok(Value::Array(table)) => {

View File

@ -7660,3 +7660,15 @@ fn test_merger_0_15() {
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 15).unwrap(); crate::tests::runner::run_test_case(&path, 0, 15).unwrap();
} }
#[test]
fn test_merger_0_16() {
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 16).unwrap();
}
#[test]
fn test_merger_0_17() {
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 17).unwrap();
}

View File

@ -1 +1 @@
1.0.169 1.0.170