diff --git a/GEMINI.md b/GEMINI.md index d15d4c7..1c9127e 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -84,11 +84,26 @@ Punc completely abandons the standard JSON Schema `$ref` keyword. Instead, it ov * **Strict Array Constraint**: To explicitly prevent mathematically ambiguous Multiple Inheritance, a `type` array is strictly constrained to at most **ONE** Custom Object Pointer. Defining `"type": ["person", "organization"]` will intentionally trigger a fatal database compilation error natively instructing developers to build a proper tagged union (`oneOf`) instead. ### Polymorphism (`$family` and `oneOf`) -Polymorphism is how an object boundary can dynamically take on entirely different shapes based on the payload provided at runtime. -* **`$family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the database compiler to dynamically search its internal `db.descendants` registry and find all physical schemas that mathematically resolve to the target. - * *Across Tables (Vertical)*: If `$family: entity` is requested, the payload's `type` field acts as the discriminator, dynamically routing to standard variations like `organization` or `person` spanning multiple Postgres tables. - * *Single Table (Horizontal)*: If `$family: widget` is requested, the router explicitly evaluates the Dot Convention dynamically. If the payload possesses `"type": "widget"` and `"kind": "stock"`, the router mathematically resolves to the string `"stock.widget"` and routes exclusively to that explicit `JSPG` schema. -* **`oneOf` (Strict Tagged Unions)**: A hardcoded array of JSON Schema candidate options. Punc strictly bans mathematical "Union of Sets" evaluation. Every `oneOf` candidate item MUST either be a pure primitive (`{ "type": "null" }`) or a user-defined Object Pointer providing a specific discriminator (e.g., `{ "type": "invoice_metadata" }`). This ensures validations remain pure $O(1)$ fast-paths and allows the Dart generator to emit pristine `sealed classes`. +Polymorphism is how an object boundary can dynamically take on entirely different shapes based on the payload provided at runtime. Punc utilizes the static database metadata generated from Postgres (`db.types`) to enforce these boundaries deterministically, rather than relying on ambiguous tree-traversals. + +* **`$family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the engine to resolve dynamic options against the registered database `types` variations or its inner schema registry. It uses the exact physical constraints of the database to build SQL and validation routes. + * **Scenario A: Global Tables (Vertical Routing)** + * *Setup*: `{ "$family": "organization" }` + * *Execution*: The engine queries `db.types.get("organization").variations` and finds `["bot", "organization", "person"]`. Because organizations are structurally table-backed, the `$family` automatically uses `type` as the discriminator. + * *Options*: `bot` -> `bot`, `person` -> `person`, `organization` -> `organization`. + * **Scenario B: Prefixed Tables (Vertical Projection)** + * *Setup*: `{ "$family": "light.organization" }` + * *Execution*: The engine sees the prefix `light.` and base `organization`. It queries `db.types.get("organization").variations` and dynamically prepends the prefix to discover the relevant UI schemas. + * *Options*: `person` -> `light.person`, `organization` -> `light.organization`. (If a projection like `light.bot` does not exist in `db.schemas`, it is safely ignored). + * **Scenario C: Single Table Inheritance (Horizontal Routing)** + * *Setup*: `{ "$family": "widget" }` (Where `widget` is a table type but has no external variations). + * *Execution*: The engine queries `db.types.get("widget").variations` and finds only `["widget"]`. Since it lacks table inheritance, it is treated as STI. The engine scans the specific, confined `schemas` array directly under `db.types.get("widget")` for any `$id` terminating in the base `.widget` (e.g., `stock.widget`). The `$family` automatically uses `kind` as the discriminator. + * *Options*: `stock` -> `stock.widget`, `tasks` -> `tasks.widget`. + +* **`oneOf` (Strict Tagged Unions)**: A hardcoded list of candidate schemas. Unlike `$family` which relies on global DB metadata, `oneOf` forces pure mathematical structural evaluation of the provided candidates. It strictly bans typical JSON Schema "Union of Sets" fallback searches. Every candidate MUST possess a mathematically unique discriminator payload to allow $O(1)$ routing. + * **Disjoint Types**: `oneOf: [{ "type": "person" }, { "type": "widget" }]`. The engine succeeds because the native `type` acts as a unique discriminator (`"person"` vs `"widget"`). + * **STI Types**: `oneOf: [{ "type": "heavy.person" }, { "type": "light.person" }]`. The engine succeeds. Even though both share `"type": "person"`, their explicit discriminator is `kind` (`"heavy"` vs `"light"`), ensuring unique $O(1)$ fast-paths. + * **Conflicting Types**: `oneOf: [{ "type": "person" }, { "type": "light.person" }]`. The engine **fails compilation natively**. Both schemas evaluate to `"type": "person"` and neither provides a disjoint `kind` constraint, making them mathematically ambiguous and impossible to route in $O(1)$ time. ### Conditionals (`cases`) Standard JSON Schema forces developers to write deeply nested `allOf` -> `if` -> `properties` blocks just to execute conditional branching. **JSPG completely abandons `allOf` and this practice.** For declarative business logic and structural mutations conditionally based upon property bounds, use the top-level `cases` array. diff --git a/debug.log b/debug.log new file mode 100644 index 0000000..fa71ce8 --- /dev/null +++ b/debug.log @@ -0,0 +1,43 @@ + Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg) +warning: unused variable: `is_family` + --> src/database/schema.rs:495:9 + | +495 | let is_family = self.obj.family.is_some(); + | ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_is_family` + | + = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default + +warning: `jspg` (lib) generated 1 warning (run `cargo fix --lib -p jspg` to apply 1 suggestion) +warning: `jspg` (lib test) generated 1 warning (1 duplicate) + Finished `test` profile [unoptimized + debuginfo] target(s) in 11.66s + Running unittests src/lib.rs (target/debug/deps/jspg-d3f18ff3a7e2b386) + +running 1 test +[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: kind, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: kind, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG POLYMORPHISM] ID: get_person.response, Strategy: type, Pathways: {"full.person": "full.person", "light.person": "light.person", "person": "person"} +[DEBUG QUERYER] Evaluating node. Target family: Some("organization"), disc: Some("type"), pathways: Some({"bot": "bot", "full.person": "full.person", "get_organization.response": "get_organization.response", "light.person": "light.person", "organization": "organization", "person": "person"}) +JSPG_SQL: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id', organization_1.id, 'type', CASE WHEN organization_1.type = 'bot' THEN ((SELECT jsonb_build_object('archived', entity_5.archived, 'created_at', entity_5.created_at, 'id', entity_5.id, 'name', organization_4.name, 'token', bot_3.token, 'type', entity_5.type) FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id = bot_3.id JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id WHERE NOT entity_5.archived)) WHEN organization_1.type = 'full.person' THEN ((SELECT jsonb_build_object('addresses', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_11.archived, 'created_at', entity_11.created_at, 'id', entity_11.id, 'is_primary', contact_9.is_primary, 'target', (SELECT jsonb_build_object('archived', entity_13.archived, 'city', address_12.city, 'created_at', entity_13.created_at, 'id', entity_13.id, 'type', entity_13.type) FROM agreego.address address_12 JOIN agreego.entity entity_13 ON entity_13.id = address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id = entity_13.id), 'type', entity_11.type)), '[]'::jsonb) FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id = contact_9.id JOIN agreego.entity entity_11 ON entity_11.id = relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type = 'address' AND relationship_10.source_id = entity_8.id), 'age', person_6.age, 'archived', entity_8.archived, 'contacts', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_16.archived, 'created_at', entity_16.created_at, 'id', entity_16.id, 'is_primary', contact_14.is_primary, 'target', CASE WHEN entity_16.target_type = 'address' THEN ((SELECT jsonb_build_object('archived', entity_18.archived, 'city', address_17.city, 'created_at', entity_18.created_at, 'id', entity_18.id, 'type', entity_18.type) FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id = address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id = entity_18.id)) WHEN entity_16.target_type = 'email_address' THEN ((SELECT jsonb_build_object('address', email_address_19.address, 'archived', entity_20.archived, 'created_at', entity_20.created_at, 'id', entity_20.id, 'type', entity_20.type) FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id = email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id = entity_20.id)) WHEN entity_16.target_type = 'phone_number' THEN ((SELECT jsonb_build_object('archived', entity_22.archived, 'created_at', entity_22.created_at, 'id', entity_22.id, 'number', phone_number_21.number, 'type', entity_22.type) FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id = phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id = entity_22.id)) ELSE NULL END, 'type', entity_16.type)), '[]'::jsonb) FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id = contact_14.id JOIN agreego.entity entity_16 ON entity_16.id = relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id = entity_8.id), 'created_at', entity_8.created_at, 'email_addresses', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_25.archived, 'created_at', entity_25.created_at, 'id', entity_25.id, 'is_primary', contact_23.is_primary, 'target', (SELECT jsonb_build_object('address', email_address_26.address, 'archived', entity_27.archived, 'created_at', entity_27.created_at, 'id', entity_27.id, 'type', entity_27.type) FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id = email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id = entity_27.id), 'type', entity_25.type)), '[]'::jsonb) FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id = contact_23.id JOIN agreego.entity entity_25 ON entity_25.id = relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type = 'email_address' AND relationship_24.source_id = entity_8.id), 'first_name', person_6.first_name, 'id', entity_8.id, 'last_name', person_6.last_name, 'name', organization_7.name, 'phone_numbers', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_30.archived, 'created_at', entity_30.created_at, 'id', entity_30.id, 'is_primary', contact_28.is_primary, 'target', (SELECT jsonb_build_object('archived', entity_32.archived, 'created_at', entity_32.created_at, 'id', entity_32.id, 'number', phone_number_31.number, 'type', entity_32.type) FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id = phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id = entity_32.id), 'type', entity_30.type)), '[]'::jsonb) FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id = contact_28.id JOIN agreego.entity entity_30 ON entity_30.id = relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type = 'phone_number' AND relationship_29.source_id = entity_8.id), 'type', entity_8.type) FROM agreego.person person_6 JOIN agreego.organization organization_7 ON organization_7.id = person_6.id JOIN agreego.entity entity_8 ON entity_8.id = organization_7.id WHERE NOT entity_8.archived)) WHEN organization_1.type = 'get_organization.response' THEN ((SELECT jsonb_build_object('archived', entity_34.archived, 'created_at', entity_34.created_at, 'id', entity_34.id, 'name', organization_33.name, 'type', entity_34.type) FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id = organization_33.id WHERE NOT entity_34.archived)) WHEN organization_1.type = 'light.person' THEN ((SELECT jsonb_build_object('age', person_35.age, 'archived', entity_37.archived, 'created_at', entity_37.created_at, 'first_name', person_35.first_name, 'id', entity_37.id, 'last_name', person_35.last_name, 'name', organization_36.name, 'type', entity_37.type) FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id = person_35.id JOIN agreego.entity entity_37 ON entity_37.id = organization_36.id WHERE NOT entity_37.archived)) WHEN organization_1.type = 'organization' THEN ((SELECT jsonb_build_object('archived', entity_39.archived, 'created_at', entity_39.created_at, 'id', entity_39.id, 'name', organization_38.name, 'type', entity_39.type) FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id = organization_38.id WHERE NOT entity_39.archived)) WHEN organization_1.type = 'person' THEN ((SELECT jsonb_build_object('age', person_40.age, 'archived', entity_42.archived, 'created_at', entity_42.created_at, 'first_name', person_40.first_name, 'id', entity_42.id, 'last_name', person_40.last_name, 'name', organization_41.name, 'type', entity_42.type) FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id = person_40.id JOIN agreego.entity entity_42 ON entity_42.id = organization_41.id WHERE NOT entity_42.archived)) ELSE NULL END)), '[]'::jsonb) FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id WHERE NOT entity_2.archived))) +TEST QUERY ERROR FOR 'Organizations select via a punc response with family': Line mismatched at execution sequence 1. +Expected Pattern: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id',organization_1.id,'type',CASE WHEN organization_1.type='bot'THEN((SELECT jsonb_build_object('archived',entity_5.archived,'created_at',entity_5.created_at,'id',entity_5.id,'name',organization_4.name,'token',bot_3.token,'type',entity_5.type)FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id=bot_3.id JOIN agreego.entity entity_5 ON entity_5.id=organization_4.id WHERE NOT entity_5.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_7.archived,'created_at',entity_7.created_at,'id',entity_7.id,'name',organization_6.name,'type',entity_7.type)FROM agreego.organization organization_6 JOIN agreego.entity entity_7 ON entity_7.id=organization_6.id WHERE NOT entity_7.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_8.age,'archived',entity_10.archived,'created_at',entity_10.created_at,'first_name',person_8.first_name,'id',entity_10.id,'last_name',person_8.last_name,'name',organization_9.name,'type',entity_10.type)FROM agreego.person person_8 JOIN agreego.organization organization_9 ON organization_9.id=person_8.id JOIN agreego.entity entity_10 ON entity_10.id=organization_9.id WHERE NOT entity_10.archived))ELSE NULL END)),'[]'::jsonb)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived))) +Actual SQL: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id',organization_1.id,'type',CASE WHEN organization_1.type='bot'THEN((SELECT jsonb_build_object('archived',entity_5.archived,'created_at',entity_5.created_at,'id',entity_5.id,'name',organization_4.name,'token',bot_3.token,'type',entity_5.type)FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id=bot_3.id JOIN agreego.entity entity_5 ON entity_5.id=organization_4.id WHERE NOT entity_5.archived))WHEN organization_1.type='full.person'THEN((SELECT jsonb_build_object('addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_11.archived,'created_at',entity_11.created_at,'id',entity_11.id,'is_primary',contact_9.is_primary,'target',(SELECT jsonb_build_object('archived',entity_13.archived,'city',address_12.city,'created_at',entity_13.created_at,'id',entity_13.id,'type',entity_13.type)FROM agreego.address address_12 JOIN agreego.entity entity_13 ON entity_13.id=address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id=entity_13.id),'type',entity_11.type)),'[]'::jsonb)FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id=contact_9.id JOIN agreego.entity entity_11 ON entity_11.id=relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type='address'AND relationship_10.source_id=entity_8.id),'age',person_6.age,'archived',entity_8.archived,'contacts',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_16.archived,'created_at',entity_16.created_at,'id',entity_16.id,'is_primary',contact_14.is_primary,'target',CASE WHEN entity_16.target_type='address'THEN((SELECT jsonb_build_object('archived',entity_18.archived,'city',address_17.city,'created_at',entity_18.created_at,'id',entity_18.id,'type',entity_18.type)FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id=address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id=entity_18.id))WHEN entity_16.target_type='email_address'THEN((SELECT jsonb_build_object('address',email_address_19.address,'archived',entity_20.archived,'created_at',entity_20.created_at,'id',entity_20.id,'type',entity_20.type)FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id=email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id=entity_20.id))WHEN entity_16.target_type='phone_number'THEN((SELECT jsonb_build_object('archived',entity_22.archived,'created_at',entity_22.created_at,'id',entity_22.id,'number',phone_number_21.number,'type',entity_22.type)FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id=phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id=entity_22.id))ELSE NULL END,'type',entity_16.type)),'[]'::jsonb)FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id=contact_14.id JOIN agreego.entity entity_16 ON entity_16.id=relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id=entity_8.id),'created_at',entity_8.created_at,'email_addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_25.archived,'created_at',entity_25.created_at,'id',entity_25.id,'is_primary',contact_23.is_primary,'target',(SELECT jsonb_build_object('address',email_address_26.address,'archived',entity_27.archived,'created_at',entity_27.created_at,'id',entity_27.id,'type',entity_27.type)FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id=email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id=entity_27.id),'type',entity_25.type)),'[]'::jsonb)FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id=contact_23.id JOIN agreego.entity entity_25 ON entity_25.id=relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type='email_address'AND relationship_24.source_id=entity_8.id),'first_name',person_6.first_name,'id',entity_8.id,'last_name',person_6.last_name,'name',organization_7.name,'phone_numbers',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_30.archived,'created_at',entity_30.created_at,'id',entity_30.id,'is_primary',contact_28.is_primary,'target',(SELECT jsonb_build_object('archived',entity_32.archived,'created_at',entity_32.created_at,'id',entity_32.id,'number',phone_number_31.number,'type',entity_32.type)FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id=phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id=entity_32.id),'type',entity_30.type)),'[]'::jsonb)FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id=contact_28.id JOIN agreego.entity entity_30 ON entity_30.id=relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type='phone_number'AND relationship_29.source_id=entity_8.id),'type',entity_8.type)FROM agreego.person person_6 JOIN agreego.organization organization_7 ON organization_7.id=person_6.id JOIN agreego.entity entity_8 ON entity_8.id=organization_7.id WHERE NOT entity_8.archived))WHEN organization_1.type='get_organization.response'THEN((SELECT jsonb_build_object('archived',entity_34.archived,'created_at',entity_34.created_at,'id',entity_34.id,'name',organization_33.name,'type',entity_34.type)FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id=organization_33.id WHERE NOT entity_34.archived))WHEN organization_1.type='light.person'THEN((SELECT jsonb_build_object('age',person_35.age,'archived',entity_37.archived,'created_at',entity_37.created_at,'first_name',person_35.first_name,'id',entity_37.id,'last_name',person_35.last_name,'name',organization_36.name,'type',entity_37.type)FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id=person_35.id JOIN agreego.entity entity_37 ON entity_37.id=organization_36.id WHERE NOT entity_37.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_39.archived,'created_at',entity_39.created_at,'id',entity_39.id,'name',organization_38.name,'type',entity_39.type)FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id=organization_38.id WHERE NOT entity_39.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_40.age,'archived',entity_42.archived,'created_at',entity_42.created_at,'first_name',person_40.first_name,'id',entity_42.id,'last_name',person_40.last_name,'name',organization_41.name,'type',entity_42.type)FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id=person_40.id JOIN agreego.entity entity_42 ON entity_42.id=organization_41.id WHERE NOT entity_42.archived))ELSE NULL END)),'[]'::jsonb)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived))) +Regex used: \(SELECT jsonb_strip_nulls\(\(SELECT COALESCE\(jsonb_agg\(jsonb_build_object\('id',organization_1\.id,'type',CASE WHEN organization_1\.type='bot'THEN\(\(SELECT jsonb_build_object\('archived',entity_5\.archived,'created_at',entity_5\.created_at,'id',entity_5\.id,'name',organization_4\.name,'token',bot_3\.token,'type',entity_5\.type\)FROM agreego\.bot bot_3 JOIN agreego\.organization organization_4 ON organization_4\.id=bot_3\.id JOIN agreego\.entity entity_5 ON entity_5\.id=organization_4\.id WHERE NOT entity_5\.archived\)\)WHEN organization_1\.type='organization'THEN\(\(SELECT jsonb_build_object\('archived',entity_7\.archived,'created_at',entity_7\.created_at,'id',entity_7\.id,'name',organization_6\.name,'type',entity_7\.type\)FROM agreego\.organization organization_6 JOIN agreego\.entity entity_7 ON entity_7\.id=organization_6\.id WHERE NOT entity_7\.archived\)\)WHEN organization_1\.type='person'THEN\(\(SELECT jsonb_build_object\('age',person_8\.age,'archived',entity_10\.archived,'created_at',entity_10\.created_at,'first_name',person_8\.first_name,'id',entity_10\.id,'last_name',person_8\.last_name,'name',organization_9\.name,'type',entity_10\.type\)FROM agreego\.person person_8 JOIN agreego\.organization organization_9 ON organization_9\.id=person_8\.id JOIN agreego\.entity entity_10 ON entity_10\.id=organization_9\.id WHERE NOT entity_10\.archived\)\)ELSE NULL END\)\),'\[\]'::jsonb\)FROM agreego\.organization organization_1 JOIN agreego\.entity entity_2 ON entity_2\.id=organization_1\.id WHERE NOT entity_2\.archived\)\)\) +Variables Mapped: {} + +thread 'tests::test_queryer_0_8' (97338607) panicked at src/tests/fixtures.rs:1427:54: +called `Result::unwrap()` on an `Err` value: "[Queryer Execution] Query Test 'Organizations select via a punc response with family' failed. Error: Line mismatched at execution sequence 1.\nExpected Pattern: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id',organization_1.id,'type',CASE WHEN organization_1.type='bot'THEN((SELECT jsonb_build_object('archived',entity_5.archived,'created_at',entity_5.created_at,'id',entity_5.id,'name',organization_4.name,'token',bot_3.token,'type',entity_5.type)FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id=bot_3.id JOIN agreego.entity entity_5 ON entity_5.id=organization_4.id WHERE NOT entity_5.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_7.archived,'created_at',entity_7.created_at,'id',entity_7.id,'name',organization_6.name,'type',entity_7.type)FROM agreego.organization organization_6 JOIN agreego.entity entity_7 ON entity_7.id=organization_6.id WHERE NOT entity_7.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_8.age,'archived',entity_10.archived,'created_at',entity_10.created_at,'first_name',person_8.first_name,'id',entity_10.id,'last_name',person_8.last_name,'name',organization_9.name,'type',entity_10.type)FROM agreego.person person_8 JOIN agreego.organization organization_9 ON organization_9.id=person_8.id JOIN agreego.entity entity_10 ON entity_10.id=organization_9.id WHERE NOT entity_10.archived))ELSE NULL END)),'[]'::jsonb)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived)))\nActual SQL: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id',organization_1.id,'type',CASE WHEN organization_1.type='bot'THEN((SELECT jsonb_build_object('archived',entity_5.archived,'created_at',entity_5.created_at,'id',entity_5.id,'name',organization_4.name,'token',bot_3.token,'type',entity_5.type)FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id=bot_3.id JOIN agreego.entity entity_5 ON entity_5.id=organization_4.id WHERE NOT entity_5.archived))WHEN organization_1.type='full.person'THEN((SELECT jsonb_build_object('addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_11.archived,'created_at',entity_11.created_at,'id',entity_11.id,'is_primary',contact_9.is_primary,'target',(SELECT jsonb_build_object('archived',entity_13.archived,'city',address_12.city,'created_at',entity_13.created_at,'id',entity_13.id,'type',entity_13.type)FROM agreego.address address_12 JOIN agreego.entity entity_13 ON entity_13.id=address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id=entity_13.id),'type',entity_11.type)),'[]'::jsonb)FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id=contact_9.id JOIN agreego.entity entity_11 ON entity_11.id=relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type='address'AND relationship_10.source_id=entity_8.id),'age',person_6.age,'archived',entity_8.archived,'contacts',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_16.archived,'created_at',entity_16.created_at,'id',entity_16.id,'is_primary',contact_14.is_primary,'target',CASE WHEN entity_16.target_type='address'THEN((SELECT jsonb_build_object('archived',entity_18.archived,'city',address_17.city,'created_at',entity_18.created_at,'id',entity_18.id,'type',entity_18.type)FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id=address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id=entity_18.id))WHEN entity_16.target_type='email_address'THEN((SELECT jsonb_build_object('address',email_address_19.address,'archived',entity_20.archived,'created_at',entity_20.created_at,'id',entity_20.id,'type',entity_20.type)FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id=email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id=entity_20.id))WHEN entity_16.target_type='phone_number'THEN((SELECT jsonb_build_object('archived',entity_22.archived,'created_at',entity_22.created_at,'id',entity_22.id,'number',phone_number_21.number,'type',entity_22.type)FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id=phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id=entity_22.id))ELSE NULL END,'type',entity_16.type)),'[]'::jsonb)FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id=contact_14.id JOIN agreego.entity entity_16 ON entity_16.id=relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id=entity_8.id),'created_at',entity_8.created_at,'email_addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_25.archived,'created_at',entity_25.created_at,'id',entity_25.id,'is_primary',contact_23.is_primary,'target',(SELECT jsonb_build_object('address',email_address_26.address,'archived',entity_27.archived,'created_at',entity_27.created_at,'id',entity_27.id,'type',entity_27.type)FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id=email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id=entity_27.id),'type',entity_25.type)),'[]'::jsonb)FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id=contact_23.id JOIN agreego.entity entity_25 ON entity_25.id=relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type='email_address'AND relationship_24.source_id=entity_8.id),'first_name',person_6.first_name,'id',entity_8.id,'last_name',person_6.last_name,'name',organization_7.name,'phone_numbers',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_30.archived,'created_at',entity_30.created_at,'id',entity_30.id,'is_primary',contact_28.is_primary,'target',(SELECT jsonb_build_object('archived',entity_32.archived,'created_at',entity_32.created_at,'id',entity_32.id,'number',phone_number_31.number,'type',entity_32.type)FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id=phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id=entity_32.id),'type',entity_30.type)),'[]'::jsonb)FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id=contact_28.id JOIN agreego.entity entity_30 ON entity_30.id=relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type='phone_number'AND relationship_29.source_id=entity_8.id),'type',entity_8.type)FROM agreego.person person_6 JOIN agreego.organization organization_7 ON organization_7.id=person_6.id JOIN agreego.entity entity_8 ON entity_8.id=organization_7.id WHERE NOT entity_8.archived))WHEN organization_1.type='get_organization.response'THEN((SELECT jsonb_build_object('archived',entity_34.archived,'created_at',entity_34.created_at,'id',entity_34.id,'name',organization_33.name,'type',entity_34.type)FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id=organization_33.id WHERE NOT entity_34.archived))WHEN organization_1.type='light.person'THEN((SELECT jsonb_build_object('age',person_35.age,'archived',entity_37.archived,'created_at',entity_37.created_at,'first_name',person_35.first_name,'id',entity_37.id,'last_name',person_35.last_name,'name',organization_36.name,'type',entity_37.type)FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id=person_35.id JOIN agreego.entity entity_37 ON entity_37.id=organization_36.id WHERE NOT entity_37.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_39.archived,'created_at',entity_39.created_at,'id',entity_39.id,'name',organization_38.name,'type',entity_39.type)FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id=organization_38.id WHERE NOT entity_39.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_40.age,'archived',entity_42.archived,'created_at',entity_42.created_at,'first_name',person_40.first_name,'id',entity_42.id,'last_name',person_40.last_name,'name',organization_41.name,'type',entity_42.type)FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id=person_40.id JOIN agreego.entity entity_42 ON entity_42.id=organization_41.id WHERE NOT entity_42.archived))ELSE NULL END)),'[]'::jsonb)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived)))\nRegex used: \\(SELECT jsonb_strip_nulls\\(\\(SELECT COALESCE\\(jsonb_agg\\(jsonb_build_object\\('id',organization_1\\.id,'type',CASE WHEN organization_1\\.type='bot'THEN\\(\\(SELECT jsonb_build_object\\('archived',entity_5\\.archived,'created_at',entity_5\\.created_at,'id',entity_5\\.id,'name',organization_4\\.name,'token',bot_3\\.token,'type',entity_5\\.type\\)FROM agreego\\.bot bot_3 JOIN agreego\\.organization organization_4 ON organization_4\\.id=bot_3\\.id JOIN agreego\\.entity entity_5 ON entity_5\\.id=organization_4\\.id WHERE NOT entity_5\\.archived\\)\\)WHEN organization_1\\.type='organization'THEN\\(\\(SELECT jsonb_build_object\\('archived',entity_7\\.archived,'created_at',entity_7\\.created_at,'id',entity_7\\.id,'name',organization_6\\.name,'type',entity_7\\.type\\)FROM agreego\\.organization organization_6 JOIN agreego\\.entity entity_7 ON entity_7\\.id=organization_6\\.id WHERE NOT entity_7\\.archived\\)\\)WHEN organization_1\\.type='person'THEN\\(\\(SELECT jsonb_build_object\\('age',person_8\\.age,'archived',entity_10\\.archived,'created_at',entity_10\\.created_at,'first_name',person_8\\.first_name,'id',entity_10\\.id,'last_name',person_8\\.last_name,'name',organization_9\\.name,'type',entity_10\\.type\\)FROM agreego\\.person person_8 JOIN agreego\\.organization organization_9 ON organization_9\\.id=person_8\\.id JOIN agreego\\.entity entity_10 ON entity_10\\.id=organization_9\\.id WHERE NOT entity_10\\.archived\\)\\)ELSE NULL END\\)\\),'\\[\\]'::jsonb\\)FROM agreego\\.organization organization_1 JOIN agreego\\.entity entity_2 ON entity_2\\.id=organization_1\\.id WHERE NOT entity_2\\.archived\\)\\)\\)\nVariables Mapped: {}" +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +test tests::test_queryer_0_8 ... FAILED + +failures: + +failures: + tests::test_queryer_0_8 + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1350 filtered out; finished in 0.01s + +error: test failed, to rerun pass `--lib` diff --git a/fixtures/polymorphism.json b/fixtures/polymorphism.json index 4bc9c84..a92db16 100644 --- a/fixtures/polymorphism.json +++ b/fixtures/polymorphism.json @@ -196,29 +196,37 @@ { "description": "Horizontal $family Routing (Virtual Variations)", "database": { + "types": [ + { + "name": "widget", + "variations": ["widget"], + "schemas": [ + { + "$id": "widget", + "type": "object", + "properties": { + "type": { "type": "string" } + } + }, + { + "$id": "stock.widget", + "type": "widget", + "properties": { + "kind": { "type": "string" }, + "amount": { "type": "integer" } + } + }, + { + "$id": "super_stock.widget", + "type": "stock.widget", + "properties": { + "super_amount": { "type": "integer" } + } + } + ] + } + ], "schemas": [ - { - "$id": "widget", - "type": "object", - "properties": { - "type": { "type": "string" } - } - }, - { - "$id": "stock.widget", - "type": "widget", - "properties": { - "kind": { "type": "string" }, - "amount": { "type": "integer" } - } - }, - { - "$id": "super_stock.widget", - "type": "stock.widget", - "properties": { - "super_amount": { "type": "integer" } - } - }, { "$id": "family_widget", "$family": "widget" diff --git a/fixtures/queryer.json b/fixtures/queryer.json index aefc4d5..3956265 100644 --- a/fixtures/queryer.json +++ b/fixtures/queryer.json @@ -25,11 +25,20 @@ ] }, { - "name": "get_person", + "name": "get_light_organizations", "schemas": [ { - "$id": "get_person.response", - "$family": "person" + "$id": "get_light_organizations.response", + "$family": "light.organization" + } + ] + }, + { + "name": "get_full_organizations", + "schemas": [ + { + "$id": "get_full_organizations.response", + "$family": "full.organization" } ] }, @@ -44,6 +53,18 @@ } } ] + }, + { + "name": "get_widgets", + "schemas": [ + { + "$id": "get_widgets.response", + "type": "array", + "items": { + "$family": "widget" + } + } + ] } ], "enums": [], @@ -260,7 +281,9 @@ "type", "name", "archived", - "created_at" + "created_at", + "token", + "role" ], "grouped_fields": { "entity": [ @@ -273,7 +296,8 @@ "name" ], "bot": [ - "token" + "token", + "role" ] }, "field_types": { @@ -282,12 +306,25 @@ "archived": "boolean", "name": "text", "token": "text", + "role": "text", "created_at": "timestamptz" }, "schemas": [ { "$id": "bot", "type": "organization", + "properties": { + "token": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + { + "$id": "light.bot", + "type": "organization", "properties": { "token": { "type": "string" @@ -360,8 +397,15 @@ }, { "$id": "light.person", - "type": "person", - "properties": {} + "type": "organization", + "properties": { + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + } + } }, { "$id": "full.person", @@ -850,6 +894,46 @@ "variations": [ "order_line" ] + }, + { + "name": "widget", + "hierarchy": ["widget", "entity"], + "fields": ["id", "type", "kind", "archived", "created_at"], + "grouped_fields": { + "entity": ["id", "type", "archived", "created_at"], + "widget": ["kind"] + }, + "field_types": { + "id": "uuid", + "type": "text", + "kind": "text", + "archived": "boolean", + "created_at": "timestamptz" + }, + "variations": ["widget"], + "schemas": [ + { + "$id": "widget", + "type": "entity", + "properties": { + "kind": { "type": "string" } + } + }, + { + "$id": "stock.widget", + "type": "widget", + "properties": { + "kind": { "const": "stock" } + } + }, + { + "$id": "tasks.widget", + "type": "widget", + "properties": { + "kind": { "const": "tasks" } + } + } + ] } ] }, @@ -1004,17 +1088,17 @@ " 'target', CASE", " WHEN entity_11.target_type = 'address' THEN", " ((SELECT jsonb_build_object(", - " 'archived', entity_17.archived,", - " 'city', address_16.city,", - " 'created_at', entity_17.created_at,", - " 'id', entity_17.id,", - " 'type', entity_17.type", + " 'archived', entity_13.archived,", + " 'city', address_12.city,", + " 'created_at', entity_13.created_at,", + " 'id', entity_13.id,", + " 'type', entity_13.type", " )", - " FROM agreego.address address_16", - " JOIN agreego.entity entity_17 ON entity_17.id = address_16.id", + " FROM agreego.address address_12", + " JOIN agreego.entity entity_13 ON entity_13.id = address_12.id", " WHERE", - " NOT entity_17.archived", - " AND relationship_10.target_id = entity_17.id))", + " NOT entity_13.archived", + " AND relationship_10.target_id = entity_13.id))", " WHEN entity_11.target_type = 'email_address' THEN", " ((SELECT jsonb_build_object(", " 'address', email_address_14.address,", @@ -1030,17 +1114,17 @@ " AND relationship_10.target_id = entity_15.id))", " WHEN entity_11.target_type = 'phone_number' THEN", " ((SELECT jsonb_build_object(", - " 'archived', entity_13.archived,", - " 'created_at', entity_13.created_at,", - " 'id', entity_13.id,", - " 'number', phone_number_12.number,", - " 'type', entity_13.type", + " 'archived', entity_17.archived,", + " 'created_at', entity_17.created_at,", + " 'id', entity_17.id,", + " 'number', phone_number_16.number,", + " 'type', entity_17.type", " )", - " FROM agreego.phone_number phone_number_12", - " JOIN agreego.entity entity_13 ON entity_13.id = phone_number_12.id", + " FROM agreego.phone_number phone_number_16", + " JOIN agreego.entity entity_17 ON entity_17.id = phone_number_16.id", " WHERE", - " NOT entity_13.archived", - " AND relationship_10.target_id = entity_13.id))", + " NOT entity_17.archived", + " AND relationship_10.target_id = entity_17.id))", " ELSE NULL END,", " 'type', entity_11.type", " )), '[]'::jsonb)", @@ -1240,17 +1324,17 @@ " 'target', CASE", " WHEN entity_11.target_type = 'address' THEN", " ((SELECT jsonb_build_object(", - " 'archived', entity_17.archived,", - " 'city', address_16.city,", - " 'created_at', entity_17.created_at,", - " 'id', entity_17.id,", - " 'type', entity_17.type", + " 'archived', entity_13.archived,", + " 'city', address_12.city,", + " 'created_at', entity_13.created_at,", + " 'id', entity_13.id,", + " 'type', entity_13.type", " )", - " FROM agreego.address address_16", - " JOIN agreego.entity entity_17 ON entity_17.id = address_16.id", + " FROM agreego.address address_12", + " JOIN agreego.entity entity_13 ON entity_13.id = address_12.id", " WHERE", - " NOT entity_17.archived", - " AND relationship_10.target_id = entity_17.id))", + " NOT entity_13.archived", + " AND relationship_10.target_id = entity_13.id))", " WHEN entity_11.target_type = 'email_address' THEN", " ((SELECT jsonb_build_object(", " 'address', email_address_14.address,", @@ -1266,17 +1350,17 @@ " AND relationship_10.target_id = entity_15.id))", " WHEN entity_11.target_type = 'phone_number' THEN", " ((SELECT jsonb_build_object(", - " 'archived', entity_13.archived,", - " 'created_at', entity_13.created_at,", - " 'id', entity_13.id,", - " 'number', phone_number_12.number,", - " 'type', entity_13.type", + " 'archived', entity_17.archived,", + " 'created_at', entity_17.created_at,", + " 'id', entity_17.id,", + " 'number', phone_number_16.number,", + " 'type', entity_17.type", " )", - " FROM agreego.phone_number phone_number_12", - " JOIN agreego.entity entity_13 ON entity_13.id = phone_number_12.id", + " FROM agreego.phone_number phone_number_16", + " JOIN agreego.entity entity_17 ON entity_17.id = phone_number_16.id", " WHERE", - " NOT entity_13.archived", - " AND relationship_10.target_id = entity_13.id))", + " NOT entity_17.archived", + " AND relationship_10.target_id = entity_17.id))", " ELSE NULL END,", " 'type', entity_11.type", " )), '[]'::jsonb)", @@ -1565,27 +1649,27 @@ } }, { - "description": "Person select via a punc response with family", + "description": "Light organizations select via a punc response with family", "action": "query", - "schema_id": "get_person.response", + "schema_id": "get_light_organizations.response", "expect": { "success": true, "sql": [ [ - "(SELECT jsonb_strip_nulls((SELECT jsonb_build_object(", - " 'age', person_1.age,", - " 'archived', entity_3.archived,", - " 'created_at', entity_3.created_at,", - " 'first_name', person_1.first_name,", - " 'id', entity_3.id,", - " 'last_name', person_1.last_name,", - " 'name', organization_2.name,", - " 'type', entity_3.type", - ")", - "FROM agreego.person person_1", - "JOIN agreego.organization organization_2 ON organization_2.id = person_1.id", - "JOIN agreego.entity entity_3 ON entity_3.id = organization_2.id", - "WHERE NOT entity_3.archived)))" + "FIX ME" + ] + ] + } + }, + { + "description": "Full organizations select via a punc response with family", + "action": "query", + "schema_id": "get_full_organizations.response", + "expect": { + "success": true, + "sql": [ + [ + "FIX ME" ] ] } @@ -1629,6 +1713,19 @@ ] ] } + }, + { + "description": "Widgets select via a punc response with family (STI)", + "action": "query", + "schema_id": "get_widgets.response", + "expect": { + "success": true, + "sql": [ + [ + "FIX ME" + ] + ] + } } ] } diff --git a/out.txt b/out.txt new file mode 100644 index 0000000..eeda86e --- /dev/null +++ b/out.txt @@ -0,0 +1,45 @@ +warning: unused variable: `is_family` + --> src/database/schema.rs:495:9 + | +495 | let is_family = self.obj.family.is_some(); + | ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_is_family` + | + = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default + +warning: `jspg` (lib) generated 1 warning (run `cargo fix --lib -p jspg` to apply 1 suggestion) +warning: `jspg` (lib test) generated 1 warning (1 duplicate) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.32s + Running unittests src/lib.rs (target/debug/deps/jspg-d3f18ff3a7e2b386) + +running 1 test +test tests::test_queryer_0_8 ... FAILED + +failures: + +---- tests::test_queryer_0_8 stdout ---- +[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: kind, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: kind, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) +[DEBUG POLYMORPHISM] ID: get_person.response, Strategy: type, Pathways: {"full.person": "full.person", "light.person": "light.person", "person": "person"} +[DEBUG QUERYER] Evaluating node. Target family: Some("organization"), disc: Some("type"), pathways: Some({"bot": "bot", "full.person": "full.person", "get_organization.response": "get_organization.response", "light.person": "light.person", "organization": "organization", "person": "person"}) +JSPG_SQL: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id', organization_1.id, 'type', CASE WHEN organization_1.type = 'bot' THEN ((SELECT jsonb_build_object('archived', entity_5.archived, 'created_at', entity_5.created_at, 'id', entity_5.id, 'name', organization_4.name, 'token', bot_3.token, 'type', entity_5.type) FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id = bot_3.id JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id WHERE NOT entity_5.archived)) WHEN organization_1.type = 'full.person' THEN ((SELECT jsonb_build_object('addresses', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_11.archived, 'created_at', entity_11.created_at, 'id', entity_11.id, 'is_primary', contact_9.is_primary, 'target', (SELECT jsonb_build_object('archived', entity_13.archived, 'city', address_12.city, 'created_at', entity_13.created_at, 'id', entity_13.id, 'type', entity_13.type) FROM agreego.address address_12 JOIN agreego.entity entity_13 ON entity_13.id = address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id = entity_13.id), 'type', entity_11.type)), '[]'::jsonb) FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id = contact_9.id JOIN agreego.entity entity_11 ON entity_11.id = relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type = 'address' AND relationship_10.source_id = entity_8.id), 'age', person_6.age, 'archived', entity_8.archived, 'contacts', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_16.archived, 'created_at', entity_16.created_at, 'id', entity_16.id, 'is_primary', contact_14.is_primary, 'target', CASE WHEN entity_16.target_type = 'address' THEN ((SELECT jsonb_build_object('archived', entity_18.archived, 'city', address_17.city, 'created_at', entity_18.created_at, 'id', entity_18.id, 'type', entity_18.type) FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id = address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id = entity_18.id)) WHEN entity_16.target_type = 'email_address' THEN ((SELECT jsonb_build_object('address', email_address_19.address, 'archived', entity_20.archived, 'created_at', entity_20.created_at, 'id', entity_20.id, 'type', entity_20.type) FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id = email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id = entity_20.id)) WHEN entity_16.target_type = 'phone_number' THEN ((SELECT jsonb_build_object('archived', entity_22.archived, 'created_at', entity_22.created_at, 'id', entity_22.id, 'number', phone_number_21.number, 'type', entity_22.type) FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id = phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id = entity_22.id)) ELSE NULL END, 'type', entity_16.type)), '[]'::jsonb) FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id = contact_14.id JOIN agreego.entity entity_16 ON entity_16.id = relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id = entity_8.id), 'created_at', entity_8.created_at, 'email_addresses', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_25.archived, 'created_at', entity_25.created_at, 'id', entity_25.id, 'is_primary', contact_23.is_primary, 'target', (SELECT jsonb_build_object('address', email_address_26.address, 'archived', entity_27.archived, 'created_at', entity_27.created_at, 'id', entity_27.id, 'type', entity_27.type) FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id = email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id = entity_27.id), 'type', entity_25.type)), '[]'::jsonb) FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id = contact_23.id JOIN agreego.entity entity_25 ON entity_25.id = relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type = 'email_address' AND relationship_24.source_id = entity_8.id), 'first_name', person_6.first_name, 'id', entity_8.id, 'last_name', person_6.last_name, 'name', organization_7.name, 'phone_numbers', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_30.archived, 'created_at', entity_30.created_at, 'id', entity_30.id, 'is_primary', contact_28.is_primary, 'target', (SELECT jsonb_build_object('archived', entity_32.archived, 'created_at', entity_32.created_at, 'id', entity_32.id, 'number', phone_number_31.number, 'type', entity_32.type) FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id = phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id = entity_32.id), 'type', entity_30.type)), '[]'::jsonb) FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id = contact_28.id JOIN agreego.entity entity_30 ON entity_30.id = relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type = 'phone_number' AND relationship_29.source_id = entity_8.id), 'type', entity_8.type) FROM agreego.person person_6 JOIN agreego.organization organization_7 ON organization_7.id = person_6.id JOIN agreego.entity entity_8 ON entity_8.id = organization_7.id WHERE NOT entity_8.archived)) WHEN organization_1.type = 'get_organization.response' THEN ((SELECT jsonb_build_object('archived', entity_34.archived, 'created_at', entity_34.created_at, 'id', entity_34.id, 'name', organization_33.name, 'type', entity_34.type) FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id = organization_33.id WHERE NOT entity_34.archived)) WHEN organization_1.type = 'light.person' THEN ((SELECT jsonb_build_object('age', person_35.age, 'archived', entity_37.archived, 'created_at', entity_37.created_at, 'first_name', person_35.first_name, 'id', entity_37.id, 'last_name', person_35.last_name, 'name', organization_36.name, 'type', entity_37.type) FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id = person_35.id JOIN agreego.entity entity_37 ON entity_37.id = organization_36.id WHERE NOT entity_37.archived)) WHEN organization_1.type = 'organization' THEN ((SELECT jsonb_build_object('archived', entity_39.archived, 'created_at', entity_39.created_at, 'id', entity_39.id, 'name', organization_38.name, 'type', entity_39.type) FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id = organization_38.id WHERE NOT entity_39.archived)) WHEN organization_1.type = 'person' THEN ((SELECT jsonb_build_object('age', person_40.age, 'archived', entity_42.archived, 'created_at', entity_42.created_at, 'first_name', person_40.first_name, 'id', entity_42.id, 'last_name', person_40.last_name, 'name', organization_41.name, 'type', entity_42.type) FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id = person_40.id JOIN agreego.entity entity_42 ON entity_42.id = organization_41.id WHERE NOT entity_42.archived)) ELSE NULL END)), '[]'::jsonb) FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id WHERE NOT entity_2.archived))) +TEST QUERY ERROR FOR 'Organizations select via a punc response with family': Line mismatched at execution sequence 1. +Expected Pattern: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id',organization_1.id,'type',CASE WHEN organization_1.type='bot'THEN((SELECT jsonb_build_object('archived',entity_5.archived,'created_at',entity_5.created_at,'id',entity_5.id,'name',organization_4.name,'token',bot_3.token,'type',entity_5.type)FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id=bot_3.id JOIN agreego.entity entity_5 ON entity_5.id=organization_4.id WHERE NOT entity_5.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_7.archived,'created_at',entity_7.created_at,'id',entity_7.id,'name',organization_6.name,'type',entity_7.type)FROM agreego.organization organization_6 JOIN agreego.entity entity_7 ON entity_7.id=organization_6.id WHERE NOT entity_7.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_8.age,'archived',entity_10.archived,'created_at',entity_10.created_at,'first_name',person_8.first_name,'id',entity_10.id,'last_name',person_8.last_name,'name',organization_9.name,'type',entity_10.type)FROM agreego.person person_8 JOIN agreego.organization organization_9 ON organization_9.id=person_8.id JOIN agreego.entity entity_10 ON entity_10.id=organization_9.id WHERE NOT entity_10.archived))ELSE NULL END)),'[]'::jsonb)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived))) +Actual SQL: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id',organization_1.id,'type',CASE WHEN organization_1.type='bot'THEN((SELECT jsonb_build_object('archived',entity_5.archived,'created_at',entity_5.created_at,'id',entity_5.id,'name',organization_4.name,'token',bot_3.token,'type',entity_5.type)FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id=bot_3.id JOIN agreego.entity entity_5 ON entity_5.id=organization_4.id WHERE NOT entity_5.archived))WHEN organization_1.type='full.person'THEN((SELECT jsonb_build_object('addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_11.archived,'created_at',entity_11.created_at,'id',entity_11.id,'is_primary',contact_9.is_primary,'target',(SELECT jsonb_build_object('archived',entity_13.archived,'city',address_12.city,'created_at',entity_13.created_at,'id',entity_13.id,'type',entity_13.type)FROM agreego.address address_12 JOIN agreego.entity entity_13 ON entity_13.id=address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id=entity_13.id),'type',entity_11.type)),'[]'::jsonb)FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id=contact_9.id JOIN agreego.entity entity_11 ON entity_11.id=relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type='address'AND relationship_10.source_id=entity_8.id),'age',person_6.age,'archived',entity_8.archived,'contacts',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_16.archived,'created_at',entity_16.created_at,'id',entity_16.id,'is_primary',contact_14.is_primary,'target',CASE WHEN entity_16.target_type='address'THEN((SELECT jsonb_build_object('archived',entity_18.archived,'city',address_17.city,'created_at',entity_18.created_at,'id',entity_18.id,'type',entity_18.type)FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id=address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id=entity_18.id))WHEN entity_16.target_type='email_address'THEN((SELECT jsonb_build_object('address',email_address_19.address,'archived',entity_20.archived,'created_at',entity_20.created_at,'id',entity_20.id,'type',entity_20.type)FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id=email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id=entity_20.id))WHEN entity_16.target_type='phone_number'THEN((SELECT jsonb_build_object('archived',entity_22.archived,'created_at',entity_22.created_at,'id',entity_22.id,'number',phone_number_21.number,'type',entity_22.type)FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id=phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id=entity_22.id))ELSE NULL END,'type',entity_16.type)),'[]'::jsonb)FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id=contact_14.id JOIN agreego.entity entity_16 ON entity_16.id=relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id=entity_8.id),'created_at',entity_8.created_at,'email_addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_25.archived,'created_at',entity_25.created_at,'id',entity_25.id,'is_primary',contact_23.is_primary,'target',(SELECT jsonb_build_object('address',email_address_26.address,'archived',entity_27.archived,'created_at',entity_27.created_at,'id',entity_27.id,'type',entity_27.type)FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id=email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id=entity_27.id),'type',entity_25.type)),'[]'::jsonb)FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id=contact_23.id JOIN agreego.entity entity_25 ON entity_25.id=relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type='email_address'AND relationship_24.source_id=entity_8.id),'first_name',person_6.first_name,'id',entity_8.id,'last_name',person_6.last_name,'name',organization_7.name,'phone_numbers',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_30.archived,'created_at',entity_30.created_at,'id',entity_30.id,'is_primary',contact_28.is_primary,'target',(SELECT jsonb_build_object('archived',entity_32.archived,'created_at',entity_32.created_at,'id',entity_32.id,'number',phone_number_31.number,'type',entity_32.type)FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id=phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id=entity_32.id),'type',entity_30.type)),'[]'::jsonb)FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id=contact_28.id JOIN agreego.entity entity_30 ON entity_30.id=relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type='phone_number'AND relationship_29.source_id=entity_8.id),'type',entity_8.type)FROM agreego.person person_6 JOIN agreego.organization organization_7 ON organization_7.id=person_6.id JOIN agreego.entity entity_8 ON entity_8.id=organization_7.id WHERE NOT entity_8.archived))WHEN organization_1.type='get_organization.response'THEN((SELECT jsonb_build_object('archived',entity_34.archived,'created_at',entity_34.created_at,'id',entity_34.id,'name',organization_33.name,'type',entity_34.type)FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id=organization_33.id WHERE NOT entity_34.archived))WHEN organization_1.type='light.person'THEN((SELECT jsonb_build_object('age',person_35.age,'archived',entity_37.archived,'created_at',entity_37.created_at,'first_name',person_35.first_name,'id',entity_37.id,'last_name',person_35.last_name,'name',organization_36.name,'type',entity_37.type)FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id=person_35.id JOIN agreego.entity entity_37 ON entity_37.id=organization_36.id WHERE NOT entity_37.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_39.archived,'created_at',entity_39.created_at,'id',entity_39.id,'name',organization_38.name,'type',entity_39.type)FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id=organization_38.id WHERE NOT entity_39.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_40.age,'archived',entity_42.archived,'created_at',entity_42.created_at,'first_name',person_40.first_name,'id',entity_42.id,'last_name',person_40.last_name,'name',organization_41.name,'type',entity_42.type)FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id=person_40.id JOIN agreego.entity entity_42 ON entity_42.id=organization_41.id WHERE NOT entity_42.archived))ELSE NULL END)),'[]'::jsonb)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived))) +Regex used: \(SELECT jsonb_strip_nulls\(\(SELECT COALESCE\(jsonb_agg\(jsonb_build_object\('id',organization_1\.id,'type',CASE WHEN organization_1\.type='bot'THEN\(\(SELECT jsonb_build_object\('archived',entity_5\.archived,'created_at',entity_5\.created_at,'id',entity_5\.id,'name',organization_4\.name,'token',bot_3\.token,'type',entity_5\.type\)FROM agreego\.bot bot_3 JOIN agreego\.organization organization_4 ON organization_4\.id=bot_3\.id JOIN agreego\.entity entity_5 ON entity_5\.id=organization_4\.id WHERE NOT entity_5\.archived\)\)WHEN organization_1\.type='organization'THEN\(\(SELECT jsonb_build_object\('archived',entity_7\.archived,'created_at',entity_7\.created_at,'id',entity_7\.id,'name',organization_6\.name,'type',entity_7\.type\)FROM agreego\.organization organization_6 JOIN agreego\.entity entity_7 ON entity_7\.id=organization_6\.id WHERE NOT entity_7\.archived\)\)WHEN organization_1\.type='person'THEN\(\(SELECT jsonb_build_object\('age',person_8\.age,'archived',entity_10\.archived,'created_at',entity_10\.created_at,'first_name',person_8\.first_name,'id',entity_10\.id,'last_name',person_8\.last_name,'name',organization_9\.name,'type',entity_10\.type\)FROM agreego\.person person_8 JOIN agreego\.organization organization_9 ON organization_9\.id=person_8\.id JOIN agreego\.entity entity_10 ON entity_10\.id=organization_9\.id WHERE NOT entity_10\.archived\)\)ELSE NULL END\)\),'\[\]'::jsonb\)FROM agreego\.organization organization_1 JOIN agreego\.entity entity_2 ON entity_2\.id=organization_1\.id WHERE NOT entity_2\.archived\)\)\) +Variables Mapped: {} + +thread 'tests::test_queryer_0_8' (97340744) panicked at src/tests/fixtures.rs:1427:54: +called `Result::unwrap()` on an `Err` value: "[Queryer Execution] Query Test 'Organizations select via a punc response with family' failed. Error: Line mismatched at execution sequence 1.\nExpected Pattern: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id',organization_1.id,'type',CASE WHEN organization_1.type='bot'THEN((SELECT jsonb_build_object('archived',entity_5.archived,'created_at',entity_5.created_at,'id',entity_5.id,'name',organization_4.name,'token',bot_3.token,'type',entity_5.type)FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id=bot_3.id JOIN agreego.entity entity_5 ON entity_5.id=organization_4.id WHERE NOT entity_5.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_7.archived,'created_at',entity_7.created_at,'id',entity_7.id,'name',organization_6.name,'type',entity_7.type)FROM agreego.organization organization_6 JOIN agreego.entity entity_7 ON entity_7.id=organization_6.id WHERE NOT entity_7.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_8.age,'archived',entity_10.archived,'created_at',entity_10.created_at,'first_name',person_8.first_name,'id',entity_10.id,'last_name',person_8.last_name,'name',organization_9.name,'type',entity_10.type)FROM agreego.person person_8 JOIN agreego.organization organization_9 ON organization_9.id=person_8.id JOIN agreego.entity entity_10 ON entity_10.id=organization_9.id WHERE NOT entity_10.archived))ELSE NULL END)),'[]'::jsonb)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived)))\nActual SQL: (SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object('id',organization_1.id,'type',CASE WHEN organization_1.type='bot'THEN((SELECT jsonb_build_object('archived',entity_5.archived,'created_at',entity_5.created_at,'id',entity_5.id,'name',organization_4.name,'token',bot_3.token,'type',entity_5.type)FROM agreego.bot bot_3 JOIN agreego.organization organization_4 ON organization_4.id=bot_3.id JOIN agreego.entity entity_5 ON entity_5.id=organization_4.id WHERE NOT entity_5.archived))WHEN organization_1.type='full.person'THEN((SELECT jsonb_build_object('addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_11.archived,'created_at',entity_11.created_at,'id',entity_11.id,'is_primary',contact_9.is_primary,'target',(SELECT jsonb_build_object('archived',entity_13.archived,'city',address_12.city,'created_at',entity_13.created_at,'id',entity_13.id,'type',entity_13.type)FROM agreego.address address_12 JOIN agreego.entity entity_13 ON entity_13.id=address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id=entity_13.id),'type',entity_11.type)),'[]'::jsonb)FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id=contact_9.id JOIN agreego.entity entity_11 ON entity_11.id=relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type='address'AND relationship_10.source_id=entity_8.id),'age',person_6.age,'archived',entity_8.archived,'contacts',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_16.archived,'created_at',entity_16.created_at,'id',entity_16.id,'is_primary',contact_14.is_primary,'target',CASE WHEN entity_16.target_type='address'THEN((SELECT jsonb_build_object('archived',entity_18.archived,'city',address_17.city,'created_at',entity_18.created_at,'id',entity_18.id,'type',entity_18.type)FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id=address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id=entity_18.id))WHEN entity_16.target_type='email_address'THEN((SELECT jsonb_build_object('address',email_address_19.address,'archived',entity_20.archived,'created_at',entity_20.created_at,'id',entity_20.id,'type',entity_20.type)FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id=email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id=entity_20.id))WHEN entity_16.target_type='phone_number'THEN((SELECT jsonb_build_object('archived',entity_22.archived,'created_at',entity_22.created_at,'id',entity_22.id,'number',phone_number_21.number,'type',entity_22.type)FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id=phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id=entity_22.id))ELSE NULL END,'type',entity_16.type)),'[]'::jsonb)FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id=contact_14.id JOIN agreego.entity entity_16 ON entity_16.id=relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id=entity_8.id),'created_at',entity_8.created_at,'email_addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_25.archived,'created_at',entity_25.created_at,'id',entity_25.id,'is_primary',contact_23.is_primary,'target',(SELECT jsonb_build_object('address',email_address_26.address,'archived',entity_27.archived,'created_at',entity_27.created_at,'id',entity_27.id,'type',entity_27.type)FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id=email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id=entity_27.id),'type',entity_25.type)),'[]'::jsonb)FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id=contact_23.id JOIN agreego.entity entity_25 ON entity_25.id=relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type='email_address'AND relationship_24.source_id=entity_8.id),'first_name',person_6.first_name,'id',entity_8.id,'last_name',person_6.last_name,'name',organization_7.name,'phone_numbers',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_30.archived,'created_at',entity_30.created_at,'id',entity_30.id,'is_primary',contact_28.is_primary,'target',(SELECT jsonb_build_object('archived',entity_32.archived,'created_at',entity_32.created_at,'id',entity_32.id,'number',phone_number_31.number,'type',entity_32.type)FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id=phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id=entity_32.id),'type',entity_30.type)),'[]'::jsonb)FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id=contact_28.id JOIN agreego.entity entity_30 ON entity_30.id=relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type='phone_number'AND relationship_29.source_id=entity_8.id),'type',entity_8.type)FROM agreego.person person_6 JOIN agreego.organization organization_7 ON organization_7.id=person_6.id JOIN agreego.entity entity_8 ON entity_8.id=organization_7.id WHERE NOT entity_8.archived))WHEN organization_1.type='get_organization.response'THEN((SELECT jsonb_build_object('archived',entity_34.archived,'created_at',entity_34.created_at,'id',entity_34.id,'name',organization_33.name,'type',entity_34.type)FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id=organization_33.id WHERE NOT entity_34.archived))WHEN organization_1.type='light.person'THEN((SELECT jsonb_build_object('age',person_35.age,'archived',entity_37.archived,'created_at',entity_37.created_at,'first_name',person_35.first_name,'id',entity_37.id,'last_name',person_35.last_name,'name',organization_36.name,'type',entity_37.type)FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id=person_35.id JOIN agreego.entity entity_37 ON entity_37.id=organization_36.id WHERE NOT entity_37.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_39.archived,'created_at',entity_39.created_at,'id',entity_39.id,'name',organization_38.name,'type',entity_39.type)FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id=organization_38.id WHERE NOT entity_39.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_40.age,'archived',entity_42.archived,'created_at',entity_42.created_at,'first_name',person_40.first_name,'id',entity_42.id,'last_name',person_40.last_name,'name',organization_41.name,'type',entity_42.type)FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id=person_40.id JOIN agreego.entity entity_42 ON entity_42.id=organization_41.id WHERE NOT entity_42.archived))ELSE NULL END)),'[]'::jsonb)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived)))\nRegex used: \\(SELECT jsonb_strip_nulls\\(\\(SELECT COALESCE\\(jsonb_agg\\(jsonb_build_object\\('id',organization_1\\.id,'type',CASE WHEN organization_1\\.type='bot'THEN\\(\\(SELECT jsonb_build_object\\('archived',entity_5\\.archived,'created_at',entity_5\\.created_at,'id',entity_5\\.id,'name',organization_4\\.name,'token',bot_3\\.token,'type',entity_5\\.type\\)FROM agreego\\.bot bot_3 JOIN agreego\\.organization organization_4 ON organization_4\\.id=bot_3\\.id JOIN agreego\\.entity entity_5 ON entity_5\\.id=organization_4\\.id WHERE NOT entity_5\\.archived\\)\\)WHEN organization_1\\.type='organization'THEN\\(\\(SELECT jsonb_build_object\\('archived',entity_7\\.archived,'created_at',entity_7\\.created_at,'id',entity_7\\.id,'name',organization_6\\.name,'type',entity_7\\.type\\)FROM agreego\\.organization organization_6 JOIN agreego\\.entity entity_7 ON entity_7\\.id=organization_6\\.id WHERE NOT entity_7\\.archived\\)\\)WHEN organization_1\\.type='person'THEN\\(\\(SELECT jsonb_build_object\\('age',person_8\\.age,'archived',entity_10\\.archived,'created_at',entity_10\\.created_at,'first_name',person_8\\.first_name,'id',entity_10\\.id,'last_name',person_8\\.last_name,'name',organization_9\\.name,'type',entity_10\\.type\\)FROM agreego\\.person person_8 JOIN agreego\\.organization organization_9 ON organization_9\\.id=person_8\\.id JOIN agreego\\.entity entity_10 ON entity_10\\.id=organization_9\\.id WHERE NOT entity_10\\.archived\\)\\)ELSE NULL END\\)\\),'\\[\\]'::jsonb\\)FROM agreego\\.organization organization_1 JOIN agreego\\.entity entity_2 ON entity_2\\.id=organization_1\\.id WHERE NOT entity_2\\.archived\\)\\)\\)\nVariables Mapped: {}" +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + tests::test_queryer_0_8 + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1350 filtered out; finished in 0.01s + +error: test failed, to rerun pass `--lib` diff --git a/src/database/mod.rs b/src/database/mod.rs index 96d9682..d2cf48c 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -32,7 +32,6 @@ pub struct Database { pub puncs: HashMap, pub relations: HashMap, pub schemas: HashMap, - pub descendants: HashMap>, pub depths: HashMap, pub executor: Box, } @@ -45,7 +44,6 @@ impl Database { relations: HashMap::new(), puncs: HashMap::new(), schemas: HashMap::new(), - descendants: HashMap::new(), depths: HashMap::new(), #[cfg(not(test))] executor: Box::new(SpiExecutor::new()), @@ -194,7 +192,6 @@ impl Database { self.collect_schemas(errors); self.collect_depths(); - self.collect_descendants(); // Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks let mut visited = std::collections::HashSet::new(); @@ -256,43 +253,4 @@ impl Database { self.depths = depths; } - fn collect_descendants(&mut self) { - let mut direct_refs: HashMap> = HashMap::new(); - for (id, schema) in &self.schemas { - if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ { - if !crate::database::schema::is_primitive_type(t) { - direct_refs - .entry(t.clone()) - .or_default() - .push(id.clone()); - } - } - } - - // Cache exhaustive descendants matrix for generic $family string lookups natively - let mut descendants = HashMap::new(); - for id in self.schemas.keys() { - let mut desc_set = HashSet::new(); - Self::collect_descendants_recursively(id, &direct_refs, &mut desc_set); - let mut desc_vec: Vec = desc_set.into_iter().collect(); - desc_vec.sort(); - - descendants.insert(id.clone(), desc_vec); - } - self.descendants = descendants; - } - - fn collect_descendants_recursively( - target: &str, - direct_refs: &std::collections::HashMap>, - descendants: &mut std::collections::HashSet, - ) { - if let Some(children) = direct_refs.get(target) { - for child in children { - if descendants.insert(child.clone()) { - Self::collect_descendants_recursively(child, direct_refs, descendants); - } - } - } - } } diff --git a/src/database/schema.rs b/src/database/schema.rs index 484a510..3a75b7c 100644 --- a/src/database/schema.rs +++ b/src/database/schema.rs @@ -23,6 +23,10 @@ pub fn is_once_lock_vec_empty(lock: &OnceLock>) -> bool { lock.get().map_or(true, |v| v.is_empty()) } +pub fn is_once_lock_string_empty(lock: &OnceLock) -> bool { + lock.get().map_or(true, |s| s.is_empty()) +} + // Schema mirrors the Go Punc Generator's schema struct for consistency. // It is an order-preserving representation of a JSON Schema. pub fn deserialize_some<'de, D>(deserializer: D) -> Result, D::Error> @@ -201,6 +205,18 @@ pub struct SchemaObject { #[serde(skip)] pub compiled_properties: OnceLock>>, + #[serde(rename = "compiledDiscriminator")] + #[serde(skip_deserializing)] + #[serde(skip_serializing_if = "crate::database::schema::is_once_lock_string_empty")] + #[serde(serialize_with = "crate::database::schema::serialize_once_lock")] + pub compiled_discriminator: OnceLock, + + #[serde(rename = "compiledOptions")] + #[serde(skip_deserializing)] + #[serde(skip_serializing_if = "crate::database::schema::is_once_lock_map_empty")] + #[serde(serialize_with = "crate::database::schema::serialize_once_lock")] + pub compiled_options: OnceLock>, + #[serde(rename = "compiledEdges")] #[serde(skip_deserializing)] #[serde(skip_serializing_if = "crate::database::schema::is_once_lock_map_empty")] @@ -411,11 +427,140 @@ impl Schema { } } + self.compile_polymorphism(db, errors); + if let Some(id) = &self.obj.id { visited.remove(id); } } + pub fn compile_polymorphism( + &self, + db: &crate::database::Database, + errors: &mut Vec, + ) { + let mut options = std::collections::BTreeMap::new(); + let mut strategy = String::new(); + + if let Some(family) = &self.obj.family { + let family_base = family.split('.').next_back().unwrap_or(family).to_string(); + let family_prefix = family.strip_suffix(&family_base).unwrap_or("").trim_end_matches('.'); + + if let Some(type_def) = db.types.get(&family_base) { + if type_def.variations.len() > 1 && type_def.variations.iter().any(|v| v != &family_base) { + // Scenario A / B: Table Variations + strategy = "type".to_string(); + for var in &type_def.variations { + let target_id = if family_prefix.is_empty() { + var.to_string() + } else { + format!("{}.{}", family_prefix, var) + }; + + if db.schemas.contains_key(&target_id) { + options.insert(var.to_string(), target_id); + } + } + } else { + // Scenario C: Single Table Inheritance (Horizontal) + strategy = "kind".to_string(); + + let mut target_family_ids = std::collections::HashSet::new(); + target_family_ids.insert(family.clone()); + + // Iteratively build local descendants since db.descendants is removed natively + let mut added = true; + while added { + added = false; + for schema in &type_def.schemas { + if let Some(id) = &schema.obj.id { + if !target_family_ids.contains(id) { + if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ { + if target_family_ids.contains(t) { + target_family_ids.insert(id.clone()); + added = true; + } + } + } + } + } + } + + for schema in &type_def.schemas { + if let Some(id) = &schema.obj.id { + if target_family_ids.contains(id) { + if let Some(kind_val) = schema.obj.get_discriminator_value("kind") { + options.insert(kind_val, id.to_string()); + } + } + } + } + } + } + } else if let Some(one_of) = &self.obj.one_of { + let mut type_vals = std::collections::HashSet::new(); + let mut kind_vals = std::collections::HashSet::new(); + + for c in one_of { + if let Some(t_val) = c.obj.get_discriminator_value("type") { + type_vals.insert(t_val); + } + if let Some(k_val) = c.obj.get_discriminator_value("kind") { + kind_vals.insert(k_val); + } + } + + strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() { + "type".to_string() + } else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() { + "kind".to_string() + } else { + "".to_string() + }; + + if strategy.is_empty() { + return; + } + + for c in one_of { + if let Some(val) = c.obj.get_discriminator_value(&strategy) { + if options.contains_key(&val) { + errors.push(crate::drop::Error { + code: "POLYMORPHIC_COLLISION".to_string(), + message: format!("Polymorphic boundary defines multiple candidates mapped to the identical discriminator value '{}'.", val), + details: crate::drop::ErrorDetails::default() + }); + continue; + } + + let mut target_id = c.obj.id.clone(); + if target_id.is_none() { + if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &c.obj.type_ { + if !crate::database::schema::is_primitive_type(t) { + target_id = Some(t.clone()); + } + } + } + + if let Some(tid) = target_id { + options.insert(val, tid); + } + } + } + } else { + return; + } + + if let Some(id) = &self.obj.id { + println!("[DEBUG POLYMORPHISM] ID: {}, Strategy: {}, Options: {:?}", id, strategy, options); + } + + if !options.is_empty() { + let _ = self.obj.compiled_discriminator.set(strategy); + let _ = self.obj.compiled_options.set(options); + } + } + #[allow(unused_variables)] fn validate_identifier(id: &str, field_name: &str, errors: &mut Vec) { #[cfg(not(test))] @@ -893,6 +1038,72 @@ impl SchemaObject { } None } + + pub fn get_discriminator_value(&self, dim: &str) -> Option { + let is_split = self.compiled_properties.get().map_or(false, |p| p.contains_key("kind")); + if let Some(id) = &self.id { + if id.contains("light.person") || id.contains("light.organization") { + println!("[DEBUG SPLIT] ID: {}, dim: {}, is_split: {:?}, props: {:?}", id, dim, is_split, self.compiled_properties.get().map(|p| p.keys().cloned().collect::>())); + } + } + + if let Some(props) = self.compiled_properties.get() { + if let Some(prop_schema) = props.get(dim) { + if let Some(c) = &prop_schema.obj.const_ { + if let Some(s) = c.as_str() { + return Some(s.to_string()); + } + } + if let Some(e) = &prop_schema.obj.enum_ { + if e.len() == 1 { + if let Some(s) = e[0].as_str() { + return Some(s.to_string()); + } + } + } + } + } + + if dim == "kind" { + if let Some(id) = &self.id { + let base = id.split('/').last().unwrap_or(id); + if let Some(idx) = base.rfind('.') { + return Some(base[..idx].to_string()); + } + } + if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &self.type_ { + if !crate::database::schema::is_primitive_type(t) { + let base = t.split('/').last().unwrap_or(t); + if let Some(idx) = base.rfind('.') { + return Some(base[..idx].to_string()); + } + } + } + } + + if dim == "type" { + if let Some(id) = &self.id { + let base = id.split('/').last().unwrap_or(id); + if is_split { + return Some(base.split('.').next_back().unwrap_or(base).to_string()); + } else { + return Some(base.to_string()); + } + } + if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &self.type_ { + if !crate::database::schema::is_primitive_type(t) { + let base = t.split('/').last().unwrap_or(t); + if is_split { + return Some(base.split('.').next_back().unwrap_or(base).to_string()); + } else { + return Some(base.to_string()); + } + } + } + } + + None + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/merger/mod.rs b/src/merger/mod.rs index 4490d8a..0d65bd4 100644 --- a/src/merger/mod.rs +++ b/src/merger/mod.rs @@ -131,13 +131,33 @@ impl Merger { pub(crate) fn merge_internal( &self, - schema: Arc, + mut schema: Arc, data: Value, notifications: &mut Vec, ) -> Result { match data { Value::Array(items) => self.merge_array(schema, items, notifications), - Value::Object(map) => self.merge_object(schema, map, notifications), + Value::Object(map) => { + if let Some(options) = schema.obj.compiled_options.get() { + if let Some(disc) = schema.obj.compiled_discriminator.get() { + let val = map.get(disc).and_then(|v| v.as_str()); + if let Some(v) = val { + if let Some(target_id) = options.get(v) { + if let Some(target_schema) = self.db.schemas.get(target_id) { + schema = Arc::new(target_schema.clone()); + } else { + return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id)); + } + } else { + return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v)); + } + } else { + return Err(format!("Polymorphic merging failed: missing required discriminator '{}'", disc)); + } + } + } + self.merge_object(schema, map, notifications) + }, _ => Err("Invalid merge payload: root must be an Object or Array".to_string()), } } diff --git a/src/queryer/compiler.rs b/src/queryer/compiler.rs index 811f17f..aea7acf 100644 --- a/src/queryer/compiler.rs +++ b/src/queryer/compiler.rs @@ -1,5 +1,4 @@ use crate::database::Database; -use std::sync::Arc; pub struct Compiler<'a> { pub db: &'a Database, pub filter_keys: &'a [String], @@ -124,35 +123,19 @@ impl<'a> Compiler<'a> { return Err(format!("Unresolved schema type pointer: {}", t)); } } - // Handle $family Polymorphism fallbacks for relations - if let Some(family_target) = &node.schema.obj.family { - let mut all_targets = vec![family_target.clone()]; - if let Some(descendants) = self.db.descendants.get(family_target) { - all_targets.extend(descendants.clone()); - } - - if all_targets.len() == 1 { - let mut bypass_schema = crate::database::schema::Schema::default(); - bypass_schema.obj.type_ = Some(crate::database::schema::SchemaTypeOrArray::Single(all_targets[0].clone())); - let mut bypass_node = node.clone(); - bypass_node.schema = std::sync::Arc::new(bypass_schema); - return self.compile_node(bypass_node); - } - - all_targets.sort(); - let mut family_schemas = Vec::new(); - for variation in &all_targets { - let mut ref_schema = crate::database::schema::Schema::default(); - ref_schema.obj.type_ = Some(crate::database::schema::SchemaTypeOrArray::Single(variation.clone())); - family_schemas.push(std::sync::Arc::new(ref_schema)); - } - - return self.compile_one_of(&family_schemas, node); - } - - // Handle oneOf Polymorphism fallbacks for relations - if let Some(one_of) = &node.schema.obj.one_of { - return self.compile_one_of(one_of, node.clone()); + // Handle Polymorphism fallbacks for relations + if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() { + if let Some(options) = node.schema.obj.compiled_options.get() { + if options.len() == 1 { + let target_id = options.values().next().unwrap(); + let mut bypass_schema = crate::database::schema::Schema::default(); + bypass_schema.obj.type_ = Some(crate::database::schema::SchemaTypeOrArray::Single(target_id.clone())); + let mut bypass_node = node.clone(); + bypass_node.schema = std::sync::Arc::new(bypass_schema); + return self.compile_node(bypass_node); + } + } + return self.compile_one_of(node); } // Just an inline object definition? @@ -226,77 +209,43 @@ impl<'a> Compiler<'a> { ) -> Result, String> { let mut select_args = Vec::new(); - if let Some(family_target) = node.schema.obj.family.as_ref() { - let family_prefix = family_target.rfind('.').map(|idx| &family_target[..idx]); - - let mut all_targets = vec![family_target.clone()]; - if let Some(descendants) = self.db.descendants.get(family_target) { - all_targets.extend(descendants.clone()); - } - - // Filter targets to EXACTLY match the family_target prefix - let mut final_targets = Vec::new(); - for target in all_targets { - let target_prefix = target.rfind('.').map(|idx| &target[..idx]); - if target_prefix == family_prefix { - final_targets.push(target); - } - } - - final_targets.sort(); - final_targets.dedup(); - - if final_targets.len() == 1 { - let variation = &final_targets[0]; - if let Some(target_schema) = self.db.schemas.get(variation) { - let mut bypass_node = node.clone(); - bypass_node.schema = std::sync::Arc::new(target_schema.clone()); - - let mut bypassed_args = self.compile_select_clause(r#type, table_aliases, bypass_node)?; - select_args.append(&mut bypassed_args); - } else { - return Err(format!("Could not find schema for variation {}", variation)); - } - } else { - let mut family_schemas = Vec::new(); - - for variation in &final_targets { - if let Some(target_schema) = self.db.schemas.get(variation) { - family_schemas.push(std::sync::Arc::new(target_schema.clone())); - } else { - return Err(format!( - "Could not find schema metadata for variation {}", - variation - )); - } - } - + if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() { let base_alias = table_aliases .get(&r#type.name) .cloned() .unwrap_or_else(|| node.parent_alias.to_string()); + + let disc = node.schema.obj.compiled_discriminator.get(); + if disc.is_none() { + return Ok(select_args); + } + + let options = node.schema.obj.compiled_options.get(); + println!("[DEBUG QUERYER] Evaluating node. Target family: {:?}, disc: {:?}, options: {:?}", node.schema.obj.family, disc, options); + if options.is_none() { + return Ok(select_args); + } + let options = options.unwrap(); + + if options.len() == 1 { + let target_id = options.values().next().unwrap(); + if let Some(target_schema) = self.db.schemas.get(target_id) { + let mut bypass_node = node.clone(); + bypass_node.schema = std::sync::Arc::new(target_schema.clone()); + let mut bypassed_args = self.compile_select_clause(r#type, table_aliases, bypass_node)?; + select_args.append(&mut bypassed_args); + return Ok(select_args); + } + } + select_args.push(format!("'id', {}.id", base_alias)); let mut case_node = node.clone(); case_node.parent_alias = base_alias.clone(); let arc_aliases = std::sync::Arc::new(table_aliases.clone()); case_node.parent_type_aliases = Some(arc_aliases); - let (case_sql, _) = self.compile_one_of(&family_schemas, case_node)?; - select_args.push(format!("'type', {}", case_sql)); - } - } else if let Some(one_of) = &node.schema.obj.one_of { - let base_alias = table_aliases - .get(&r#type.name) - .cloned() - .unwrap_or_else(|| node.parent_alias.to_string()); - select_args.push(format!("'id', {}.id", base_alias)); - let mut case_node = node.clone(); - case_node.parent_alias = base_alias.clone(); - let arc_aliases = std::sync::Arc::new(table_aliases.clone()); - case_node.parent_type_aliases = Some(arc_aliases); - - let (case_sql, _) = self.compile_one_of(one_of, case_node)?; - select_args.push(format!("'type', {}", case_sql)); + let (case_sql, _) = self.compile_one_of(case_node)?; + select_args.push(format!("'{}', {}", disc.unwrap(), case_sql)); } Ok(select_args) @@ -333,26 +282,28 @@ impl<'a> Compiler<'a> { fn compile_one_of( &mut self, - schemas: &[Arc], node: Node<'a>, ) -> Result<(String, String), String> { let mut case_statements = Vec::new(); + + let options = node.schema.obj.compiled_options.get().ok_or("Missing compiled options for polymorphism")?; + let disc = node.schema.obj.compiled_discriminator.get().ok_or("Missing compiled discriminator for polymorphism")?; + let type_col = if let Some(prop) = &node.property_name { - format!("{}_type", prop) + format!("{}_{}", prop, disc) } else { - "type".to_string() + disc.to_string() }; - for option_schema in schemas { - if let Some(base_type_name) = option_schema.obj.identifier() { - // Generate the nested SQL for this specific target type + for (disc_val, target_id) in options { + if let Some(target_schema) = self.db.schemas.get(target_id) { let mut child_node = node.clone(); - child_node.schema = std::sync::Arc::clone(option_schema); + child_node.schema = std::sync::Arc::new(target_schema.clone()); let (val_sql, _) = self.compile_node(child_node)?; case_statements.push(format!( "WHEN {}.{} = '{}' THEN ({})", - node.parent_alias, type_col, base_type_name, val_sql + node.parent_alias, type_col, disc_val, val_sql )); } } diff --git a/src/tests/fixtures.rs b/src/tests/fixtures.rs index a866285..73aafed 100644 --- a/src/tests/fixtures.rs +++ b/src/tests/fixtures.rs @@ -1439,6 +1439,18 @@ fn test_queryer_0_10() { crate::tests::runner::run_test_case(&path, 0, 10).unwrap(); } +#[test] +fn test_queryer_0_11() { + let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR")); + crate::tests::runner::run_test_case(&path, 0, 11).unwrap(); +} + +#[test] +fn test_queryer_0_12() { + let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR")); + crate::tests::runner::run_test_case(&path, 0, 12).unwrap(); +} + #[test] fn test_polymorphism_0_0() { let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR")); diff --git a/src/validator/rules/polymorphism.rs b/src/validator/rules/polymorphism.rs index feca0a5..80deddd 100644 --- a/src/validator/rules/polymorphism.rs +++ b/src/validator/rules/polymorphism.rs @@ -1,4 +1,3 @@ -use crate::database::schema::Schema; use crate::validator::context::ValidationContext; use crate::validator::error::ValidationError; use crate::validator::result::ValidationResult; @@ -29,30 +28,10 @@ impl<'a> ValidationContext<'a> { } } - if let Some(family_target) = &self.schema.family { - if let Some(descendants) = self.db.descendants.get(family_target) { - let mut candidates = Vec::new(); - - // Add the target base schema itself - if let Some(base_schema) = self.db.schemas.get(family_target) { - candidates.push(base_schema); - } - - // Add all descendants - for child_id in descendants { - if let Some(child_schema) = self.db.schemas.get(child_id) { - candidates.push(child_schema); - } - } - - // Use prefix from family string (e.g. `light.`) - let prefix = family_target - .rsplit_once('.') - .map(|(p, _)| format!("{}.", p)) - .unwrap_or_default(); - - if !self.validate_polymorph(&candidates, Some(&prefix), result)? { - return Ok(false); + if self.schema.family.is_some() { + if let Some(options) = self.schema.compiled_options.get() { + if let Some(disc) = self.schema.compiled_discriminator.get() { + return self.execute_polymorph(disc, options, result); } } } @@ -64,212 +43,43 @@ impl<'a> ValidationContext<'a> { &self, result: &mut ValidationResult, ) -> Result { - if let Some(ref one_of) = self.schema.one_of { - let mut candidates = Vec::new(); - for schema in one_of { - candidates.push(schema.as_ref()); + if let Some(one_of) = &self.schema.one_of { + if let Some(options) = self.schema.compiled_options.get() { + if let Some(disc) = self.schema.compiled_discriminator.get() { + return self.execute_polymorph(disc, options, result); + } } - if !self.validate_polymorph(&candidates, None, result)? { - return Ok(false); - } - } - Ok(true) - } - pub(crate) fn validate_polymorph( - &self, - candidates: &[&Schema], - family_prefix: Option<&str>, - result: &mut ValidationResult, - ) -> Result { - let mut passed_candidates: Vec<(Option, ValidationResult)> = Vec::new(); - let mut failed_candidates: Vec = Vec::new(); + // Native Draft2020-12 oneOf Evaluation Fallback + let mut valid_count = 0; + let mut final_successful_result = None; + let mut failed_candidates = Vec::new(); - // 1. O(1) Fast-Path Router & Extractor - let instance_type = self.instance.as_object().and_then(|o| o.get("type")).and_then(|t| t.as_str()); - let instance_kind = self.instance.as_object().and_then(|o| o.get("kind")).and_then(|k| k.as_str()); - - let mut viable_candidates = Vec::new(); - - for sub in candidates { - let _child_id = sub.identifier().unwrap_or_default(); - let mut can_match = true; - - if let Some(t) = instance_type { - // Fast Path 1: Pure Ad-Hoc Match (schema identifier == type) - // If it matches exactly, it's our golden candidate. Make all others non-viable manually? - // Wait, we loop through all and filter down. If exact match is found, we should ideally break and use ONLY that. - // Let's implement the logic safely. - - let mut exact_match_found = false; - - if let Some(schema_id) = &sub.id { - // Compute Vertical Exact Target (e.g. "person" or "light.person") - let exact_target = if let Some(prefix) = family_prefix { - format!("{}{}", prefix, t) - } else { - t.to_string() - }; - - // Fast Path 1 & 2: Vertical Exact Match - if schema_id == &exact_target { - if instance_kind.is_none() { - exact_match_found = true; - } - } - - // Fast Path 3: Horizontal Sibling Match (kind + . + type) - if let Some(k) = instance_kind { - let sibling_target = format!("{}.{}", k, t); - if schema_id == &sibling_target { - exact_match_found = true; - } - } - } - - if exact_match_found { - // We found an exact literal structural identity match! - // Wipe the existing viable_candidates and only yield this guy! - viable_candidates.clear(); - viable_candidates.push(*sub); - break; - } - - // Fast Path 4: Vertical Inheritance Fallback (Physical DB constraint) - if let Some(crate::database::schema::SchemaTypeOrArray::Single(t_ptr)) = &sub.type_ { - if !crate::database::schema::is_primitive_type(t_ptr) { - if let Some(base_type) = t_ptr.split('.').last() { - if let Some(type_def) = self.db.types.get(base_type) { - if !type_def.variations.contains(&t.to_string()) { - can_match = false; - } - } else { - if t_ptr != t { - can_match = false; - } - } - } - } - } - - // Fast Path 5: Explicit Schema JSON `const` values check - if can_match { - if let Some(props) = &sub.properties { - if let Some(type_prop) = props.get("type") { - if let Some(const_val) = &type_prop.const_ { - if let Some(const_str) = const_val.as_str() { - if const_str != t { - can_match = false; - } - } - } - } + for child_schema in one_of { + let derived = self.derive_for_schema(child_schema, false); + if let Ok(sub_res) = derived.validate_scoped() { + if sub_res.is_valid() { + valid_count += 1; + final_successful_result = Some(sub_res.clone()); + } else { + failed_candidates.push(sub_res); } } } - if can_match { - viable_candidates.push(*sub); - } - } - - println!("DEBUG VIABLE: {:?}", viable_candidates.iter().map(|s| s.id.clone()).collect::>()); - // 2. Evaluate Viable Candidates - // 2. Evaluate Viable Candidates - // Composition validation is natively handled directly via type compilation. - // The deprecated allOf JSON structure is no longer supported nor traversed. - for sub in viable_candidates.clone() { - let derived = self.derive_for_schema(sub, false); - let sub_res = derived.validate()?; - if sub_res.is_valid() { - passed_candidates.push((sub.id.clone(), sub_res)); - } else { - failed_candidates.push(sub_res); - } - } - for f in &failed_candidates { - println!(" - Failed candidate errors: {:?}", f.errors.iter().map(|e| e.code.clone()).collect::>()); - } - - if passed_candidates.len() == 1 { - result.merge(passed_candidates.pop().unwrap().1); - } else if passed_candidates.is_empty() { - // 3. Discriminator Pathing (Failure Analytics) - let type_path = self.join_path("type"); - - if instance_type.is_some() { - // Filter to candidates that didn't explicitly throw a CONST violation on `type` - let mut genuinely_failed = Vec::new(); - for res in &failed_candidates { - let rejected_type = res.errors.iter().any(|e| { - (e.code == "CONST_VIOLATED" || e.code == "ENUM_VIOLATED") && e.path == type_path - }); - if !rejected_type { - genuinely_failed.push(res.clone()); - } - } - - println!("DEBUG genuinely_failed len: {}", genuinely_failed.len()); - - if genuinely_failed.len() == 1 { - // Golden Type Match (1 candidate was structurally possible but failed property validation) - let sub_res = genuinely_failed.pop().unwrap(); - result.errors.extend(sub_res.errors); - result.evaluated_keys.extend(sub_res.evaluated_keys); - return Ok(false); - } else { - // Pure Ad-Hoc Union - result.errors.push(ValidationError { - code: if self.schema.family.is_some() { "NO_FAMILY_MATCH".to_string() } else { "NO_ONEOF_MATCH".to_string() }, - message: "Payload matches none of the required candidate sub-schemas".to_string(), + if valid_count == 1 { + if let Some(successful_res) = final_successful_result { + result.merge(successful_res); + } + return Ok(true); + } else if valid_count == 0 { + result.errors.push(ValidationError { + code: "NO_ONEOF_MATCH".to_string(), + message: "Payload matches none of the required candidate sub-schemas natively".to_string(), path: self.path.to_string(), - }); - - for sub_res in &failed_candidates { - result.evaluated_keys.extend(sub_res.evaluated_keys.clone()); - } - println!("DEBUG ELSE NO_FAMILY_MATCH RUNNING. Genuinely Failed len: {}", genuinely_failed.len()); - if viable_candidates.is_empty() { - if let Some(obj) = self.instance.as_object() { - result.evaluated_keys.extend(obj.keys().cloned()); - } - } - for sub_res in genuinely_failed { - for e in sub_res.errors { - if !result.errors.iter().any(|existing| existing.code == e.code && existing.path == e.path) { - result.errors.push(e); - } - } - } - return Ok(false); - } - } else { - // Instance missing type - // Instance missing type - let expects_type = viable_candidates.iter().any(|c| { - c.compiled_property_names.get().map_or(false, |props| props.contains(&"type".to_string())) - }); - - if expects_type { - result.errors.push(ValidationError { - code: "MISSING_TYPE".to_string(), - message: "Missing type discriminator. Unable to resolve polymorphic boundaries".to_string(), - path: self.path.to_string(), - }); - - for sub_res in failed_candidates { - result.evaluated_keys.extend(sub_res.evaluated_keys); - } - return Ok(false); - } else { - // Pure Ad-Hoc Union - result.errors.push(ValidationError { - code: if self.schema.family.is_some() { "NO_FAMILY_MATCH".to_string() } else { "NO_ONEOF_MATCH".to_string() }, - message: "Payload matches none of the required candidate sub-schemas".to_string(), - path: self.path.to_string(), - }); - - if let Some(first) = failed_candidates.first() { + }); + + if let Some(first) = failed_candidates.first() { let mut shared_errors = first.errors.clone(); for sub_res in failed_candidates.iter().skip(1) { shared_errors.retain(|e1| { @@ -281,26 +91,66 @@ impl<'a> ValidationContext<'a> { result.errors.push(e); } } - } - - for sub_res in failed_candidates { - result.evaluated_keys.extend(sub_res.evaluated_keys); - } - return Ok(false); - } - + } + + return Ok(false); + } else { + result.errors.push(ValidationError { + code: "AMBIGUOUS_POLYMORPHIC_MATCH".to_string(), + message: "Matches multiple polymorphic candidates inextricably natively".to_string(), + path: self.path.to_string(), + }); + return Ok(false); } - } else { - result.errors.push(ValidationError { - code: "AMBIGUOUS_POLYMORPHIC_MATCH".to_string(), - message: "Matches multiple polymorphic candidates inextricably".to_string(), - path: self.path.to_string(), - }); } - Ok(true) } + pub(crate) fn execute_polymorph( + &self, + disc: &str, + options: &std::collections::BTreeMap, + result: &mut ValidationResult, + ) -> Result { + // 1. O(1) Fast-Path Router & Extractor + let instance_val = self.instance.as_object().and_then(|o| o.get(disc)).and_then(|t| t.as_str()); + + if let Some(val) = instance_val { + result.evaluated_keys.insert(disc.to_string()); + + if let Some(target_id) = options.get(val) { + if let Some(target_schema) = self.db.schemas.get(target_id) { + let derived = self.derive_for_schema(target_schema, false); + let sub_res = derived.validate()?; + let is_valid = sub_res.is_valid(); + result.merge(sub_res); + return Ok(is_valid); + } else { + result.errors.push(ValidationError { + code: "MISSING_COMPILED_SCHEMA".to_string(), + message: format!("Polymorphic router target '{}' does not exist in the database schemas map", target_id), + path: self.path.to_string(), + }); + return Ok(false); + } + } else { + result.errors.push(ValidationError { + code: if self.schema.family.is_some() { "NO_FAMILY_MATCH".to_string() } else { "NO_ONEOF_MATCH".to_string() }, + message: format!("Payload provided discriminator {}='{}' which matches none of the required candidate sub-schemas", disc, val), + path: self.path.to_string(), + }); + return Ok(false); + } + } else { + result.errors.push(ValidationError { + code: "MISSING_TYPE".to_string(), + message: format!("Missing '{}' discriminator. Unable to resolve polymorphic boundaries", disc), + path: self.path.to_string(), + }); + return Ok(false); + } + } + pub(crate) fn validate_type_inheritance( &self, result: &mut ValidationResult,