diff --git a/GEMINI.md b/GEMINI.md index 3822455..dcc649c 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -43,7 +43,7 @@ JSPG implements specific extensions to the Draft 2020-12 standard to support the #### A. Polymorphism & Referencing (`$ref`, `$family`, and Native Types) * **Native Type Discrimination (`variations`)**: Schemas defined inside a Postgres `type` are Entities. The validator securely and implicitly manages their `"type"` property. If an entity inherits from `user`, incoming JSON can safely define `{"type": "person"}` without errors, thanks to `compiled_variations` inheritance. * **Structural Inheritance & Viral Infection (`$ref`)**: `$ref` is used exclusively for structural inheritance, *never* for union creation. A Punc request schema that `$ref`s an Entity virally inherits all physical database polymorphism rules for that target. -* **Shape Polymorphism (`$family`)**: Auto-expands polymorphic API lists based on an abstract Descendants Graph. If `{"$family": "widget"}` is used, JSPG evaluates the JSON against every schema that `$ref`s widget. +* **Shape Polymorphism (`$family`)**: Auto-expands polymorphic API lists based on an abstract **Descendants Graph**. If `{"$family": "widget"}` is used, the Validator dynamically identifies *every* schema in the registry that `$ref`s `widget` (e.g., `stock.widget`, `task.widget`) and evaluates the JSON against all of them. * **Strict Matches & Depth Heuristic**: Polymorphic structures MUST match exactly **one** schema permutation. If multiple inherited struct permutations pass, JSPG applies the **Depth Heuristic Tie-Breaker**, selecting the candidate deepest in the inheritance tree. #### B. Dot-Notation Schema Resolution & Database Mapping @@ -103,6 +103,10 @@ The Queryer transforms Postgres into a pre-compiled Semantic Query Engine via th * **Array Inclusion**: `{"$in": [values]}`, `{"$nin": [values]}` use native `jsonb_array_elements_text()` bindings to enforce `IN` and `NOT IN` logic without runtime SQL injection risks. * **Text Matching (ILIKE)**: Evaluates `$eq` or `$ne` against string fields containing the `%` character natively into Postgres `ILIKE` and `NOT ILIKE` partial substring matches. * **Type Casting**: Safely resolves dynamic combinations by casting values instantly into the physical database types mapped in the schema (e.g. parsing `uuid` bindings to `::uuid`, formatting DateTimes to `::timestamptz`, and numbers to `::numeric`). +* **Polymorphic SQL Generation (`$family`)**: Compiles `$family` properties by analyzing the **Physical Database Variations**, *not* the schema descendants. + * **The Dot Convention**: When a schema requests `$family: "target.schema"`, the compiler extracts the base type (e.g. `schema`) and looks up its Physical Table definition. + * **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into `JOIN`s for each variation. + * **Single-Table Bypass**: If the Physical Table is a leaf node with only one variation (e.g. `person` has variations `["person"]`), the compiler cleanly bypasses `CASE` generation and compiles a simple `SELECT` across the base table, as all schema extensions (e.g. `light.person`, `full.person`) are guaranteed to reside in the exact same physical row. ### The Stem Engine diff --git a/fixtures/queryer.json b/fixtures/queryer.json index 1c18cf9..d654694 100644 --- a/fixtures/queryer.json +++ b/fixtures/queryer.json @@ -8,7 +8,16 @@ "schemas": [ { "$id": "get_entities.response", - "$family": "entity" + "$family": "organization" + } + ] + }, + { + "name": "get_persons", + "schemas": [ + { + "$id": "get_persons.response", + "$family": "base.person" } ] } @@ -135,6 +144,7 @@ ], "variations": [ "address", + "bot", "contact", "email_address", "entity", @@ -146,10 +156,108 @@ "relationship" ] }, + { + "name": "organization", + "hierarchy": [ + "organization", + "entity" + ], + "fields": [ + "id", + "type", + "name", + "archived", + "created_at" + ], + "grouped_fields": { + "entity": [ + "id", + "type", + "name", + "archived", + "created_at" + ], + "organization": [] + }, + "field_types": { + "id": "uuid", + "type": "text", + "archived": "boolean", + "name": "text", + "created_at": "timestamptz" + }, + "lookup_fields": [ + "id" + ], + "null_fields": [], + "default_fields": [ + "id", + "type", + "created_at", + "archived" + ], + "variations": [ + "bot", + "organization", + "person" + ] + }, + { + "name": "bot", + "hierarchy": [ + "bot", + "organization", + "entity" + ], + "fields": [ + "token", + "id", + "type", + "name", + "archived", + "created_at" + ], + "grouped_fields": { + "entity": [ + "id", + "type", + "name", + "archived", + "created_at" + ], + "organization": [], + "bot": [ + "token" + ] + }, + "field_types": { + "id": "uuid", + "type": "text", + "archived": "boolean", + "name": "text", + "token": "text", + "created_at": "timestamptz" + }, + "schemas": [ + { + "$id": "bot", + "$ref": "organization", + "properties": { + "token": { + "type": "string" + } + } + } + ], + "variations": [ + "bot" + ] + }, { "name": "person", "hierarchy": [ "person", + "organization", "entity" ], "fields": [ @@ -170,6 +278,7 @@ "archived", "created_at" ], + "organization": [], "person": [ "first_name", "last_name", @@ -189,7 +298,7 @@ "schemas": [ { "$id": "base.person", - "$ref": "entity", + "$ref": "organization", "properties": { "first_name": { "type": "string" @@ -202,6 +311,11 @@ } } }, + { + "$id": "light.person", + "$ref": "base.person", + "properties": {} + }, { "$id": "full.person", "$ref": "base.person", @@ -695,50 +809,6 @@ "variations": [ "order_line" ] - }, - { - "name": "organization", - "hierarchy": [ - "organization", - "entity" - ], - "fields": [ - "id", - "type", - "name", - "archived", - "created_at" - ], - "grouped_fields": { - "entity": [ - "id", - "type", - "name", - "archived", - "created_at" - ], - "organization": [] - }, - "field_types": { - "id": "uuid", - "type": "text", - "archived": "boolean", - "name": "text", - "created_at": "timestamptz" - }, - "lookup_fields": [ - "id" - ], - "null_fields": [], - "default_fields": [ - "id", - "type", - "created_at", - "archived" - ], - "variations": [ - "organization" - ] } ], "schemas": [ @@ -753,6 +823,12 @@ "$ref": "entity", "properties": {} }, + { + "$id": "bot", + "type": "object", + "$ref": "bot", + "properties": {} + }, { "$id": "person", "type": "object", @@ -771,13 +847,13 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'archived', t1_obj_t1.archived,", - " 'created_at', t1_obj_t1.created_at,", - " 'id', t1_obj_t1.id,", - " 'name', t1_obj_t1.name,", - " 'type', t1_obj_t1.type)", - "FROM agreego.entity t1_obj_t1", - "WHERE NOT t1_obj_t1.archived)" + " 'archived', entity_1.archived,", + " 'created_at', entity_1.created_at,", + " 'id', entity_1.id,", + " 'name', entity_1.name,", + " 'type', entity_1.type)", + "FROM agreego.entity entity_1", + "WHERE NOT entity_1.archived)" ] ] } @@ -792,13 +868,13 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'archived', t1_obj_t1.archived,", - " 'created_at', t1_obj_t1.created_at,", - " 'id', t1_obj_t1.id,", - " 'name', t1_obj_t1.name,", - " 'type', t1_obj_t1.type)", - "FROM agreego.entity t1_obj_t1", - "WHERE NOT t1_obj_t1.archived)" + " 'archived', entity_1.archived,", + " 'created_at', entity_1.created_at,", + " 'id', entity_1.id,", + " 'name', entity_1.name,", + " 'type', entity_1.type)", + "FROM agreego.entity entity_1", + "WHERE NOT entity_1.archived)" ] ] } @@ -852,35 +928,35 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'archived', t1_obj_t1.archived,", - " 'created_at', t1_obj_t1.created_at,", - " 'id', t1_obj_t1.id,", - " 'name', t1_obj_t1.name,", - " 'type', t1_obj_t1.type", + " 'archived', entity_1.archived,", + " 'created_at', entity_1.created_at,", + " 'id', entity_1.id,", + " 'name', entity_1.name,", + " 'type', entity_1.type", ")", - "FROM agreego.entity t1_obj_t1", + "FROM agreego.entity entity_1", "WHERE", - " NOT t1_obj_t1.archived", - " AND t1_obj_t1.archived = ($1#>>'{}')::boolean", - " AND t1_obj_t1.archived != ($2#>>'{}')::boolean", - " AND t1_obj_t1.created_at = ($3#>>'{}')::timestamptz", - " AND t1_obj_t1.created_at > ($4#>>'{}')::timestamptz", - " AND t1_obj_t1.created_at >= ($5#>>'{}')::timestamptz", - " AND t1_obj_t1.created_at < ($6#>>'{}')::timestamptz", - " AND t1_obj_t1.created_at <= ($7#>>'{}')::timestamptz", - " AND t1_obj_t1.created_at != ($8#>>'{}')::timestamptz", - " AND t1_obj_t1.id = ($9#>>'{}')::uuid", - " AND t1_obj_t1.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($10#>>'{}')::jsonb))", - " AND t1_obj_t1.id != ($11#>>'{}')::uuid", - " AND t1_obj_t1.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($12#>>'{}')::jsonb))", - " AND t1_obj_t1.name ILIKE $13#>>'{}'", - " AND t1_obj_t1.name > ($14#>>'{}')", - " AND t1_obj_t1.name >= ($15#>>'{}')", - " AND t1_obj_t1.name IN (SELECT value FROM jsonb_array_elements_text(($16#>>'{}')::jsonb))", - " AND t1_obj_t1.name < ($17#>>'{}')", - " AND t1_obj_t1.name <= ($18#>>'{}')", - " AND t1_obj_t1.name NOT ILIKE $19#>>'{}'", - " AND t1_obj_t1.name NOT IN (SELECT value FROM jsonb_array_elements_text(($20#>>'{}')::jsonb))", + " NOT entity_1.archived", + " AND entity_1.archived = ($1#>>'{}')::boolean", + " AND entity_1.archived != ($2#>>'{}')::boolean", + " AND entity_1.created_at = ($3#>>'{}')::timestamptz", + " AND entity_1.created_at > ($4#>>'{}')::timestamptz", + " AND entity_1.created_at >= ($5#>>'{}')::timestamptz", + " AND entity_1.created_at < ($6#>>'{}')::timestamptz", + " AND entity_1.created_at <= ($7#>>'{}')::timestamptz", + " AND entity_1.created_at != ($8#>>'{}')::timestamptz", + " AND entity_1.id = ($9#>>'{}')::uuid", + " AND entity_1.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($10#>>'{}')::jsonb))", + " AND entity_1.id != ($11#>>'{}')::uuid", + " AND entity_1.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($12#>>'{}')::jsonb))", + " AND entity_1.name ILIKE $13#>>'{}'", + " AND entity_1.name > ($14#>>'{}')", + " AND entity_1.name >= ($15#>>'{}')", + " AND entity_1.name IN (SELECT value FROM jsonb_array_elements_text(($16#>>'{}')::jsonb))", + " AND entity_1.name < ($17#>>'{}')", + " AND entity_1.name <= ($18#>>'{}')", + " AND entity_1.name NOT ILIKE $19#>>'{}'", + " AND entity_1.name NOT IN (SELECT value FROM jsonb_array_elements_text(($20#>>'{}')::jsonb))", ")" ] ] @@ -895,17 +971,18 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'age', t1_obj_t1.age,", - " 'archived', t1_obj_t2.archived,", - " 'created_at', t1_obj_t2.created_at,", - " 'first_name', t1_obj_t1.first_name,", - " 'id', t1_obj_t2.id,", - " 'last_name', t1_obj_t1.last_name,", - " 'name', t1_obj_t2.name,", - " 'type', t1_obj_t2.type)", - "FROM agreego.person t1_obj_t1", - "JOIN agreego.entity t1_obj_t2 ON t1_obj_t2.id = t1_obj_t1.id", - "WHERE NOT t1_obj_t2.archived)" + " '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', entity_3.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)" ] ] } @@ -921,169 +998,163 @@ "(SELECT jsonb_build_object(", " 'addresses',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_addresses_t3.archived,", - " 'created_at', t1_obj_t2_addresses_t3.created_at,", - " 'id', t1_obj_t2_addresses_t3.id,", - " 'is_primary', t1_obj_t2_addresses_t1.is_primary,", - " 'name', t1_obj_t2_addresses_t3.name,", + " 'archived', entity_6.archived,", + " 'created_at', entity_6.created_at,", + " 'id', entity_6.id,", + " 'is_primary', contact_4.is_primary,", + " 'name', entity_6.name,", " 'target',", " (SELECT jsonb_build_object(", - " 'archived', t1_obj_t2_addresses_t3_target_t2.archived,", - " 'city', t1_obj_t2_addresses_t3_target_t1.city,", - " 'created_at', t1_obj_t2_addresses_t3_target_t2.created_at,", - " 'id', t1_obj_t2_addresses_t3_target_t2.id,", - " 'name', t1_obj_t2_addresses_t3_target_t2.name,", - " 'type', t1_obj_t2_addresses_t3_target_t2.type", - " )", - " FROM agreego.address t1_obj_t2_addresses_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_addresses_t3_target_t2 ON t1_obj_t2_addresses_t3_target_t2.id = t1_obj_t2_addresses_t3_target_t1.id", - " WHERE", - " NOT t1_obj_t2_addresses_t3_target_t2.archived", - " AND t1_obj_t2_addresses_t3_target_t1.id = t1_obj_t2_addresses_t3.target_id", - " ),", - " 'type', t1_obj_t2_addresses_t3.type", + " 'archived', entity_8.archived,", + " 'city', address_7.city,", + " 'created_at', entity_8.created_at,", + " 'id', entity_8.id,", + " 'name', entity_8.name,", + " 'type', entity_8.type", + " )", + " FROM agreego.address address_7", + " JOIN agreego.entity entity_8 ON entity_8.id = address_7.id", + " WHERE", + " NOT entity_8.archived", + " AND relationship_5.target_id = address_7.id),", + " 'type', entity_6.type", " )), '[]'::jsonb)", - " FROM agreego.contact t1_obj_t2_addresses_t1", - " JOIN agreego.relationship t1_obj_t2_addresses_t2 ON t1_obj_t2_addresses_t2.id = t1_obj_t2_addresses_t1.id", - " JOIN agreego.entity t1_obj_t2_addresses_t3 ON t1_obj_t2_addresses_t3.id = t1_obj_t2_addresses_t2.id", + " FROM agreego.contact contact_4", + " JOIN agreego.relationship relationship_5 ON relationship_5.id = contact_4.id", + " JOIN agreego.entity entity_6 ON entity_6.id = relationship_5.id", " WHERE", - " NOT t1_obj_t2_addresses_t3.archived", - " AND t1_obj_t2_addresses_t1.parent_id = t1_obj_t2.id),", - " 'age', t1_obj_t1.age,", - " 'archived', t1_obj_t2.archived,", + " NOT entity_6.archived", + " AND contact_4.parent_id = entity_3.id),", + " 'age', person_1.age,", + " 'archived', entity_3.archived,", " 'contacts',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_contacts_t3.archived,", - " 'created_at', t1_obj_t2_contacts_t3.created_at,", - " 'id', t1_obj_t2_contacts_t3.id,", - " 'is_primary', t1_obj_t2_contacts_t1.is_primary,", - " 'name', t1_obj_t2_contacts_t3.name,", + " 'archived', entity_11.archived,", + " 'created_at', entity_11.created_at,", + " 'id', entity_11.id,", + " 'is_primary', contact_9.is_primary,", + " 'name', entity_11.name,", " 'target', CASE", - " WHEN t1_obj_t2_contacts_t3.target_type = 'phone_number' THEN", + " WHEN entity_11.target_type = 'address' THEN", " ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t2_contacts_t3_target_t2.archived,", - " 'created_at', t1_obj_t2_contacts_t3_target_t2.created_at,", - " 'id', t1_obj_t2_contacts_t3_target_t2.id,", - " 'name', t1_obj_t2_contacts_t3_target_t2.name,", - " 'number', t1_obj_t2_contacts_t3_target_t1.number,", - " 'type', t1_obj_t2_contacts_t3_target_t2.type", + " 'archived', entity_17.archived,", + " 'city', address_16.city,", + " 'created_at', entity_17.created_at,", + " 'id', entity_17.id,", + " 'name', entity_17.name,", + " 'type', entity_17.type", " )", - " FROM agreego.phone_number t1_obj_t2_contacts_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_contacts_t3_target_t2 ON t1_obj_t2_contacts_t3_target_t2.id = t1_obj_t2_contacts_t3_target_t1.id", + " FROM agreego.address address_16", + " JOIN agreego.entity entity_17 ON entity_17.id = address_16.id", " WHERE", - " NOT t1_obj_t2_contacts_t3_target_t2.archived", - " AND t1_obj_t2_contacts_t3_target_t1.id = t1_obj_t2_contacts_t3.target_id", - " ))", - " WHEN t1_obj_t2_contacts_t3.target_type = 'email_address' THEN", + " NOT entity_17.archived", + " AND relationship_10.target_id = address_16.id))", + " WHEN entity_11.target_type = 'email_address' THEN", " ((SELECT jsonb_build_object(", - " 'address', t1_obj_t2_contacts_t3_target_t1.address,", - " 'archived', t1_obj_t2_contacts_t3_target_t2.archived,", - " 'created_at', t1_obj_t2_contacts_t3_target_t2.created_at,", - " 'id', t1_obj_t2_contacts_t3_target_t2.id,", - " 'name', t1_obj_t2_contacts_t3_target_t2.name,", - " 'type', t1_obj_t2_contacts_t3_target_t2.type", + " 'address', email_address_14.address,", + " 'archived', entity_15.archived,", + " 'created_at', entity_15.created_at,", + " 'id', entity_15.id,", + " 'name', entity_15.name,", + " 'type', entity_15.type", " )", - " FROM agreego.email_address t1_obj_t2_contacts_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_contacts_t3_target_t2 ON t1_obj_t2_contacts_t3_target_t2.id = t1_obj_t2_contacts_t3_target_t1.id", + " FROM agreego.email_address email_address_14", + " JOIN agreego.entity entity_15 ON entity_15.id = email_address_14.id", " WHERE", - " NOT t1_obj_t2_contacts_t3_target_t2.archived", - " AND t1_obj_t2_contacts_t3_target_t1.id = t1_obj_t2_contacts_t3.target_id", - " ))", - " WHEN t1_obj_t2_contacts_t3.target_type = 'address' THEN", + " NOT entity_15.archived", + " AND relationship_10.target_id = email_address_14.id))", + " WHEN entity_11.target_type = 'phone_number' THEN", " ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t2_contacts_t3_target_t2.archived,", - " 'city', t1_obj_t2_contacts_t3_target_t1.city,", - " 'created_at', t1_obj_t2_contacts_t3_target_t2.created_at,", - " 'id', t1_obj_t2_contacts_t3_target_t2.id,", - " 'name', t1_obj_t2_contacts_t3_target_t2.name,", - " 'type', t1_obj_t2_contacts_t3_target_t2.type", + " 'archived', entity_13.archived,", + " 'created_at', entity_13.created_at,", + " 'id', entity_13.id,", + " 'name', entity_13.name,", + " 'number', phone_number_12.number,", + " 'type', entity_13.type", " )", - " FROM agreego.address t1_obj_t2_contacts_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_contacts_t3_target_t2 ON t1_obj_t2_contacts_t3_target_t2.id = t1_obj_t2_contacts_t3_target_t1.id", + " FROM agreego.phone_number phone_number_12", + " JOIN agreego.entity entity_13 ON entity_13.id = phone_number_12.id", " WHERE", - " NOT t1_obj_t2_contacts_t3_target_t2.archived", - " AND t1_obj_t2_contacts_t3_target_t1.id = t1_obj_t2_contacts_t3.target_id", - " ))", - " ELSE NULL", - " END,", - " 'type', t1_obj_t2_contacts_t3.type", + " NOT entity_13.archived", + " AND relationship_10.target_id = phone_number_12.id))", + " ELSE NULL END,", + " 'type', entity_11.type", " )), '[]'::jsonb)", - " FROM agreego.contact t1_obj_t2_contacts_t1", - " JOIN agreego.relationship t1_obj_t2_contacts_t2 ON t1_obj_t2_contacts_t2.id = t1_obj_t2_contacts_t1.id", - " JOIN agreego.entity t1_obj_t2_contacts_t3 ON t1_obj_t2_contacts_t3.id = t1_obj_t2_contacts_t2.id", + " 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 t1_obj_t2_contacts_t3.archived", - " AND t1_obj_t2_contacts_t1.parent_id = t1_obj_t2.id),", - " 'created_at', t1_obj_t2.created_at,", + " NOT entity_11.archived", + " AND contact_9.parent_id = entity_3.id),", + " 'created_at', entity_3.created_at,", " 'email_addresses',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_email_addresses_t3.archived,", - " 'created_at', t1_obj_t2_email_addresses_t3.created_at,", - " 'id', t1_obj_t2_email_addresses_t3.id,", - " 'is_primary', t1_obj_t2_email_addresses_t1.is_primary,", - " 'name', t1_obj_t2_email_addresses_t3.name,", + " 'archived', entity_20.archived,", + " 'created_at', entity_20.created_at,", + " 'id', entity_20.id,", + " 'is_primary', contact_18.is_primary,", + " 'name', entity_20.name,", " 'target',", " (SELECT jsonb_build_object(", - " 'address', t1_obj_t2_email_addresses_t3_target_t1.address,", - " 'archived', t1_obj_t2_email_addresses_t3_target_t2.archived,", - " 'created_at', t1_obj_t2_email_addresses_t3_target_t2.created_at,", - " 'id', t1_obj_t2_email_addresses_t3_target_t2.id,", - " 'name', t1_obj_t2_email_addresses_t3_target_t2.name,", - " 'type', t1_obj_t2_email_addresses_t3_target_t2.type", - " )", - " FROM agreego.email_address t1_obj_t2_email_addresses_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_email_addresses_t3_target_t2 ON t1_obj_t2_email_addresses_t3_target_t2.id = t1_obj_t2_email_addresses_t3_target_t1.id", - " WHERE", - " NOT t1_obj_t2_email_addresses_t3_target_t2.archived", - " AND t1_obj_t2_email_addresses_t3_target_t1.id = t1_obj_t2_email_addresses_t3.target_id", - " ),", - " 'type', t1_obj_t2_email_addresses_t3.type", + " 'address', email_address_21.address,", + " 'archived', entity_22.archived,", + " 'created_at', entity_22.created_at,", + " 'id', entity_22.id,", + " 'name', entity_22.name,", + " 'type', entity_22.type", + " )", + " FROM agreego.email_address email_address_21", + " JOIN agreego.entity entity_22 ON entity_22.id = email_address_21.id", + " WHERE", + " NOT entity_22.archived", + " AND relationship_19.target_id = email_address_21.id),", + " 'type', entity_20.type", " )), '[]'::jsonb)", - " FROM agreego.contact t1_obj_t2_email_addresses_t1", - " JOIN agreego.relationship t1_obj_t2_email_addresses_t2 ON t1_obj_t2_email_addresses_t2.id = t1_obj_t2_email_addresses_t1.id", - " JOIN agreego.entity t1_obj_t2_email_addresses_t3 ON t1_obj_t2_email_addresses_t3.id = t1_obj_t2_email_addresses_t2.id", + " FROM agreego.contact contact_18", + " JOIN agreego.relationship relationship_19 ON relationship_19.id = contact_18.id", + " JOIN agreego.entity entity_20 ON entity_20.id = relationship_19.id", " WHERE", - " NOT t1_obj_t2_email_addresses_t3.archived", - " AND t1_obj_t2_email_addresses_t1.parent_id = t1_obj_t2.id),", - " 'first_name', t1_obj_t1.first_name,", - " 'id', t1_obj_t2.id,", - " 'last_name', t1_obj_t1.last_name,", - " 'name', t1_obj_t2.name,", + " NOT entity_20.archived", + " AND contact_18.parent_id = entity_3.id),", + " 'first_name', person_1.first_name,", + " 'id', entity_3.id,", + " 'last_name', person_1.last_name,", + " 'name', entity_3.name,", " 'phone_numbers',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_phone_numbers_t3.archived,", - " 'created_at', t1_obj_t2_phone_numbers_t3.created_at,", - " 'id', t1_obj_t2_phone_numbers_t3.id,", - " 'is_primary', t1_obj_t2_phone_numbers_t1.is_primary,", - " 'name', t1_obj_t2_phone_numbers_t3.name,", + " 'archived', entity_25.archived,", + " 'created_at', entity_25.created_at,", + " 'id', entity_25.id,", + " 'is_primary', contact_23.is_primary,", + " 'name', entity_25.name,", " 'target',", " (SELECT jsonb_build_object(", - " 'archived', t1_obj_t2_phone_numbers_t3_target_t2.archived,", - " 'created_at', t1_obj_t2_phone_numbers_t3_target_t2.created_at,", - " 'id', t1_obj_t2_phone_numbers_t3_target_t2.id,", - " 'name', t1_obj_t2_phone_numbers_t3_target_t2.name,", - " 'number', t1_obj_t2_phone_numbers_t3_target_t1.number,", - " 'type', t1_obj_t2_phone_numbers_t3_target_t2.type", - " )", - " FROM agreego.phone_number t1_obj_t2_phone_numbers_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_phone_numbers_t3_target_t2 ON t1_obj_t2_phone_numbers_t3_target_t2.id = t1_obj_t2_phone_numbers_t3_target_t1.id", - " WHERE", - " NOT t1_obj_t2_phone_numbers_t3_target_t2.archived", - " AND t1_obj_t2_phone_numbers_t3_target_t1.id = t1_obj_t2_phone_numbers_t3.target_id", - " ),", - " 'type', t1_obj_t2_phone_numbers_t3.type", + " 'archived', entity_27.archived,", + " 'created_at', entity_27.created_at,", + " 'id', entity_27.id,", + " 'name', entity_27.name,", + " 'number', phone_number_26.number,", + " 'type', entity_27.type", + " )", + " FROM agreego.phone_number phone_number_26", + " JOIN agreego.entity entity_27 ON entity_27.id = phone_number_26.id", + " WHERE", + " NOT entity_27.archived", + " AND relationship_24.target_id = phone_number_26.id),", + " 'type', entity_25.type", " )), '[]'::jsonb)", - " FROM agreego.contact t1_obj_t2_phone_numbers_t1", - " JOIN agreego.relationship t1_obj_t2_phone_numbers_t2 ON t1_obj_t2_phone_numbers_t2.id = t1_obj_t2_phone_numbers_t1.id", - " JOIN agreego.entity t1_obj_t2_phone_numbers_t3 ON t1_obj_t2_phone_numbers_t3.id = t1_obj_t2_phone_numbers_t2.id", + " 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 t1_obj_t2_phone_numbers_t3.archived", - " AND t1_obj_t2_phone_numbers_t1.parent_id = t1_obj_t2.id),", - " 'type', t1_obj_t2.type", + " NOT entity_25.archived", + " AND contact_23.parent_id = entity_3.id),", + " 'type', entity_3.type", ")", - "FROM agreego.person t1_obj_t1", - "JOIN agreego.entity t1_obj_t2 ON t1_obj_t2.id = t1_obj_t1.id", - "WHERE NOT t1_obj_t2.archived)" + "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)" ] ] } @@ -1164,203 +1235,196 @@ "(SELECT jsonb_build_object(", " 'addresses',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_addresses_t3.archived,", - " 'created_at', t1_obj_t2_addresses_t3.created_at,", - " 'id', t1_obj_t2_addresses_t3.id,", - " 'is_primary', t1_obj_t2_addresses_t1.is_primary,", - " 'name', t1_obj_t2_addresses_t3.name,", + " 'archived', entity_6.archived,", + " 'created_at', entity_6.created_at,", + " 'id', entity_6.id,", + " 'is_primary', contact_4.is_primary,", + " 'name', entity_6.name,", " 'target',", " (SELECT jsonb_build_object(", - " 'archived', t1_obj_t2_addresses_t3_target_t2.archived,", - " 'city', t1_obj_t2_addresses_t3_target_t1.city,", - " 'created_at', t1_obj_t2_addresses_t3_target_t2.created_at,", - " 'id', t1_obj_t2_addresses_t3_target_t2.id,", - " 'name', t1_obj_t2_addresses_t3_target_t2.name,", - " 'type', t1_obj_t2_addresses_t3_target_t2.type", - " )", - " FROM agreego.address t1_obj_t2_addresses_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_addresses_t3_target_t2 ON t1_obj_t2_addresses_t3_target_t2.id = t1_obj_t2_addresses_t3_target_t1.id", - " WHERE", - " NOT t1_obj_t2_addresses_t3_target_t2.archived", - " AND t1_obj_t2_addresses_t3_target_t1.id = t1_obj_t2_addresses_t3.target_id", - " ),", - " 'type', t1_obj_t2_addresses_t3.type", + " 'archived', entity_8.archived,", + " 'city', address_7.city,", + " 'created_at', entity_8.created_at,", + " 'id', entity_8.id,", + " 'name', entity_8.name,", + " 'type', entity_8.type", + " )", + " FROM agreego.address address_7", + " JOIN agreego.entity entity_8 ON entity_8.id = address_7.id", + " WHERE", + " NOT entity_8.archived", + " AND relationship_5.target_id = address_7.id),", + " 'type', entity_6.type", " )), '[]'::jsonb)", - " FROM agreego.contact t1_obj_t2_addresses_t1", - " JOIN agreego.relationship t1_obj_t2_addresses_t2 ON t1_obj_t2_addresses_t2.id = t1_obj_t2_addresses_t1.id", - " JOIN agreego.entity t1_obj_t2_addresses_t3 ON t1_obj_t2_addresses_t3.id = t1_obj_t2_addresses_t2.id", + " FROM agreego.contact contact_4", + " JOIN agreego.relationship relationship_5 ON relationship_5.id = contact_4.id", + " JOIN agreego.entity entity_6 ON entity_6.id = relationship_5.id", " WHERE", - " NOT t1_obj_t2_addresses_t3.archived", - " AND t1_obj_t2_addresses_t1.parent_id = t1_obj_t2.id),", - " 'age', t1_obj_t1.age,", - " 'archived', t1_obj_t2.archived,", + " NOT entity_6.archived", + " AND contact_4.parent_id = entity_3.id),", + " 'age', person_1.age,", + " 'archived', entity_3.archived,", " 'contacts',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_contacts_t3.archived,", - " 'created_at', t1_obj_t2_contacts_t3.created_at,", - " 'id', t1_obj_t2_contacts_t3.id,", - " 'is_primary', t1_obj_t2_contacts_t1.is_primary,", - " 'name', t1_obj_t2_contacts_t3.name,", + " 'archived', entity_11.archived,", + " 'created_at', entity_11.created_at,", + " 'id', entity_11.id,", + " 'is_primary', contact_9.is_primary,", + " 'name', entity_11.name,", " 'target', CASE", - " WHEN t1_obj_t2_contacts_t3.target_type = 'phone_number' THEN", + " WHEN entity_11.target_type = 'address' THEN", " ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t2_contacts_t3_target_t2.archived,", - " 'created_at', t1_obj_t2_contacts_t3_target_t2.created_at,", - " 'id', t1_obj_t2_contacts_t3_target_t2.id,", - " 'name', t1_obj_t2_contacts_t3_target_t2.name,", - " 'number', t1_obj_t2_contacts_t3_target_t1.number,", - " 'type', t1_obj_t2_contacts_t3_target_t2.type", + " 'archived', entity_17.archived,", + " 'city', address_16.city,", + " 'created_at', entity_17.created_at,", + " 'id', entity_17.id,", + " 'name', entity_17.name,", + " 'type', entity_17.type", " )", - " FROM agreego.phone_number t1_obj_t2_contacts_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_contacts_t3_target_t2 ON t1_obj_t2_contacts_t3_target_t2.id = t1_obj_t2_contacts_t3_target_t1.id", + " FROM agreego.address address_16", + " JOIN agreego.entity entity_17 ON entity_17.id = address_16.id", " WHERE", - " NOT t1_obj_t2_contacts_t3_target_t2.archived", - " AND t1_obj_t2_contacts_t3_target_t1.id = t1_obj_t2_contacts_t3.target_id", - " ))", - " WHEN t1_obj_t2_contacts_t3.target_type = 'email_address' THEN", + " NOT entity_17.archived", + " AND relationship_10.target_id = address_16.id))", + " WHEN entity_11.target_type = 'email_address' THEN", " ((SELECT jsonb_build_object(", - " 'address', t1_obj_t2_contacts_t3_target_t1.address,", - " 'archived', t1_obj_t2_contacts_t3_target_t2.archived,", - " 'created_at', t1_obj_t2_contacts_t3_target_t2.created_at,", - " 'id', t1_obj_t2_contacts_t3_target_t2.id,", - " 'name', t1_obj_t2_contacts_t3_target_t2.name,", - " 'type', t1_obj_t2_contacts_t3_target_t2.type", + " 'address', email_address_14.address,", + " 'archived', entity_15.archived,", + " 'created_at', entity_15.created_at,", + " 'id', entity_15.id,", + " 'name', entity_15.name,", + " 'type', entity_15.type", " )", - " FROM agreego.email_address t1_obj_t2_contacts_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_contacts_t3_target_t2 ON t1_obj_t2_contacts_t3_target_t2.id = t1_obj_t2_contacts_t3_target_t1.id", + " FROM agreego.email_address email_address_14", + " JOIN agreego.entity entity_15 ON entity_15.id = email_address_14.id", " WHERE", - " NOT t1_obj_t2_contacts_t3_target_t2.archived", - " AND t1_obj_t2_contacts_t3_target_t1.id = t1_obj_t2_contacts_t3.target_id", - " ))", - " WHEN t1_obj_t2_contacts_t3.target_type = 'address' THEN", + " NOT entity_15.archived", + " AND relationship_10.target_id = email_address_14.id))", + " WHEN entity_11.target_type = 'phone_number' THEN", " ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t2_contacts_t3_target_t2.archived,", - " 'city', t1_obj_t2_contacts_t3_target_t1.city,", - " 'created_at', t1_obj_t2_contacts_t3_target_t2.created_at,", - " 'id', t1_obj_t2_contacts_t3_target_t2.id,", - " 'name', t1_obj_t2_contacts_t3_target_t2.name,", - " 'type', t1_obj_t2_contacts_t3_target_t2.type", + " 'archived', entity_13.archived,", + " 'created_at', entity_13.created_at,", + " 'id', entity_13.id,", + " 'name', entity_13.name,", + " 'number', phone_number_12.number,", + " 'type', entity_13.type", " )", - " FROM agreego.address t1_obj_t2_contacts_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_contacts_t3_target_t2 ON t1_obj_t2_contacts_t3_target_t2.id = t1_obj_t2_contacts_t3_target_t1.id", + " FROM agreego.phone_number phone_number_12", + " JOIN agreego.entity entity_13 ON entity_13.id = phone_number_12.id", " WHERE", - " NOT t1_obj_t2_contacts_t3_target_t2.archived", - " AND t1_obj_t2_contacts_t3_target_t1.id = t1_obj_t2_contacts_t3.target_id", - " ))", - " ELSE NULL", - " END,", - " 'type', t1_obj_t2_contacts_t3.type", + " NOT entity_13.archived", + " AND relationship_10.target_id = phone_number_12.id))", + " ELSE NULL END,", + " 'type', entity_11.type", " )), '[]'::jsonb)", - " FROM agreego.contact t1_obj_t2_contacts_t1", - " JOIN agreego.relationship t1_obj_t2_contacts_t2 ON t1_obj_t2_contacts_t2.id = t1_obj_t2_contacts_t1.id", - " JOIN agreego.entity t1_obj_t2_contacts_t3 ON t1_obj_t2_contacts_t3.id = t1_obj_t2_contacts_t2.id", + " 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 t1_obj_t2_contacts_t3.archived", - " AND t1_obj_t2_contacts_t1.is_primary = ($11#>>'{}')::boolean", - " AND t1_obj_t2_contacts_t1.parent_id = t1_obj_t2.id),", - " 'created_at', t1_obj_t2.created_at,", + " NOT entity_11.archived", + " AND contact_9.is_primary = ($11#>>'{}')::boolean", + " AND contact_9.parent_id = entity_3.id),", + " 'created_at', entity_3.created_at,", " 'email_addresses',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_email_addresses_t3.archived,", - " 'created_at', t1_obj_t2_email_addresses_t3.created_at,", - " 'id', t1_obj_t2_email_addresses_t3.id,", - " 'is_primary', t1_obj_t2_email_addresses_t1.is_primary,", - " 'name', t1_obj_t2_email_addresses_t3.name,", + " 'archived', entity_20.archived,", + " 'created_at', entity_20.created_at,", + " 'id', entity_20.id,", + " 'is_primary', contact_18.is_primary,", + " 'name', entity_20.name,", " 'target',", " (SELECT jsonb_build_object(", - " 'address', t1_obj_t2_email_addresses_t3_target_t1.address,", - " 'archived', t1_obj_t2_email_addresses_t3_target_t2.archived,", - " 'created_at', t1_obj_t2_email_addresses_t3_target_t2.created_at,", - " 'id', t1_obj_t2_email_addresses_t3_target_t2.id,", - " 'name', t1_obj_t2_email_addresses_t3_target_t2.name,", - " 'type', t1_obj_t2_email_addresses_t3_target_t2.type", - " )", - " FROM agreego.email_address t1_obj_t2_email_addresses_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_email_addresses_t3_target_t2 ON t1_obj_t2_email_addresses_t3_target_t2.id = t1_obj_t2_email_addresses_t3_target_t1.id", - " WHERE", - " NOT t1_obj_t2_email_addresses_t3_target_t2.archived", - " AND t1_obj_t2_email_addresses_t3_target_t1.id = t1_obj_t2_email_addresses_t3.target_id", - " ),", - " 'type', t1_obj_t2_email_addresses_t3.type", + " 'address', email_address_21.address,", + " 'archived', entity_22.archived,", + " 'created_at', entity_22.created_at,", + " 'id', entity_22.id,", + " 'name', entity_22.name,", + " 'type', entity_22.type", + " )", + " FROM agreego.email_address email_address_21", + " JOIN agreego.entity entity_22 ON entity_22.id = email_address_21.id", + " WHERE", + " NOT entity_22.archived", + " AND relationship_19.target_id = email_address_21.id),", + " 'type', entity_20.type", " )), '[]'::jsonb)", - " FROM agreego.contact t1_obj_t2_email_addresses_t1", - " JOIN agreego.relationship t1_obj_t2_email_addresses_t2 ON t1_obj_t2_email_addresses_t2.id = t1_obj_t2_email_addresses_t1.id", - " JOIN agreego.entity t1_obj_t2_email_addresses_t3 ON t1_obj_t2_email_addresses_t3.id = t1_obj_t2_email_addresses_t2.id", + " FROM agreego.contact contact_18", + " JOIN agreego.relationship relationship_19 ON relationship_19.id = contact_18.id", + " JOIN agreego.entity entity_20 ON entity_20.id = relationship_19.id", " WHERE", - " NOT t1_obj_t2_email_addresses_t3.archived", - " AND t1_obj_t2_email_addresses_t1.parent_id = t1_obj_t2.id),", - " 'first_name', t1_obj_t1.first_name,", - " 'id', t1_obj_t2.id,", - " 'last_name', t1_obj_t1.last_name,", - " 'name', t1_obj_t2.name,", + " NOT entity_20.archived", + " AND contact_18.parent_id = entity_3.id),", + " 'first_name', person_1.first_name,", + " 'id', entity_3.id,", + " 'last_name', person_1.last_name,", + " 'name', entity_3.name,", " 'phone_numbers',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_phone_numbers_t3.archived,", - " 'created_at', t1_obj_t2_phone_numbers_t3.created_at,", - " 'id', t1_obj_t2_phone_numbers_t3.id,", - " 'is_primary', t1_obj_t2_phone_numbers_t1.is_primary,", - " 'name', t1_obj_t2_phone_numbers_t3.name,", + " 'archived', entity_25.archived,", + " 'created_at', entity_25.created_at,", + " 'id', entity_25.id,", + " 'is_primary', contact_23.is_primary,", + " 'name', entity_25.name,", " 'target',", " (SELECT jsonb_build_object(", - " 'archived', t1_obj_t2_phone_numbers_t3_target_t2.archived,", - " 'created_at', t1_obj_t2_phone_numbers_t3_target_t2.created_at,", - " 'id', t1_obj_t2_phone_numbers_t3_target_t2.id,", - " 'name', t1_obj_t2_phone_numbers_t3_target_t2.name,", - " 'number', t1_obj_t2_phone_numbers_t3_target_t1.number,", - " 'type', t1_obj_t2_phone_numbers_t3_target_t2.type", - " )", - " FROM agreego.phone_number t1_obj_t2_phone_numbers_t3_target_t1", - " JOIN agreego.entity t1_obj_t2_phone_numbers_t3_target_t2 ON t1_obj_t2_phone_numbers_t3_target_t2.id = t1_obj_t2_phone_numbers_t3_target_t1.id", - " WHERE", - " NOT t1_obj_t2_phone_numbers_t3_target_t2.archived", - " AND t1_obj_t2_phone_numbers_t3_target_t1.number ILIKE $32#>>'{}'", - " AND t1_obj_t2_phone_numbers_t3_target_t1.id = t1_obj_t2_phone_numbers_t3.target_id", - " ),", - " 'type', t1_obj_t2_phone_numbers_t3.type", + " 'archived', entity_27.archived,", + " 'created_at', entity_27.created_at,", + " 'id', entity_27.id,", + " 'name', entity_27.name,", + " 'number', phone_number_26.number,", + " 'type', entity_27.type", + " )", + " FROM agreego.phone_number phone_number_26", + " JOIN agreego.entity entity_27 ON entity_27.id = phone_number_26.id", + " WHERE", + " NOT entity_27.archived", + " AND phone_number_26.number ILIKE $32#>>'{}'", + " AND relationship_24.target_id = phone_number_26.id),", + " 'type', entity_25.type", " )), '[]'::jsonb)", - " FROM agreego.contact t1_obj_t2_phone_numbers_t1", - " JOIN agreego.relationship t1_obj_t2_phone_numbers_t2 ON t1_obj_t2_phone_numbers_t2.id = t1_obj_t2_phone_numbers_t1.id", - " JOIN agreego.entity t1_obj_t2_phone_numbers_t3 ON t1_obj_t2_phone_numbers_t3.id = t1_obj_t2_phone_numbers_t2.id", + " 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 t1_obj_t2_phone_numbers_t3.archived", - " AND t1_obj_t2_phone_numbers_t1.parent_id = t1_obj_t2.id),", - " 'type', t1_obj_t2.type", + " NOT entity_25.archived", + " AND contact_23.parent_id = entity_3.id),", + " 'type', entity_3.type", ")", - "FROM agreego.person t1_obj_t1", - "JOIN agreego.entity t1_obj_t2 ON t1_obj_t2.id = t1_obj_t1.id", + "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 t1_obj_t2.archived", - " AND t1_obj_t1.age = ($1#>>'{}')::numeric", - " AND t1_obj_t1.age > ($2#>>'{}')::numeric", - " AND t1_obj_t1.age >= ($3#>>'{}')::numeric", - " AND t1_obj_t1.age IN (SELECT value::numeric FROM jsonb_array_elements_text(($4#>>'{}')::jsonb))", - " AND t1_obj_t1.age < ($5#>>'{}')::numeric", - " AND t1_obj_t1.age <= ($6#>>'{}')::numeric", - " AND t1_obj_t1.age != ($7#>>'{}')::numeric", - " AND t1_obj_t1.age NOT IN (SELECT value::numeric FROM jsonb_array_elements_text(($8#>>'{}')::jsonb))", - " AND t1_obj_t2.archived = ($9#>>'{}')::boolean", - " AND t1_obj_t2.archived != ($10#>>'{}')::boolean", - " AND t1_obj_t2.created_at = ($12#>>'{}')::timestamptz", - " AND t1_obj_t2.created_at > ($13#>>'{}')::timestamptz", - " AND t1_obj_t2.created_at >= ($14#>>'{}')::timestamptz", - " AND t1_obj_t2.created_at < ($15#>>'{}')::timestamptz", - " AND t1_obj_t2.created_at <= ($16#>>'{}')::timestamptz", - " AND t1_obj_t2.created_at != ($17#>>'{}')::timestamptz", - " AND t1_obj_t1.first_name ILIKE $18#>>'{}'", - " AND t1_obj_t1.first_name > ($19#>>'{}')", - " AND t1_obj_t1.first_name >= ($20#>>'{}')", - " AND t1_obj_t1.first_name IN (SELECT value FROM jsonb_array_elements_text(($21#>>'{}')::jsonb))", - " AND t1_obj_t1.first_name < ($22#>>'{}')", - " AND t1_obj_t1.first_name <= ($23#>>'{}')", - " AND t1_obj_t1.first_name NOT ILIKE $24#>>'{}'", - " AND t1_obj_t1.first_name NOT IN (SELECT value FROM jsonb_array_elements_text(($25#>>'{}')::jsonb))", - " AND t1_obj_t2.id = ($26#>>'{}')::uuid", - " AND t1_obj_t2.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($27#>>'{}')::jsonb))", - " AND t1_obj_t2.id != ($28#>>'{}')::uuid", - " AND t1_obj_t2.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($29#>>'{}')::jsonb))", - " AND t1_obj_t1.last_name ILIKE $30#>>'{}'", - " AND t1_obj_t1.last_name NOT ILIKE $31#>>'{}'", - ")" + " NOT entity_3.archived", + " AND person_1.age = ($1#>>'{}')::numeric", + " AND person_1.age > ($2#>>'{}')::numeric", + " AND person_1.age >= ($3#>>'{}')::numeric", + " AND person_1.age IN (SELECT value::numeric FROM jsonb_array_elements_text(($4#>>'{}')::jsonb))", + " AND person_1.age < ($5#>>'{}')::numeric", + " AND person_1.age <= ($6#>>'{}')::numeric", + " AND person_1.age != ($7#>>'{}')::numeric", + " AND person_1.age NOT IN (SELECT value::numeric FROM jsonb_array_elements_text(($8#>>'{}')::jsonb))", + " AND entity_3.archived = ($9#>>'{}')::boolean", + " AND entity_3.archived != ($10#>>'{}')::boolean", + " AND entity_3.created_at = ($12#>>'{}')::timestamptz", + " AND entity_3.created_at > ($13#>>'{}')::timestamptz", + " AND entity_3.created_at >= ($14#>>'{}')::timestamptz", + " AND entity_3.created_at < ($15#>>'{}')::timestamptz", + " AND entity_3.created_at <= ($16#>>'{}')::timestamptz", + " AND entity_3.created_at != ($17#>>'{}')::timestamptz", + " AND person_1.first_name ILIKE $18#>>'{}'", + " AND person_1.first_name > ($19#>>'{}')", + " AND person_1.first_name >= ($20#>>'{}')", + " AND person_1.first_name IN (SELECT value FROM jsonb_array_elements_text(($21#>>'{}')::jsonb))", + " AND person_1.first_name < ($22#>>'{}')", + " AND person_1.first_name <= ($23#>>'{}')", + " AND person_1.first_name NOT ILIKE $24#>>'{}'", + " AND person_1.first_name NOT IN (SELECT value FROM jsonb_array_elements_text(($25#>>'{}')::jsonb))", + " AND entity_3.id = ($26#>>'{}')::uuid", + " AND entity_3.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($27#>>'{}')::jsonb))", + " AND entity_3.id != ($28#>>'{}')::uuid", + " AND entity_3.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($29#>>'{}')::jsonb))", + " AND person_1.last_name ILIKE $30#>>'{}'", + " AND person_1.last_name NOT ILIKE $31#>>'{}')" ] ] } @@ -1375,31 +1439,32 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'archived', t1_obj_t3.archived,", - " 'created_at', t1_obj_t3.created_at,", - " 'id', t1_obj_t3.id,", - " 'is_primary', t1_obj_t1.is_primary,", - " 'name', t1_obj_t3.name,", + " 'archived', entity_3.archived,", + " 'created_at', entity_3.created_at,", + " 'id', entity_3.id,", + " 'is_primary', contact_1.is_primary,", + " 'name', entity_3.name,", " 'target',", " (SELECT jsonb_build_object(", - " 'archived', t1_obj_t3_target_t2.archived,", - " 'created_at', t1_obj_t3_target_t2.created_at,", - " 'id', t1_obj_t3_target_t2.id,", - " 'name', t1_obj_t3_target_t2.name,", - " 'number', t1_obj_t3_target_t1.number,", - " 'type', t1_obj_t3_target_t2.type", + " 'archived', entity_5.archived,", + " 'created_at', entity_5.created_at,", + " 'id', entity_5.id,", + " 'name', entity_5.name,", + " 'number', phone_number_4.number,", + " 'type', entity_5.type", " )", - " FROM agreego.phone_number t1_obj_t3_target_t1", - " JOIN agreego.entity t1_obj_t3_target_t2 ON t1_obj_t3_target_t2.id = t1_obj_t3_target_t1.id", + " FROM agreego.phone_number phone_number_4", + " JOIN agreego.entity entity_5 ON entity_5.id = phone_number_4.id", " WHERE", - " NOT t1_obj_t3_target_t2.archived", - " AND t1_obj_t3_target_t1.id = t1_obj_t3.target_id),", - " 'type', t1_obj_t3.type", + " NOT entity_5.archived", + " AND relationship_2.target_id = phone_number_4.id", + " ),", + " 'type', entity_3.type", ")", - "FROM agreego.contact t1_obj_t1", - "JOIN agreego.relationship t1_obj_t2 ON t1_obj_t2.id = t1_obj_t1.id", - "JOIN agreego.entity t1_obj_t3 ON t1_obj_t3.id = t1_obj_t2.id", - "WHERE NOT t1_obj_t3.archived)" + "FROM agreego.contact contact_1", + "JOIN agreego.relationship relationship_2 ON relationship_2.id = contact_1.id", + "JOIN agreego.entity entity_3 ON entity_3.id = relationship_2.id", + "WHERE NOT entity_3.archived)" ] ] } @@ -1414,15 +1479,16 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'archived', t1_obj_t2.archived,", - " 'created_at', t1_obj_t2.created_at,", - " 'id', t1_obj_t2.id,", - " 'name', t1_obj_t2.name,", - " 'number', t1_obj_t1.number,", - " 'type', t1_obj_t2.type)", - "FROM agreego.phone_number t1_obj_t1", - "JOIN agreego.entity t1_obj_t2 ON t1_obj_t2.id = t1_obj_t1.id", - "WHERE NOT t1_obj_t2.archived)" + " 'archived', entity_2.archived,", + " 'created_at', entity_2.created_at,", + " 'id', entity_2.id,", + " 'name', entity_2.name,", + " 'number', phone_number_1.number,", + " 'type', entity_2.type", + ")", + "FROM agreego.phone_number phone_number_1", + "JOIN agreego.entity entity_2 ON entity_2.id = phone_number_1.id", + "WHERE NOT entity_2.archived)" ] ] } @@ -1437,15 +1503,16 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'address', t1_obj_t1.address,", - " 'archived', t1_obj_t2.archived,", - " 'created_at', t1_obj_t2.created_at,", - " 'id', t1_obj_t2.id,", - " 'name', t1_obj_t2.name,", - " 'type', t1_obj_t2.type)", - "FROM agreego.email_address t1_obj_t1", - "JOIN agreego.entity t1_obj_t2 ON t1_obj_t2.id = t1_obj_t1.id", - "WHERE NOT t1_obj_t2.archived)" + " 'address', email_address_1.address,", + " 'archived', entity_2.archived,", + " 'created_at', entity_2.created_at,", + " 'id', entity_2.id,", + " 'name', entity_2.name,", + " 'type', entity_2.type", + ")", + "FROM agreego.email_address email_address_1", + "JOIN agreego.entity entity_2 ON entity_2.id = email_address_1.id", + "WHERE NOT entity_2.archived)" ] ] } @@ -1459,49 +1526,50 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'archived', t1_obj_t2.archived,", - " 'created_at', t1_obj_t2.created_at,", + " 'archived', entity_2.archived,", + " 'created_at', entity_2.created_at,", " 'customer',", " (SELECT jsonb_build_object(", - " 'age', t1_obj_t2_customer_t1.age,", - " 'archived', t1_obj_t2_customer_t2.archived,", - " 'created_at', t1_obj_t2_customer_t2.created_at,", - " 'first_name', t1_obj_t2_customer_t1.first_name,", - " 'id', t1_obj_t2_customer_t2.id,", - " 'last_name', t1_obj_t2_customer_t1.last_name,", - " 'name', t1_obj_t2_customer_t2.name,", - " 'type', t1_obj_t2_customer_t2.type", + " 'age', person_3.age,", + " 'archived', entity_5.archived,", + " 'created_at', entity_5.created_at,", + " 'first_name', person_3.first_name,", + " 'id', entity_5.id,", + " 'last_name', person_3.last_name,", + " 'name', entity_5.name,", + " 'type', entity_5.type", " )", - " FROM agreego.person t1_obj_t2_customer_t1", - " JOIN agreego.entity t1_obj_t2_customer_t2 ON t1_obj_t2_customer_t2.id = t1_obj_t2_customer_t1.id", + " FROM agreego.person person_3", + " JOIN agreego.organization organization_4 ON organization_4.id = person_3.id", + " JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id", " WHERE", - " NOT t1_obj_t2_customer_t2.archived", - " AND t1_obj_t2_customer_t1.parent_id = t1_obj_t2.id),", - " 'customer_id', t1_obj_t1.customer_id,", - " 'id', t1_obj_t2.id,", + " NOT entity_5.archived", + " AND order_1.customer_id = person_3.id),", + " 'customer_id', order_1.customer_id,", + " 'id', entity_2.id,", " 'lines',", " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", - " 'archived', t1_obj_t2_lines_t2.archived,", - " 'created_at', t1_obj_t2_lines_t2.created_at,", - " 'id', t1_obj_t2_lines_t2.id,", - " 'name', t1_obj_t2_lines_t2.name,", - " 'order_id', t1_obj_t2_lines_t1.order_id,", - " 'price', t1_obj_t2_lines_t1.price,", - " 'product', t1_obj_t2_lines_t1.product,", - " 'type', t1_obj_t2_lines_t2.type", + " 'archived', entity_7.archived,", + " 'created_at', entity_7.created_at,", + " 'id', entity_7.id,", + " 'name', entity_7.name,", + " 'order_id', order_line_6.order_id,", + " 'price', order_line_6.price,", + " 'product', order_line_6.product,", + " 'type', entity_7.type", " )), '[]'::jsonb)", - " FROM agreego.order_line t1_obj_t2_lines_t1", - " JOIN agreego.entity t1_obj_t2_lines_t2 ON t1_obj_t2_lines_t2.id = t1_obj_t2_lines_t1.id", + " FROM agreego.order_line order_line_6", + " JOIN agreego.entity entity_7 ON entity_7.id = order_line_6.id", " WHERE", - " NOT t1_obj_t2_lines_t2.archived", - " AND t1_obj_t2_lines_t1.parent_id = t1_obj_t2.id),", - " 'name', t1_obj_t2.name,", - " 'total', t1_obj_t1.total,", - " 'type', t1_obj_t2.type", + " NOT entity_7.archived", + " AND order_line_6.order_id = order_1.id),", + " 'name', entity_2.name,", + " 'total', order_1.total,", + " 'type', entity_2.type", ")", - "FROM agreego.order t1_obj_t1", - "JOIN agreego.entity t1_obj_t2 ON t1_obj_t2.id = t1_obj_t1.id", - "WHERE NOT t1_obj_t2.archived)" + "FROM agreego.order order_1", + "JOIN agreego.entity entity_2 ON entity_2.id = order_1.id", + "WHERE NOT entity_2.archived)" ] ] } @@ -1515,112 +1583,52 @@ "sql": [ [ "(SELECT jsonb_build_object(", - " 'id', t1_obj_t1.id,", - " 'type', CASE WHEN t1_obj_t1.type = 'address' THEN ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t1_obj_t2.archived,", - " 'city', t1_obj_t1_obj_t1.city,", - " 'created_at', t1_obj_t1_obj_t2.created_at,", - " 'id', t1_obj_t1_obj_t2.id,", - " 'name', t1_obj_t1_obj_t2.name,", - " 'type', t1_obj_t1_obj_t2.type)", - " FROM agreego.address t1_obj_t1_obj_t1", - " JOIN agreego.entity t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " WHERE NOT t1_obj_t1_obj_t2.archived))", - " WHEN t1_obj_t1.type = 'contact' THEN ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t1_obj_t3.archived,", - " 'created_at', t1_obj_t1_obj_t3.created_at,", - " 'id', t1_obj_t1_obj_t3.id,", - " 'is_primary', t1_obj_t1_obj_t1.is_primary,", - " 'name', t1_obj_t1_obj_t3.name,", - " 'type', t1_obj_t1_obj_t3.type)", - " FROM agreego.contact t1_obj_t1_obj_t1", - " JOIN agreego.relationship t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " JOIN agreego.entity t1_obj_t1_obj_t3 ON t1_obj_t1_obj_t3.id = t1_obj_t1_obj_t2.id", - " WHERE NOT t1_obj_t1_obj_t3.archived))", - " WHEN t1_obj_t1.type = 'email_address' THEN ((SELECT jsonb_build_object(", - " 'address', t1_obj_t1_obj_t1.address,", - " 'archived', t1_obj_t1_obj_t2.archived,", - " 'created_at', t1_obj_t1_obj_t2.created_at,", - " 'id', t1_obj_t1_obj_t2.id,", - " 'name', t1_obj_t1_obj_t2.name,", - " 'type', t1_obj_t1_obj_t2.type)", - " FROM agreego.email_address t1_obj_t1_obj_t1", - " JOIN agreego.entity t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " WHERE NOT t1_obj_t1_obj_t2.archived))", - " WHEN t1_obj_t1.type = 'entity' THEN ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t1_obj_t1.archived,", - " 'created_at', t1_obj_t1_obj_t1.created_at,", - " 'id', t1_obj_t1_obj_t1.id,", - " 'name', t1_obj_t1_obj_t1.name,", - " 'type', t1_obj_t1_obj_t1.type)", - " FROM agreego.entity t1_obj_t1_obj_t1", - " WHERE NOT t1_obj_t1_obj_t1.archived))", - " WHEN t1_obj_t1.type = 'order' THEN ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t1_obj_t2.archived,", - " 'created_at', t1_obj_t1_obj_t2.created_at,", - " 'customer_id', t1_obj_t1_obj_t1.customer_id,", - " 'id', t1_obj_t1_obj_t2.id,", - " 'name', t1_obj_t1_obj_t2.name,", - " 'total', t1_obj_t1_obj_t1.total,", - " 'type', t1_obj_t1_obj_t2.type)", - " FROM agreego.order t1_obj_t1_obj_t1", - " JOIN agreego.entity t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " WHERE NOT t1_obj_t1_obj_t2.archived))", - " WHEN t1_obj_t1.type = 'order_line' THEN ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t1_obj_t2.archived,", - " 'created_at', t1_obj_t1_obj_t2.created_at,", - " 'id', t1_obj_t1_obj_t2.id,", - " 'name', t1_obj_t1_obj_t2.name,", - " 'order_id', t1_obj_t1_obj_t1.order_id,", - " 'price', t1_obj_t1_obj_t1.price,", - " 'product', t1_obj_t1_obj_t1.product,", - " 'type', t1_obj_t1_obj_t2.type)", - " FROM agreego.order_line t1_obj_t1_obj_t1", - " JOIN agreego.entity t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " WHERE NOT t1_obj_t1_obj_t2.archived))", - " WHEN t1_obj_t1.type = 'organization' THEN ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t1_obj_t2.archived,", - " 'created_at', t1_obj_t1_obj_t2.created_at,", - " 'id', t1_obj_t1_obj_t2.id,", - " 'name', t1_obj_t1_obj_t2.name,", - " 'type', t1_obj_t1_obj_t2.type)", - " FROM agreego.organization t1_obj_t1_obj_t1", - " JOIN agreego.entity t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " WHERE NOT t1_obj_t1_obj_t2.archived))", - " WHEN t1_obj_t1.type = 'person' THEN ((SELECT jsonb_build_object(", - " 'age', t1_obj_t1_obj_t1.age,", - " 'archived', t1_obj_t1_obj_t2.archived,", - " 'created_at', t1_obj_t1_obj_t2.created_at,", - " 'first_name', t1_obj_t1_obj_t1.first_name,", - " 'id', t1_obj_t1_obj_t2.id,", - " 'last_name', t1_obj_t1_obj_t1.last_name,", - " 'name', t1_obj_t1_obj_t2.name,", - " 'type', t1_obj_t1_obj_t2.type)", - " FROM agreego.person t1_obj_t1_obj_t1", - " JOIN agreego.entity t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " WHERE NOT t1_obj_t1_obj_t2.archived))", - " WHEN t1_obj_t1.type = 'phone_number' THEN ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t1_obj_t2.archived,", - " 'created_at', t1_obj_t1_obj_t2.created_at,", - " 'id', t1_obj_t1_obj_t2.id,", - " 'name', t1_obj_t1_obj_t2.name,", - " 'number', t1_obj_t1_obj_t1.number,", - " 'type', t1_obj_t1_obj_t2.type)", - " FROM agreego.phone_number t1_obj_t1_obj_t1", - " JOIN agreego.entity t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " WHERE NOT t1_obj_t1_obj_t2.archived))", - " WHEN t1_obj_t1.type = 'relationship' THEN ((SELECT jsonb_build_object(", - " 'archived', t1_obj_t1_obj_t2.archived,", - " 'created_at', t1_obj_t1_obj_t2.created_at,", - " 'id', t1_obj_t1_obj_t2.id,", - " 'name', t1_obj_t1_obj_t2.name,", - " 'type', t1_obj_t1_obj_t2.type)", - " FROM agreego.relationship t1_obj_t1_obj_t1", - " JOIN agreego.entity t1_obj_t1_obj_t2 ON t1_obj_t1_obj_t2.id = t1_obj_t1_obj_t1.id", - " WHERE NOT t1_obj_t1_obj_t2.archived))", - " ELSE NULL END)", - "FROM agreego.entity t1_obj_t1", - "WHERE NOT t1_obj_t1.archived)" + " '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', entity_5.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', entity_7.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', entity_10.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", + ")", + "FROM agreego.organization organization_1", + "JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id", + "WHERE NOT entity_2.archived)" ] ] } diff --git a/src/merger/mod.rs b/src/merger/mod.rs index 1f25b4a..7c28e3b 100644 --- a/src/merger/mod.rs +++ b/src/merger/mod.rs @@ -21,15 +21,12 @@ impl Merger { } pub fn merge(&self, data: Value) -> crate::drop::Drop { - let mut val_resolved = Value::Null; let mut notifications_queue = Vec::new(); let result = self.merge_internal(data, &mut notifications_queue); - match result { - Ok(val) => { - val_resolved = val; - } + let val_resolved = match result { + Ok(val) => val, Err(msg) => { return crate::drop::Drop::with_errors(vec![crate::drop::Error { code: "MERGE_FAILED".to_string(), diff --git a/src/queryer/compiler.rs b/src/queryer/compiler.rs index d57dad5..e718656 100644 --- a/src/queryer/compiler.rs +++ b/src/queryer/compiler.rs @@ -179,32 +179,49 @@ impl SqlCompiler { } // Handle $family Polymorphism fallbacks for relations if let Some(family_target) = &schema.obj.family { - let mut all_targets = vec![family_target.clone()]; - if let Some(schema_id) = &schema.obj.id { - if let Some(descendants) = self.db.descendants.get(schema_id) { - all_targets.extend(descendants.clone()); + let base_type_name = family_target.split('.').next_back().unwrap_or(family_target).to_string(); + + if let Some(type_def) = self.db.types.get(&base_type_name) { + if type_def.variations.len() == 1 { + let mut bypass_schema = crate::database::schema::Schema::default(); + bypass_schema.obj.r#ref = Some(family_target.clone()); + return self.walk_schema( + &std::sync::Arc::new(bypass_schema), + parent_alias, + parent_table_aliases, + parent_type_def, + prop_name_context, + filter_keys, + is_stem_query, + depth, + current_path, + alias_counter, + ); } - } - let mut family_schemas = Vec::new(); - for target in all_targets { - let mut ref_schema = crate::database::schema::Schema::default(); - ref_schema.obj.r#ref = Some(target); - family_schemas.push(std::sync::Arc::new(ref_schema)); - } + let mut sorted_variations: Vec = type_def.variations.iter().cloned().collect(); + sorted_variations.sort(); - return self.compile_one_of( - &family_schemas, - parent_alias, - parent_table_aliases, - parent_type_def, - prop_name_context, - filter_keys, - is_stem_query, - depth, - current_path, - alias_counter, - ); + let mut family_schemas = Vec::new(); + for variation in &sorted_variations { + let mut ref_schema = crate::database::schema::Schema::default(); + ref_schema.obj.r#ref = Some(variation.clone()); + family_schemas.push(std::sync::Arc::new(ref_schema)); + } + + return self.compile_one_of( + &family_schemas, + parent_alias, + parent_table_aliases, + parent_type_def, + prop_name_context, + filter_keys, + is_stem_query, + depth, + current_path, + alias_counter, + ); + } } // Handle oneOf Polymorphism fallbacks for relations @@ -305,45 +322,56 @@ impl SqlCompiler { // 2.5 Inject polymorphism directly into the query object if let Some(family_target) = &schema.obj.family { - let mut family_schemas = Vec::new(); - if let Some(base_type) = self.db.types.get(family_target) { - let mut sorted_targets: Vec = base_type.variations.iter().cloned().collect(); - // Ensure the base type is included if not listed in variations by default - if !sorted_targets.contains(family_target) { - sorted_targets.push(family_target.clone()); - } - sorted_targets.sort(); + let base_type_name = family_target.split('.').next_back().unwrap_or(family_target).to_string(); - for target in sorted_targets { - let mut ref_schema = crate::database::schema::Schema::default(); - ref_schema.obj.r#ref = Some(target); - family_schemas.push(std::sync::Arc::new(ref_schema)); + if let Some(fam_type_def) = self.db.types.get(&base_type_name) { + if fam_type_def.variations.len() == 1 { + let mut bypass_schema = crate::database::schema::Schema::default(); + bypass_schema.obj.r#ref = Some(family_target.clone()); + + let mut bypassed_args = self.map_properties_to_aliases( + &bypass_schema, + type_def, + &table_aliases, + parent_alias, + filter_keys, + is_stem_query, + depth, + ¤t_path, + alias_counter, + )?; + select_args.append(&mut bypassed_args); + } else { + let mut family_schemas = Vec::new(); + let mut sorted_fam_variations: Vec = fam_type_def.variations.iter().cloned().collect(); + sorted_fam_variations.sort(); + + for variation in &sorted_fam_variations { + let mut ref_schema = crate::database::schema::Schema::default(); + ref_schema.obj.r#ref = Some(variation.clone()); + family_schemas.push(std::sync::Arc::new(ref_schema)); + } + + let base_alias = table_aliases + .get(&type_def.name) + .cloned() + .unwrap_or_else(|| parent_alias.to_string()); + select_args.push(format!("'id', {}.id", base_alias)); + let (case_sql, _) = self.compile_one_of( + &family_schemas, + &base_alias, + Some(&table_aliases), + parent_type_def, + None, + filter_keys, + is_stem_query, + depth, + current_path.clone(), + alias_counter, + )?; + select_args.push(format!("'type', {}", case_sql)); } - } else { - // Fallback for types not strictly defined in physical DB - let mut ref_schema = crate::database::schema::Schema::default(); - ref_schema.obj.r#ref = Some(family_target.clone()); - family_schemas.push(std::sync::Arc::new(ref_schema)); } - - let base_alias = table_aliases - .get(&type_def.name) - .cloned() - .unwrap_or_else(|| parent_alias.to_string()); - select_args.push(format!("'id', {}.id", base_alias)); - let (case_sql, _) = self.compile_one_of( - &family_schemas, - &base_alias, - Some(&table_aliases), - parent_type_def, - None, - filter_keys, - is_stem_query, - depth, - current_path.clone(), - alias_counter, - )?; - select_args.push(format!("'type', {}", case_sql)); } else if let Some(one_of) = &schema.obj.one_of { let base_alias = table_aliases .get(&type_def.name) @@ -448,8 +476,11 @@ impl SqlCompiler { let mut select_args = Vec::new(); let grouped_fields = type_def.grouped_fields.as_ref().and_then(|v| v.as_object()); let merged_props = self.get_merged_properties(schema); + let mut sorted_keys: Vec<&String> = merged_props.keys().collect(); + sorted_keys.sort(); - for (prop_key, prop_schema) in &merged_props { + for prop_key in sorted_keys { + let prop_schema = &merged_props[prop_key]; let mut owner_alias = table_aliases .get("entity") .cloned() @@ -832,6 +863,8 @@ impl SqlCompiler { return Ok(("NULL".to_string(), "string".to_string())); } + case_statements.sort(); + let sql = format!("CASE {} ELSE NULL END", case_statements.join(" ")); Ok((sql, "object".to_string())) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 649fa83..9cfc000 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -2,7 +2,6 @@ use crate::*; pub mod runner; pub mod types; use serde_json::json; -pub mod sql_validator; // Database module tests moved to src/database/executors/mock.rs diff --git a/src/tests/runner.rs b/src/tests/runner.rs index b38b043..282893e 100644 --- a/src/tests/runner.rs +++ b/src/tests/runner.rs @@ -1,19 +1,10 @@ +use crate::tests::types::Suite; use serde::Deserialize; +use serde_json::Value; use std::collections::HashMap; use std::fs; use std::sync::{Arc, OnceLock, RwLock}; -#[derive(Debug, Deserialize)] -pub struct TestSuite { - #[allow(dead_code)] - pub description: String, - pub database: serde_json::Value, - pub tests: Vec, -} - -use crate::tests::types::TestCase; -use serde_json::Value; - pub fn deserialize_some<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, @@ -23,7 +14,7 @@ where } // Type alias for easier reading -type CompiledSuite = Arc)>>; +type CompiledSuite = Arc)>>; // Global cache mapping filename -> Vector of (Parsed JSON suite, Compiled Database) static CACHE: OnceLock>> = OnceLock::new(); @@ -46,7 +37,7 @@ fn get_cached_file(path: &str) -> CompiledSuite { } else { let content = fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path)); - let suites: Vec = serde_json::from_str(&content) + let suites: Vec = serde_json::from_str(&content) .unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e)); let mut compiled_suites = Vec::new(); diff --git a/src/tests/sql_validator.rs b/src/tests/sql_validator.rs deleted file mode 100644 index c778fe7..0000000 --- a/src/tests/sql_validator.rs +++ /dev/null @@ -1,194 +0,0 @@ -use sqlparser::ast::{Expr, Query, SelectItem, Statement, TableFactor}; -use sqlparser::dialect::PostgreSqlDialect; -use sqlparser::parser::Parser; -use std::collections::HashSet; - -pub fn validate_semantic_sql(sql: &str) -> Result<(), String> { - let dialect = PostgreSqlDialect {}; - let statements = match Parser::parse_sql(&dialect, sql) { - Ok(s) => s, - Err(e) => return Err(format!("SQL Syntax Error: {}\nSQL: {}", e, sql)), - }; - - for statement in statements { - validate_statement(&statement, sql)?; - } - - Ok(()) -} - -fn validate_statement(stmt: &Statement, original_sql: &str) -> Result<(), String> { - match stmt { - Statement::Query(query) => validate_query(query, &HashSet::new(), original_sql)?, - Statement::Insert(insert) => { - if let Some(query) = &insert.source { - validate_query(query, &HashSet::new(), original_sql)? - } - } - Statement::Update(update) => { - if let Some(expr) = &update.selection { - validate_expr(expr, &HashSet::new(), original_sql)?; - } - } - Statement::Delete(delete) => { - if let Some(expr) = &delete.selection { - validate_expr(expr, &HashSet::new(), original_sql)?; - } - } - _ => {} - } - Ok(()) -} - -fn validate_query( - query: &Query, - available_aliases: &HashSet, - original_sql: &str, -) -> Result<(), String> { - if let sqlparser::ast::SetExpr::Select(select) = &*query.body { - validate_select(&select, available_aliases, original_sql)?; - } - Ok(()) -} - -fn validate_select( - select: &sqlparser::ast::Select, - parent_aliases: &HashSet, - original_sql: &str, -) -> Result<(), String> { - let mut available_aliases = parent_aliases.clone(); - - // 1. Collect all declared table aliases in the FROM clause and JOINs - for table_with_joins in &select.from { - collect_aliases_from_table_factor(&table_with_joins.relation, &mut available_aliases); - for join in &table_with_joins.joins { - collect_aliases_from_table_factor(&join.relation, &mut available_aliases); - } - } - - // 2. Validate all SELECT projection fields - for projection in &select.projection { - if let SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } = projection { - validate_expr(expr, &available_aliases, original_sql)?; - } - } - - // 3. Validate ON conditions in joins - for table_with_joins in &select.from { - for join in &table_with_joins.joins { - if let sqlparser::ast::JoinOperator::Inner(sqlparser::ast::JoinConstraint::On(expr)) - | sqlparser::ast::JoinOperator::LeftOuter(sqlparser::ast::JoinConstraint::On(expr)) - | sqlparser::ast::JoinOperator::RightOuter(sqlparser::ast::JoinConstraint::On(expr)) - | sqlparser::ast::JoinOperator::FullOuter(sqlparser::ast::JoinConstraint::On(expr)) - | sqlparser::ast::JoinOperator::Join(sqlparser::ast::JoinConstraint::On(expr)) = - &join.join_operator - { - validate_expr(expr, &available_aliases, original_sql)?; - } - } - } - - // 4. Validate WHERE conditions - if let Some(selection) = &select.selection { - validate_expr(selection, &available_aliases, original_sql)?; - } - - Ok(()) -} - -fn collect_aliases_from_table_factor(tf: &TableFactor, aliases: &mut HashSet) { - match tf { - TableFactor::Table { name, alias, .. } => { - if let Some(table_alias) = alias { - aliases.insert(table_alias.name.value.clone()); - } else if let Some(last) = name.0.last() { - match last { - sqlparser::ast::ObjectNamePart::Identifier(i) => { - aliases.insert(i.value.clone()); - } - _ => {} - } - } - } - TableFactor::Derived { - subquery, - alias: Some(table_alias), - .. - } => { - aliases.insert(table_alias.name.value.clone()); - // A derived table is technically a nested scope which is opaque outside, but for pure semantic checks - // its internal contents should be validated purely within its own scope (not leaking external aliases in, usually) - // but Postgres allows lateral correlation. We will validate its interior with an empty scope. - let _ = validate_query(subquery, &HashSet::new(), ""); - } - _ => {} - } -} - -fn validate_expr( - expr: &Expr, - available_aliases: &HashSet, - sql: &str, -) -> Result<(), String> { - match expr { - Expr::CompoundIdentifier(idents) => { - if idents.len() == 2 { - let alias = &idents[0].value; - if !available_aliases.is_empty() && !available_aliases.contains(alias) { - return Err(format!( - "Semantic Error: Orchestrated query referenced table alias '{}' but it was not declared in the query's FROM/JOIN clauses.\nAvailable aliases: {:?}\nSQL: {}", - alias, available_aliases, sql - )); - } - } else if idents.len() > 2 { - let alias = &idents[1].value; // In form schema.table.column, 'table' is idents[1] - if !available_aliases.is_empty() && !available_aliases.contains(alias) { - return Err(format!( - "Semantic Error: Orchestrated query referenced table '{}' but it was not mapped.\nAvailable aliases: {:?}\nSQL: {}", - alias, available_aliases, sql - )); - } - } - } - Expr::Subquery(subquery) => validate_query(subquery, available_aliases, sql)?, - Expr::Exists { subquery, .. } => validate_query(subquery, available_aliases, sql)?, - Expr::InSubquery { - expr: e, subquery, .. - } => { - validate_expr(e, available_aliases, sql)?; - validate_query(subquery, available_aliases, sql)?; - } - Expr::BinaryOp { left, right, .. } => { - validate_expr(left, available_aliases, sql)?; - validate_expr(right, available_aliases, sql)?; - } - Expr::IsFalse(e) - | Expr::IsNotFalse(e) - | Expr::IsTrue(e) - | Expr::IsNotTrue(e) - | Expr::IsNull(e) - | Expr::IsNotNull(e) - | Expr::InList { expr: e, .. } - | Expr::Nested(e) - | Expr::UnaryOp { expr: e, .. } - | Expr::Cast { expr: e, .. } - | Expr::Like { expr: e, .. } - | Expr::ILike { expr: e, .. } - | Expr::AnyOp { left: e, .. } - | Expr::AllOp { left: e, .. } => { - validate_expr(e, available_aliases, sql)?; - } - Expr::Function(func) => { - if let sqlparser::ast::FunctionArguments::List(args) = &func.args { - if let Some(sqlparser::ast::FunctionArg::Unnamed(sqlparser::ast::FunctionArgExpr::Expr( - e, - ))) = args.args.get(0) - { - validate_expr(e, available_aliases, sql)?; - } - } - } - _ => {} - } - Ok(()) -} diff --git a/src/tests/types/case.rs b/src/tests/types/case.rs index 7e728bf..01dabd9 100644 --- a/src/tests/types/case.rs +++ b/src/tests/types/case.rs @@ -1,11 +1,11 @@ -use super::expect::ExpectBlock; +use super::expect::Expect; use crate::database::Database; use serde::Deserialize; use serde_json::Value; use std::sync::Arc; #[derive(Debug, Deserialize)] -pub struct TestCase { +pub struct Case { pub description: String, #[serde(default = "default_action")] @@ -30,14 +30,14 @@ pub struct TestCase { #[serde(default)] pub mocks: Option, - pub expect: Option, + pub expect: Option, } fn default_action() -> String { "validate".to_string() } -impl TestCase { +impl Case { pub fn run_compile(&self, db: Arc) -> Result<(), String> { let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false); @@ -138,6 +138,7 @@ impl TestCase { )) } else if let Some(expect) = &self.expect { let queries = db.executor.get_queries(); + expect.assert_pattern(&queries)?; expect.assert_sql(&queries) } else { Ok(()) @@ -176,6 +177,7 @@ impl TestCase { )) } else if let Some(expect) = &self.expect { let queries = db.executor.get_queries(); + expect.assert_pattern(&queries)?; expect.assert_sql(&queries) } else { Ok(()) diff --git a/src/tests/types/expect/mod.rs b/src/tests/types/expect/mod.rs new file mode 100644 index 0000000..8dee154 --- /dev/null +++ b/src/tests/types/expect/mod.rs @@ -0,0 +1,22 @@ +pub mod pattern; +pub mod sql; + +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum SqlExpectation { + Single(String), + Multi(Vec), +} + +#[derive(Debug, Deserialize)] +pub struct Expect { + pub success: bool, + pub result: Option, + pub errors: Option>, + pub stems: Option>>, + #[serde(default)] + pub sql: Option>, +} diff --git a/src/tests/types/expect.rs b/src/tests/types/expect/pattern.rs similarity index 84% rename from src/tests/types/expect.rs rename to src/tests/types/expect/pattern.rs index a9d9f84..5e1750b 100644 --- a/src/tests/types/expect.rs +++ b/src/tests/types/expect/pattern.rs @@ -1,30 +1,13 @@ +use super::Expect; use regex::Regex; -use serde::Deserialize; use std::collections::HashMap; -#[derive(Debug, Deserialize)] -#[serde(untagged)] -pub enum SqlExpectation { - Single(String), - Multi(Vec), -} - -#[derive(Debug, Deserialize)] -pub struct ExpectBlock { - pub success: bool, - pub result: Option, - pub errors: Option>, - pub stems: Option>>, - #[serde(default)] - pub sql: Option>, -} - -impl ExpectBlock { +impl Expect { /// Advanced SQL execution assertion algorithm ported from `assert.go`. /// This compares two arrays of strings, one containing {{uuid:name}} or {{timestamp}} placeholders, /// and the other containing actual executed database queries. It ensures that placeholder UUIDs /// are consistently mapped to the same actual UUIDs across all lines, and strictly validates line-by-line sequences. - pub fn assert_sql(&self, actual: &[String]) -> Result<(), String> { + pub fn assert_pattern(&self, actual: &[String]) -> Result<(), String> { let patterns = match &self.sql { Some(s) => s, None => return Ok(()), @@ -39,12 +22,6 @@ impl ExpectBlock { )); } - for query in actual { - if let Err(e) = crate::tests::sql_validator::validate_semantic_sql(query) { - return Err(e); - } - } - let ws_re = Regex::new(r"\s+").unwrap(); let types = HashMap::from([ @@ -82,8 +59,8 @@ impl ExpectBlock { let aline = clean_str(aline_raw); let pattern_str_raw = match pattern_expect { - SqlExpectation::Single(s) => s.clone(), - SqlExpectation::Multi(m) => m.join(" "), + super::SqlExpectation::Single(s) => s.clone(), + super::SqlExpectation::Multi(m) => m.join(" "), }; let pattern_str = clean_str(&pattern_str_raw); diff --git a/src/tests/types/expect/sql.rs b/src/tests/types/expect/sql.rs new file mode 100644 index 0000000..794e1dc --- /dev/null +++ b/src/tests/types/expect/sql.rs @@ -0,0 +1,206 @@ +use super::Expect; +use sqlparser::ast::{Expr, Query, SelectItem, Statement, TableFactor}; +use sqlparser::dialect::PostgreSqlDialect; +use sqlparser::parser::Parser; +use std::collections::HashSet; + +impl Expect { + pub fn assert_sql(&self, actual: &[String]) -> Result<(), String> { + for query in actual { + if let Err(e) = Self::validate_semantic_sql(query) { + return Err(e); + } + } + Ok(()) + } + + pub fn validate_semantic_sql(sql: &str) -> Result<(), String> { + let dialect = PostgreSqlDialect {}; + let statements = match Parser::parse_sql(&dialect, sql) { + Ok(s) => s, + Err(e) => return Err(format!("SQL Syntax Error: {}\nSQL: {}", e, sql)), + }; + + for statement in statements { + Self::validate_statement(&statement, sql)?; + } + + Ok(()) + } + + fn validate_statement(stmt: &Statement, original_sql: &str) -> Result<(), String> { + match stmt { + Statement::Query(query) => Self::validate_query(query, &HashSet::new(), original_sql)?, + Statement::Insert(insert) => { + if let Some(query) = &insert.source { + Self::validate_query(query, &HashSet::new(), original_sql)? + } + } + Statement::Update(update) => { + if let Some(expr) = &update.selection { + Self::validate_expr(expr, &HashSet::new(), original_sql)?; + } + } + Statement::Delete(delete) => { + if let Some(expr) = &delete.selection { + Self::validate_expr(expr, &HashSet::new(), original_sql)?; + } + } + _ => {} + } + Ok(()) + } + + fn validate_query( + query: &Query, + available_aliases: &HashSet, + original_sql: &str, + ) -> Result<(), String> { + if let sqlparser::ast::SetExpr::Select(select) = &*query.body { + Self::validate_select(&select, available_aliases, original_sql)?; + } + Ok(()) + } + + fn validate_select( + select: &sqlparser::ast::Select, + parent_aliases: &HashSet, + original_sql: &str, + ) -> Result<(), String> { + let mut available_aliases = parent_aliases.clone(); + + // 1. Collect all declared table aliases in the FROM clause and JOINs + for table_with_joins in &select.from { + Self::collect_aliases_from_table_factor(&table_with_joins.relation, &mut available_aliases); + for join in &table_with_joins.joins { + Self::collect_aliases_from_table_factor(&join.relation, &mut available_aliases); + } + } + + // 2. Validate all SELECT projection fields + for projection in &select.projection { + if let SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } = projection { + Self::validate_expr(expr, &available_aliases, original_sql)?; + } + } + + // 3. Validate ON conditions in joins + for table_with_joins in &select.from { + for join in &table_with_joins.joins { + if let sqlparser::ast::JoinOperator::Inner(sqlparser::ast::JoinConstraint::On(expr)) + | sqlparser::ast::JoinOperator::LeftOuter(sqlparser::ast::JoinConstraint::On(expr)) + | sqlparser::ast::JoinOperator::RightOuter(sqlparser::ast::JoinConstraint::On(expr)) + | sqlparser::ast::JoinOperator::FullOuter(sqlparser::ast::JoinConstraint::On(expr)) + | sqlparser::ast::JoinOperator::Join(sqlparser::ast::JoinConstraint::On(expr)) = + &join.join_operator + { + Self::validate_expr(expr, &available_aliases, original_sql)?; + } + } + } + + // 4. Validate WHERE conditions + if let Some(selection) = &select.selection { + Self::validate_expr(selection, &available_aliases, original_sql)?; + } + + Ok(()) + } + + fn collect_aliases_from_table_factor(tf: &TableFactor, aliases: &mut HashSet) { + match tf { + TableFactor::Table { name, alias, .. } => { + if let Some(table_alias) = alias { + aliases.insert(table_alias.name.value.clone()); + } else if let Some(last) = name.0.last() { + match last { + sqlparser::ast::ObjectNamePart::Identifier(i) => { + aliases.insert(i.value.clone()); + } + _ => {} + } + } + } + TableFactor::Derived { + subquery, + alias: Some(table_alias), + .. + } => { + aliases.insert(table_alias.name.value.clone()); + // A derived table is technically a nested scope which is opaque outside, but for pure semantic checks + // its internal contents should be validated purely within its own scope (not leaking external aliases in, usually) + // but Postgres allows lateral correlation. We will validate its interior with an empty scope. + let _ = Self::validate_query(subquery, &HashSet::new(), ""); + } + _ => {} + } + } + + fn validate_expr( + expr: &Expr, + available_aliases: &HashSet, + sql: &str, + ) -> Result<(), String> { + match expr { + Expr::CompoundIdentifier(idents) => { + if idents.len() == 2 { + let alias = &idents[0].value; + if !available_aliases.is_empty() && !available_aliases.contains(alias) { + return Err(format!( + "Semantic Error: Orchestrated query referenced table alias '{}' but it was not declared in the query's FROM/JOIN clauses.\nAvailable aliases: {:?}\nSQL: {}", + alias, available_aliases, sql + )); + } + } else if idents.len() > 2 { + let alias = &idents[1].value; // In form schema.table.column, 'table' is idents[1] + if !available_aliases.is_empty() && !available_aliases.contains(alias) { + return Err(format!( + "Semantic Error: Orchestrated query referenced table '{}' but it was not mapped.\nAvailable aliases: {:?}\nSQL: {}", + alias, available_aliases, sql + )); + } + } + } + Expr::Subquery(subquery) => Self::validate_query(subquery, available_aliases, sql)?, + Expr::Exists { subquery, .. } => Self::validate_query(subquery, available_aliases, sql)?, + Expr::InSubquery { + expr: e, subquery, .. + } => { + Self::validate_expr(e, available_aliases, sql)?; + Self::validate_query(subquery, available_aliases, sql)?; + } + Expr::BinaryOp { left, right, .. } => { + Self::validate_expr(left, available_aliases, sql)?; + Self::validate_expr(right, available_aliases, sql)?; + } + Expr::IsFalse(e) + | Expr::IsNotFalse(e) + | Expr::IsTrue(e) + | Expr::IsNotTrue(e) + | Expr::IsNull(e) + | Expr::IsNotNull(e) + | Expr::InList { expr: e, .. } + | Expr::Nested(e) + | Expr::UnaryOp { expr: e, .. } + | Expr::Cast { expr: e, .. } + | Expr::Like { expr: e, .. } + | Expr::ILike { expr: e, .. } + | Expr::AnyOp { left: e, .. } + | Expr::AllOp { left: e, .. } => { + Self::validate_expr(e, available_aliases, sql)?; + } + Expr::Function(func) => { + if let sqlparser::ast::FunctionArguments::List(args) = &func.args { + if let Some(sqlparser::ast::FunctionArg::Unnamed(sqlparser::ast::FunctionArgExpr::Expr( + e, + ))) = args.args.get(0) + { + Self::validate_expr(e, available_aliases, sql)?; + } + } + } + _ => {} + } + Ok(()) + } +} diff --git a/src/tests/types/mod.rs b/src/tests/types/mod.rs index 9b2513b..c6e6b76 100644 --- a/src/tests/types/mod.rs +++ b/src/tests/types/mod.rs @@ -2,6 +2,6 @@ pub mod case; pub mod expect; pub mod suite; -pub use case::TestCase; -pub use expect::ExpectBlock; -pub use suite::TestSuite; +pub use case::Case; +pub use expect::Expect; +pub use suite::Suite; diff --git a/src/tests/types/suite.rs b/src/tests/types/suite.rs index 5aaee7b..4c3c029 100644 --- a/src/tests/types/suite.rs +++ b/src/tests/types/suite.rs @@ -1,10 +1,10 @@ -use super::case::TestCase; +use super::case::Case; use serde::Deserialize; #[derive(Debug, Deserialize)] -pub struct TestSuite { +pub struct Suite { #[allow(dead_code)] pub description: String, pub database: serde_json::Value, - pub tests: Vec, + pub tests: Vec, } diff --git a/t10.json b/t10.json new file mode 100644 index 0000000..d89bae3 --- /dev/null +++ b/t10.json @@ -0,0 +1,54 @@ +[ + [ + "(SELECT jsonb_build_object(", + " 'id', organization_1.id,", + " 'type', CASE", + " WHEN organization_1.type = 'person' THEN", + " ((SELECT jsonb_build_object(", + " 'age', person_3.age,", + " 'archived', entity_5.archived,", + " 'created_at', entity_5.created_at,", + " 'first_name', person_3.first_name,", + " 'id', entity_5.id,", + " 'last_name', person_3.last_name,", + " 'name', entity_5.name,", + " 'type', entity_5.type", + " )", + " FROM agreego.person person_3", + " JOIN agreego.organization organization_4 ON organization_4.id = person_3.id", + " JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id", + " WHERE", + " NOT entity_5.archived))", + " WHEN organization_1.type = 'bot' THEN", + " ((SELECT jsonb_build_object(", + " 'archived', entity_8.archived,", + " 'created_at', entity_8.created_at,", + " 'id', entity_8.id,", + " 'name', entity_8.name,", + " 'token', bot_6.token,", + " 'type', entity_8.type", + " )", + " FROM agreego.bot bot_6", + " JOIN agreego.organization organization_7 ON organization_7.id = bot_6.id", + " JOIN agreego.entity entity_8 ON entity_8.id = organization_7.id", + " WHERE", + " NOT entity_8.archived))", + " WHEN organization_1.type = 'organization' THEN", + " ((SELECT jsonb_build_object(", + " 'archived', entity_10.archived,", + " 'created_at', entity_10.created_at,", + " 'id', entity_10.id,", + " 'name', entity_10.name,", + " 'type', entity_10.type", + " )", + " FROM agreego.organization organization_9", + " JOIN agreego.entity entity_10 ON entity_10.id = organization_9.id", + " WHERE", + " NOT entity_10.archived))", + " ELSE NULL END", + ")", + "FROM agreego.organization organization_1", + "JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id", + "WHERE NOT entity_2.archived)" + ] +] diff --git a/t4.json b/t4.json new file mode 100644 index 0000000..37c4aff --- /dev/null +++ b/t4.json @@ -0,0 +1,164 @@ +[ + [ + "(SELECT jsonb_build_object(", + " 'addresses',", + " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", + " 'archived', entity_6.archived,", + " 'created_at', entity_6.created_at,", + " 'id', entity_6.id,", + " 'is_primary', contact_4.is_primary,", + " 'name', entity_6.name,", + " 'target',", + " (SELECT jsonb_build_object(", + " 'archived', entity_8.archived,", + " 'city', address_7.city,", + " 'created_at', entity_8.created_at,", + " 'id', entity_8.id,", + " 'name', entity_8.name,", + " 'type', entity_8.type", + " )", + " FROM agreego.address address_7", + " JOIN agreego.entity entity_8 ON entity_8.id = address_7.id", + " WHERE", + " NOT entity_8.archived", + " AND relationship_5.target_id = address_7.id),", + " 'type', entity_6.type", + " )), '[]'::jsonb)", + " FROM agreego.contact contact_4", + " JOIN agreego.relationship relationship_5 ON relationship_5.id = contact_4.id", + " JOIN agreego.entity entity_6 ON entity_6.id = relationship_5.id", + " WHERE", + " NOT entity_6.archived", + " AND contact_4.parent_id = entity_3.id),", + " 'age', person_1.age,", + " 'archived', entity_3.archived,", + " 'contacts',", + " (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,", + " 'name', entity_11.name,", + " '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,", + " 'name', entity_17.name,", + " 'type', entity_17.type", + " )", + " FROM agreego.address address_16", + " JOIN agreego.entity entity_17 ON entity_17.id = address_16.id", + " WHERE", + " NOT entity_17.archived", + " AND relationship_10.target_id = address_16.id))", + " WHEN entity_11.target_type = 'email_address' THEN", + " ((SELECT jsonb_build_object(", + " 'address', email_address_14.address,", + " 'archived', entity_15.archived,", + " 'created_at', entity_15.created_at,", + " 'id', entity_15.id,", + " 'name', entity_15.name,", + " 'type', entity_15.type", + " )", + " FROM agreego.email_address email_address_14", + " JOIN agreego.entity entity_15 ON entity_15.id = email_address_14.id", + " WHERE", + " NOT entity_15.archived", + " AND relationship_10.target_id = email_address_14.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,", + " 'name', entity_13.name,", + " 'number', phone_number_12.number,", + " 'type', entity_13.type", + " )", + " FROM agreego.phone_number phone_number_12", + " JOIN agreego.entity entity_13 ON entity_13.id = phone_number_12.id", + " WHERE", + " NOT entity_13.archived", + " AND relationship_10.target_id = phone_number_12.id))", + " ELSE NULL END,", + " '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 contact_9.parent_id = entity_3.id),", + " 'created_at', entity_3.created_at,", + " 'email_addresses',", + " (SELECT COALESCE(jsonb_agg(jsonb_build_object(", + " 'archived', entity_20.archived,", + " 'created_at', entity_20.created_at,", + " 'id', entity_20.id,", + " 'is_primary', contact_18.is_primary,", + " 'name', entity_20.name,", + " 'target',", + " (SELECT jsonb_build_object(", + " 'address', email_address_21.address,", + " 'archived', entity_22.archived,", + " 'created_at', entity_22.created_at,", + " 'id', entity_22.id,", + " 'name', entity_22.name,", + " 'type', entity_22.type", + " )", + " FROM agreego.email_address email_address_21", + " JOIN agreego.entity entity_22 ON entity_22.id = email_address_21.id", + " WHERE", + " NOT entity_22.archived", + " AND relationship_19.target_id = email_address_21.id),", + " 'type', entity_20.type", + " )), '[]'::jsonb)", + " FROM agreego.contact contact_18", + " JOIN agreego.relationship relationship_19 ON relationship_19.id = contact_18.id", + " JOIN agreego.entity entity_20 ON entity_20.id = relationship_19.id", + " WHERE", + " NOT entity_20.archived", + " AND contact_18.parent_id = entity_3.id),", + " 'first_name', person_1.first_name,", + " 'id', entity_3.id,", + " 'last_name', person_1.last_name,", + " 'name', entity_3.name,", + " 'phone_numbers',", + " (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,", + " 'name', entity_25.name,", + " 'target',", + " (SELECT jsonb_build_object(", + " 'archived', entity_27.archived,", + " 'created_at', entity_27.created_at,", + " 'id', entity_27.id,", + " 'name', entity_27.name,", + " 'number', phone_number_26.number,", + " 'type', entity_27.type", + " )", + " FROM agreego.phone_number phone_number_26", + " JOIN agreego.entity entity_27 ON entity_27.id = phone_number_26.id", + " WHERE", + " NOT entity_27.archived", + " AND relationship_24.target_id = phone_number_26.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 contact_23.parent_id = entity_3.id),", + " '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)" + ] +]