This commit is contained in:
2026-04-02 21:55:57 -04:00
parent 29d8dfb608
commit 06f6a587de
21 changed files with 673 additions and 457 deletions

79
fixtures/database.json Normal file
View File

@ -0,0 +1,79 @@
[
{
"description": "ambiguous M:M and missing boundaries",
"database": {
"types": [
{ "name": "unrelated", "schema": {}, "is_enum": false, "parents": [], "variations": ["unrelated"], "has_table": true },
{ "name": "actor", "schema": {}, "is_enum": false, "parents": [], "variations": ["actor"], "has_table": true },
{ "name": "movie", "schema": {}, "is_enum": false, "parents": [], "variations": ["movie"], "has_table": true },
{ "name": "junction", "schema": {}, "is_enum": false, "parents": [], "variations": ["junction"], "has_table": true }
],
"relations": [
{ "constraint": "fk_junction_actor1", "source_type": "junction", "destination_type": "actor", "is_array": true, "prefix": null },
{ "constraint": "fk_junction_actor2", "source_type": "junction", "destination_type": "actor", "is_array": true, "prefix": null }
],
"schemas": [
{
"$id": "get.actor",
"properties": {
"missing_edge": { "$ref": "unrelated" },
"ambiguous_edge": { "$ref": "junction" }
}
}
]
},
"tests": [
{
"description": "throws EDGE_MISSING when relation does not exist",
"action": "database_compile",
"expect": {
"success": false,
"errors": [
{
"code": "EDGE_MISSING"
}
]
}
},
{
"description": "throws AMBIGUOUS_TYPE_RELATIONS when junction has multi null prefixes",
"action": "database_compile",
"expect": {
"success": false,
"errors": [
{
"code": "AMBIGUOUS_TYPE_RELATIONS"
}
]
}
}
]
},
{
"description": "invalid metadata identifiers",
"database": {
"types": [],
"relations": [],
"schemas": [
{
"$id": "invalid@id",
"properties": {}
}
]
},
"tests": [
{
"description": "throws INVALID_IDENTIFIER for non-alphanumeric ids",
"action": "database_compile",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_IDENTIFIER"
}
]
}
}
]
}
]

View File

@ -154,7 +154,7 @@
"success": false,
"errors": [
{
"code": "FAMILY_MISMATCH",
"code": "NO_FAMILY_MATCH",
"path": ""
}
]

View File

@ -47,7 +47,7 @@
"success": false,
"errors": [
{
"code": "TYPE_MISMATCH",
"code": "INVALID_TYPE",
"path": "base_prop"
}
]
@ -195,8 +195,8 @@
"success": false,
"errors": [
{
"code": "DEPENDENCY_FAILED",
"path": "base_dep"
"code": "DEPENDENCY_MISSING",
"path": ""
}
]
}
@ -213,8 +213,8 @@
"success": false,
"errors": [
{
"code": "DEPENDENCY_FAILED",
"path": "child_dep"
"code": "DEPENDENCY_MISSING",
"path": ""
}
]
}

View File

@ -4,20 +4,32 @@
"database": {
"puncs": [
{
"name": "get_entities",
"name": "get_organization",
"schemas": [
{
"$id": "get_entities.response",
"$family": "organization"
"$id": "get_organization.response",
"$ref": "organization"
}
]
},
{
"name": "get_persons",
"name": "get_organizations",
"schemas": [
{
"$id": "get_persons.response",
"$family": "base.person"
"$id": "get_organizations.response",
"type": "array",
"items": {
"$family": "organization"
}
}
]
},
{
"name": "get_person",
"schemas": [
{
"$id": "get_person.response",
"$family": "person"
}
]
},
@ -122,14 +134,12 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
]
},
"field_types": {
"id": "uuid",
"name": "text",
"archived": "boolean",
"created_at": "timestamptz",
"type": "text"
@ -146,9 +156,6 @@
"type": {
"type": "string"
},
"name": {
"type": "string"
},
"archived": {
"type": "boolean"
},
@ -165,7 +172,6 @@
"fields": [
"id",
"type",
"name",
"archived",
"created_at"
],
@ -200,11 +206,12 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
],
"organization": []
"organization": [
"name"
]
},
"field_types": {
"id": "uuid",
@ -227,6 +234,17 @@
"bot",
"organization",
"person"
],
"schemas": [
{
"$id": "organization",
"$ref": "entity",
"properties": {
"name": {
"type": "string"
}
}
}
]
},
{
@ -248,11 +266,12 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
],
"organization": [],
"organization": [
"name"
],
"bot": [
"token"
]
@ -301,11 +320,12 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
],
"organization": [],
"organization": [
"name"
],
"person": [
"first_name",
"last_name",
@ -324,7 +344,7 @@
},
"schemas": [
{
"$id": "base.person",
"$id": "person",
"$ref": "organization",
"properties": {
"first_name": {
@ -340,12 +360,12 @@
},
{
"$id": "light.person",
"$ref": "base.person",
"$ref": "person",
"properties": {}
},
{
"$id": "full.person",
"$ref": "base.person",
"$ref": "person",
"properties": {
"phone_numbers": {
"type": "array",
@ -422,7 +442,6 @@
"target_type",
"id",
"type",
"name",
"archived",
"created_at"
],
@ -430,7 +449,6 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
],
@ -449,7 +467,6 @@
"source_type": "text",
"target_id": "uuid",
"target_type": "text",
"name": "text",
"created_at": "timestamptz"
},
"schemas": [
@ -480,7 +497,6 @@
"target_type",
"id",
"type",
"name",
"archived",
"created_at"
],
@ -488,7 +504,6 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
],
@ -511,7 +526,6 @@
"target_id": "uuid",
"target_type": "text",
"is_primary": "boolean",
"name": "text",
"created_at": "timestamptz"
},
"schemas": [
@ -539,7 +553,6 @@
"number",
"id",
"type",
"name",
"archived",
"created_at"
],
@ -547,7 +560,6 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
],
@ -560,7 +572,6 @@
"type": "text",
"archived": "boolean",
"number": "text",
"name": "text",
"created_at": "timestamptz"
},
"schemas": [
@ -588,7 +599,6 @@
"address",
"id",
"type",
"name",
"archived",
"created_at"
],
@ -596,7 +606,6 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
],
@ -609,7 +618,6 @@
"type": "text",
"archived": "boolean",
"address": "text",
"name": "text",
"created_at": "timestamptz"
},
"schemas": [
@ -637,7 +645,6 @@
"city",
"id",
"type",
"name",
"archived",
"created_at"
],
@ -645,7 +652,6 @@
"entity": [
"id",
"type",
"name",
"archived",
"created_at"
],
@ -658,7 +664,6 @@
"type": "text",
"archived": "boolean",
"city": "text",
"name": "text",
"created_at": "timestamptz"
},
"schemas": [
@ -696,7 +701,7 @@
"$ref": "order",
"properties": {
"customer": {
"$ref": "base.person"
"$ref": "person"
}
}
},
@ -705,7 +710,7 @@
"$ref": "order",
"properties": {
"customer": {
"$ref": "base.person"
"$ref": "person"
},
"lines": {
"type": "array",
@ -723,7 +728,6 @@
"fields": [
"id",
"type",
"name",
"total",
"customer_id",
"created_at",
@ -746,7 +750,6 @@
"entity": [
"id",
"type",
"name",
"created_at",
"created_by",
"modified_at",
@ -762,7 +765,6 @@
"field_types": {
"id": "uuid",
"type": "text",
"name": "text",
"archived": "boolean",
"total": "numeric",
"customer_id": "uuid",
@ -803,7 +805,6 @@
"fields": [
"id",
"type",
"name",
"order_id",
"product",
"price",
@ -824,7 +825,6 @@
"entity": [
"id",
"type",
"name",
"created_at",
"created_by",
"modified_at",
@ -838,7 +838,6 @@
"field_types": {
"id": "uuid",
"type": "text",
"name": "text",
"archived": "boolean",
"order_id": "uuid",
"product": "text",
@ -852,31 +851,6 @@
"order_line"
]
}
],
"schemas": [
{
"$id": "entity",
"type": "object",
"properties": {}
},
{
"$id": "organization",
"type": "object",
"$ref": "entity",
"properties": {}
},
{
"$id": "bot",
"type": "object",
"$ref": "bot",
"properties": {}
},
{
"$id": "person",
"type": "object",
"$ref": "base.person",
"properties": {}
}
]
},
"tests": [
@ -892,7 +866,6 @@
" '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)"
@ -915,22 +888,6 @@
"123e4567-e89b-12d3-a456-426614174001"
]
},
"name": {
"$eq": "Jane%",
"$ne": "John%",
"$gt": "A",
"$gte": "B",
"$lt": "Z",
"$lte": "Y",
"$in": [
"Jane",
"John"
],
"$nin": [
"Bob",
"Alice"
]
},
"created_at": {
"$eq": "2023-01-01T00:00:00Z",
"$ne": "2023-01-02T00:00:00Z",
@ -952,7 +909,6 @@
" '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",
@ -970,14 +926,6 @@
" 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))",
")"
]
]
@ -986,7 +934,7 @@
{
"description": "Person select on base schema",
"action": "query",
"schema_id": "base.person",
"schema_id": "person",
"expect": {
"success": true,
"sql": [
@ -998,7 +946,7 @@
" 'first_name', person_1.first_name,",
" 'id', entity_3.id,",
" 'last_name', person_1.last_name,",
" 'name', entity_3.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",
@ -1023,14 +971,12 @@
" '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",
@ -1055,7 +1001,6 @@
" '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(",
@ -1063,7 +1008,6 @@
" '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",
@ -1077,7 +1021,6 @@
" '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",
@ -1090,7 +1033,6 @@
" '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",
" )",
@ -1115,14 +1057,12 @@
" '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",
@ -1142,20 +1082,18 @@
" 'first_name', person_1.first_name,",
" 'id', entity_3.id,",
" 'last_name', person_1.last_name,",
" 'name', entity_3.name,",
" 'name', organization_2.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",
" )",
@ -1208,8 +1146,10 @@
"$eq": true,
"$ne": false
},
"contacts/is_primary": {
"$eq": true
"contacts": {
"is_primary": {
"$eq": true
}
},
"created_at": {
"$eq": "2020-01-01T00:00:00Z",
@ -1248,8 +1188,12 @@
"$eq": "%Doe%",
"$ne": "%Smith%"
},
"phone_numbers/target/number": {
"$eq": "555-1234"
"phone_numbers": {
"target": {
"number": {
"$eq": "555-1234"
}
}
}
},
"expect": {
@ -1263,14 +1207,12 @@
" '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",
@ -1295,7 +1237,6 @@
" '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(",
@ -1303,7 +1244,6 @@
" '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",
@ -1317,7 +1257,6 @@
" '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",
@ -1330,7 +1269,6 @@
" '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",
" )",
@ -1356,14 +1294,12 @@
" '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",
@ -1383,20 +1319,18 @@
" 'first_name', person_1.first_name,",
" 'id', entity_3.id,",
" 'last_name', person_1.last_name,",
" 'name', entity_3.name,",
" 'name', organization_2.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",
" )",
@ -1469,14 +1403,12 @@
" '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(",
" 'address', email_address_4.address,",
" 'archived', entity_5.archived,",
" 'created_at', entity_5.created_at,",
" 'id', entity_5.id,",
" 'name', entity_5.name,",
" 'type', entity_5.type",
" )",
" FROM agreego.email_address email_address_4",
@ -1515,7 +1447,7 @@
" 'first_name', person_3.first_name,",
" 'id', entity_5.id,",
" 'last_name', person_3.last_name,",
" 'name', entity_5.name,",
" 'name', organization_4.name,",
" 'type', entity_5.type",
" )",
" FROM agreego.person person_3",
@ -1531,7 +1463,6 @@
" '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,",
@ -1542,7 +1473,6 @@
" WHERE",
" 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",
")",
@ -1554,14 +1484,36 @@
}
},
{
"description": "Base entity family select on polymorphic tree",
"description": "Organization select via a punc response with ref",
"action": "query",
"schema_id": "get_entities.response",
"schema_id": "get_organization.response",
"expect": {
"success": true,
"sql": [
[
"(SELECT jsonb_build_object(",
" 'archived', entity_2.archived,",
" 'created_at', entity_2.created_at,",
" 'id', entity_2.id,",
" 'name', organization_1.name,",
" 'type', entity_2.type",
")",
"FROM agreego.organization organization_1",
"JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id",
"WHERE NOT entity_2.archived)"
]
]
}
},
{
"description": "Organizations select via a punc response with family",
"action": "query",
"schema_id": "get_organizations.response",
"expect": {
"success": true,
"sql": [
[
"(SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'id', organization_1.id,",
" 'type', CASE",
" WHEN organization_1.type = 'bot' THEN",
@ -1569,7 +1521,7 @@
" 'archived', entity_5.archived,",
" 'created_at', entity_5.created_at,",
" 'id', entity_5.id,",
" 'name', entity_5.name,",
" 'name', organization_4.name,",
" 'token', bot_3.token,",
" 'type', entity_5.type",
" )",
@ -1582,7 +1534,7 @@
" 'archived', entity_7.archived,",
" 'created_at', entity_7.created_at,",
" 'id', entity_7.id,",
" 'name', entity_7.name,",
" 'name', organization_6.name,",
" 'type', entity_7.type",
" )",
" FROM agreego.organization organization_6",
@ -1596,7 +1548,7 @@
" 'first_name', person_8.first_name,",
" 'id', entity_10.id,",
" 'last_name', person_8.last_name,",
" 'name', entity_10.name,",
" 'name', organization_9.name,",
" 'type', entity_10.type",
" )",
" FROM agreego.person person_8",
@ -1604,7 +1556,7 @@
" 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)"
@ -1613,7 +1565,33 @@
}
},
{
"description": "Root Array SQL evaluation for Order fetching Light Order",
"description": "Person select via a punc response with family",
"action": "query",
"schema_id": "get_person.response",
"expect": {
"success": true,
"sql": [
[
"(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)"
]
]
}
},
{
"description": "Orders select via a punc with items",
"action": "query",
"schema_id": "get_orders.response",
"expect": {
@ -1631,7 +1609,7 @@
" 'first_name', person_3.first_name,",
" 'id', entity_5.id,",
" 'last_name', person_3.last_name,",
" 'name', entity_5.name,",
" 'name', organization_4.name,",
" 'type', entity_5.type",
" )",
" FROM agreego.person person_3",
@ -1642,7 +1620,6 @@
" AND order_1.customer_id = person_3.id),",
" 'customer_id', order_1.customer_id,",
" 'id', entity_2.id,",
" 'name', entity_2.name,",
" 'total', order_1.total,",
" 'type', entity_2.type",
")), '[]'::jsonb)",

View File

@ -676,7 +676,7 @@
"success": false,
"errors": [
{
"code": "TYPE_MISMATCH",
"code": "CONST_VIOLATED",
"path": "type"
}
]
@ -781,7 +781,7 @@
"success": false,
"errors": [
{
"code": "TYPE_MISMATCH",
"code": "CONST_VIOLATED",
"path": "type"
}
]