Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3de3961c1 | |||
| d0ae6eeddd | |||
| e741a7197f | |||
| cf3cabd1f5 | |||
| d608f5e963 |
@ -87,6 +87,34 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"schedule": {
|
||||||
|
"type": [
|
||||||
|
"opening_hours",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opening_hours": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"open": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"seasons": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "season"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"season": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"label": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -483,7 +511,9 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"opening_hours": {},
|
||||||
|
"season": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -365,7 +365,10 @@
|
|||||||
"name",
|
"name",
|
||||||
"age",
|
"age",
|
||||||
"archived",
|
"archived",
|
||||||
"created_at"
|
"created_at",
|
||||||
|
"rating",
|
||||||
|
"tags",
|
||||||
|
"labels"
|
||||||
],
|
],
|
||||||
"grouped_fields": {
|
"grouped_fields": {
|
||||||
"entity": [
|
"entity": [
|
||||||
@ -380,7 +383,10 @@
|
|||||||
"person": [
|
"person": [
|
||||||
"first_name",
|
"first_name",
|
||||||
"last_name",
|
"last_name",
|
||||||
"age"
|
"age",
|
||||||
|
"rating",
|
||||||
|
"tags",
|
||||||
|
"labels"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"field_types": {
|
"field_types": {
|
||||||
@ -391,7 +397,10 @@
|
|||||||
"last_name": "text",
|
"last_name": "text",
|
||||||
"name": "text",
|
"name": "text",
|
||||||
"age": "numeric",
|
"age": "numeric",
|
||||||
"created_at": "timestamptz"
|
"created_at": "timestamptz",
|
||||||
|
"rating": "float8",
|
||||||
|
"tags": "jsonb",
|
||||||
|
"labels": "jsonb"
|
||||||
},
|
},
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"person": {
|
"person": {
|
||||||
@ -405,6 +414,25 @@
|
|||||||
},
|
},
|
||||||
"age": {
|
"age": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": [
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -416,6 +444,25 @@
|
|||||||
},
|
},
|
||||||
"last_name": {
|
"last_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": [
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -475,6 +522,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": [
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1290,7 +1356,10 @@
|
|||||||
" 'name', organization_2.name,",
|
" 'name', organization_2.name,",
|
||||||
" 'first_name', person_3.first_name,",
|
" 'first_name', person_3.first_name,",
|
||||||
" 'last_name', person_3.last_name,",
|
" 'last_name', person_3.last_name,",
|
||||||
" 'age', person_3.age",
|
" 'age', person_3.age,",
|
||||||
|
" 'rating', person_3.rating,",
|
||||||
|
" 'tags', person_3.tags,",
|
||||||
|
" 'labels', person_3.labels",
|
||||||
" )",
|
" )",
|
||||||
" FROM agreego.entity entity_1",
|
" FROM agreego.entity entity_1",
|
||||||
" JOIN agreego.organization organization_2",
|
" JOIN agreego.organization organization_2",
|
||||||
@ -1320,6 +1389,9 @@
|
|||||||
" 'first_name', person_3.first_name,",
|
" 'first_name', person_3.first_name,",
|
||||||
" 'last_name', person_3.last_name,",
|
" 'last_name', person_3.last_name,",
|
||||||
" 'age', person_3.age,",
|
" 'age', person_3.age,",
|
||||||
|
" 'rating', person_3.rating,",
|
||||||
|
" 'tags', person_3.tags,",
|
||||||
|
" 'labels', person_3.labels,",
|
||||||
" 'phone_numbers', (",
|
" 'phone_numbers', (",
|
||||||
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||||
" 'id', entity_4.id,",
|
" 'id', entity_4.id,",
|
||||||
@ -1570,6 +1642,9 @@
|
|||||||
" 'first_name', person_3.first_name,",
|
" 'first_name', person_3.first_name,",
|
||||||
" 'last_name', person_3.last_name,",
|
" 'last_name', person_3.last_name,",
|
||||||
" 'age', person_3.age,",
|
" 'age', person_3.age,",
|
||||||
|
" 'rating', person_3.rating,",
|
||||||
|
" 'tags', person_3.tags,",
|
||||||
|
" 'labels', person_3.labels,",
|
||||||
" 'phone_numbers', (",
|
" 'phone_numbers', (",
|
||||||
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||||
" 'id', entity_4.id,",
|
" 'id', entity_4.id,",
|
||||||
@ -1829,7 +1904,10 @@
|
|||||||
" 'name', organization_4.name,",
|
" 'name', organization_4.name,",
|
||||||
" 'first_name', person_5.first_name,",
|
" 'first_name', person_5.first_name,",
|
||||||
" 'last_name', person_5.last_name,",
|
" 'last_name', person_5.last_name,",
|
||||||
" 'age', person_5.age",
|
" 'age', person_5.age,",
|
||||||
|
" 'rating', person_5.rating,",
|
||||||
|
" 'tags', person_5.tags,",
|
||||||
|
" 'labels', person_5.labels",
|
||||||
" )",
|
" )",
|
||||||
" FROM agreego.entity entity_3",
|
" FROM agreego.entity entity_3",
|
||||||
" JOIN agreego.organization organization_4",
|
" JOIN agreego.organization organization_4",
|
||||||
@ -1939,7 +2017,10 @@
|
|||||||
" 'name', organization_9.name,",
|
" 'name', organization_9.name,",
|
||||||
" 'first_name', person_10.first_name,",
|
" 'first_name', person_10.first_name,",
|
||||||
" 'last_name', person_10.last_name,",
|
" 'last_name', person_10.last_name,",
|
||||||
" 'age', person_10.age",
|
" 'age', person_10.age,",
|
||||||
|
" 'rating', person_10.rating,",
|
||||||
|
" 'tags', person_10.tags,",
|
||||||
|
" 'labels', person_10.labels",
|
||||||
" )",
|
" )",
|
||||||
" FROM agreego.entity entity_8",
|
" FROM agreego.entity entity_8",
|
||||||
" JOIN agreego.organization organization_9",
|
" JOIN agreego.organization organization_9",
|
||||||
@ -1993,7 +2074,10 @@
|
|||||||
" 'created_at', entity_6.created_at,",
|
" 'created_at', entity_6.created_at,",
|
||||||
" 'name', organization_7.name,",
|
" 'name', organization_7.name,",
|
||||||
" 'first_name', person_8.first_name,",
|
" 'first_name', person_8.first_name,",
|
||||||
" 'last_name', person_8.last_name",
|
" 'last_name', person_8.last_name,",
|
||||||
|
" 'rating', person_8.rating,",
|
||||||
|
" 'tags', person_8.tags,",
|
||||||
|
" 'labels', person_8.labels",
|
||||||
" )",
|
" )",
|
||||||
" FROM agreego.entity entity_6",
|
" FROM agreego.entity entity_6",
|
||||||
" JOIN agreego.organization organization_7",
|
" JOIN agreego.organization organization_7",
|
||||||
@ -2033,6 +2117,9 @@
|
|||||||
" 'first_name', person_5.first_name,",
|
" 'first_name', person_5.first_name,",
|
||||||
" 'last_name', person_5.last_name,",
|
" 'last_name', person_5.last_name,",
|
||||||
" 'age', person_5.age,",
|
" 'age', person_5.age,",
|
||||||
|
" 'rating', person_5.rating,",
|
||||||
|
" 'tags', person_5.tags,",
|
||||||
|
" 'labels', person_5.labels,",
|
||||||
" 'phone_numbers', (",
|
" 'phone_numbers', (",
|
||||||
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||||
" 'id', entity_6.id,",
|
" 'id', entity_6.id,",
|
||||||
@ -2227,7 +2314,10 @@
|
|||||||
" 'name', organization_4.name,",
|
" 'name', organization_4.name,",
|
||||||
" 'first_name', person_5.first_name,",
|
" 'first_name', person_5.first_name,",
|
||||||
" 'last_name', person_5.last_name,",
|
" 'last_name', person_5.last_name,",
|
||||||
" 'age', person_5.age",
|
" 'age', person_5.age,",
|
||||||
|
" 'rating', person_5.rating,",
|
||||||
|
" 'tags', person_5.tags,",
|
||||||
|
" 'labels', person_5.labels",
|
||||||
" )",
|
" )",
|
||||||
" FROM agreego.entity entity_3",
|
" FROM agreego.entity entity_3",
|
||||||
" JOIN agreego.organization organization_4",
|
" JOIN agreego.organization organization_4",
|
||||||
@ -2410,7 +2500,10 @@
|
|||||||
" 'name', organization_11.name,",
|
" 'name', organization_11.name,",
|
||||||
" 'first_name', person_12.first_name,",
|
" 'first_name', person_12.first_name,",
|
||||||
" 'last_name', person_12.last_name,",
|
" 'last_name', person_12.last_name,",
|
||||||
" 'age', person_12.age",
|
" 'age', person_12.age,",
|
||||||
|
" 'rating', person_12.rating,",
|
||||||
|
" 'tags', person_12.tags,",
|
||||||
|
" 'labels', person_12.labels",
|
||||||
" )",
|
" )",
|
||||||
" FROM agreego.entity entity_10",
|
" FROM agreego.entity entity_10",
|
||||||
" JOIN agreego.organization organization_11",
|
" JOIN agreego.organization organization_11",
|
||||||
@ -2437,6 +2530,207 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Person filters: float8 comparison and jsonb array containment",
|
||||||
|
"action": "query",
|
||||||
|
"schema_id": "full.person",
|
||||||
|
"filter": {
|
||||||
|
"rating": {
|
||||||
|
"$gte": 4.0
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"$eq": "landscaping",
|
||||||
|
"$of": [
|
||||||
|
"plumbing",
|
||||||
|
"electrical"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"$eq": "vip"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"success": true,
|
||||||
|
"sql": [
|
||||||
|
[
|
||||||
|
"((SELECT jsonb_strip_nulls((",
|
||||||
|
" SELECT jsonb_build_object(",
|
||||||
|
" 'id', entity_1.id,",
|
||||||
|
" 'type', entity_1.type,",
|
||||||
|
" 'archived', entity_1.archived,",
|
||||||
|
" 'created_at', entity_1.created_at,",
|
||||||
|
" 'name', organization_2.name,",
|
||||||
|
" 'first_name', person_3.first_name,",
|
||||||
|
" 'last_name', person_3.last_name,",
|
||||||
|
" 'age', person_3.age,",
|
||||||
|
" 'rating', person_3.rating,",
|
||||||
|
" 'tags', person_3.tags,",
|
||||||
|
" 'labels', person_3.labels,",
|
||||||
|
" 'phone_numbers', (",
|
||||||
|
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||||
|
" 'id', entity_4.id,",
|
||||||
|
" 'type', entity_4.type,",
|
||||||
|
" 'archived', entity_4.archived,",
|
||||||
|
" 'created_at', entity_4.created_at,",
|
||||||
|
" 'is_primary', contact_6.is_primary,",
|
||||||
|
" 'target', (",
|
||||||
|
" SELECT jsonb_build_object(",
|
||||||
|
" 'id', entity_7.id,",
|
||||||
|
" 'type', entity_7.type,",
|
||||||
|
" 'archived', entity_7.archived,",
|
||||||
|
" 'created_at', entity_7.created_at,",
|
||||||
|
" 'number', phone_number_8.number",
|
||||||
|
" )",
|
||||||
|
" FROM agreego.entity entity_7",
|
||||||
|
" JOIN agreego.phone_number phone_number_8",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_7.archived",
|
||||||
|
" AND relationship_5.target_id = entity_7.id",
|
||||||
|
" )",
|
||||||
|
" )), '[]'::jsonb)",
|
||||||
|
" FROM agreego.entity entity_4",
|
||||||
|
" JOIN agreego.relationship relationship_5",
|
||||||
|
" JOIN agreego.contact contact_6",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_4.archived",
|
||||||
|
" AND relationship_5.target_type = 'phone_number'",
|
||||||
|
" AND relationship_5.source_id = entity_1.id",
|
||||||
|
" ),",
|
||||||
|
" 'email_addresses', (",
|
||||||
|
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||||
|
" 'id', entity_9.id,",
|
||||||
|
" 'type', entity_9.type,",
|
||||||
|
" 'archived', entity_9.archived,",
|
||||||
|
" 'created_at', entity_9.created_at,",
|
||||||
|
" 'is_primary', contact_11.is_primary,",
|
||||||
|
" 'target', (",
|
||||||
|
" SELECT jsonb_build_object(",
|
||||||
|
" 'id', entity_12.id,",
|
||||||
|
" 'type', entity_12.type,",
|
||||||
|
" 'archived', entity_12.archived,",
|
||||||
|
" 'created_at', entity_12.created_at,",
|
||||||
|
" 'address', email_address_13.address",
|
||||||
|
" )",
|
||||||
|
" FROM agreego.entity entity_12",
|
||||||
|
" JOIN agreego.email_address email_address_13",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_12.archived",
|
||||||
|
" AND relationship_10.target_id = entity_12.id",
|
||||||
|
" )",
|
||||||
|
" )), '[]'::jsonb)",
|
||||||
|
" FROM agreego.entity entity_9",
|
||||||
|
" JOIN agreego.relationship relationship_10",
|
||||||
|
" JOIN agreego.contact contact_11",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_9.archived",
|
||||||
|
" AND relationship_10.target_type = 'email_address'",
|
||||||
|
" AND relationship_10.source_id = entity_1.id",
|
||||||
|
" ),",
|
||||||
|
" 'addresses', (",
|
||||||
|
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||||
|
" 'id', entity_14.id,",
|
||||||
|
" 'type', entity_14.type,",
|
||||||
|
" 'archived', entity_14.archived,",
|
||||||
|
" 'created_at', entity_14.created_at,",
|
||||||
|
" 'is_primary', contact_16.is_primary,",
|
||||||
|
" 'target', (",
|
||||||
|
" SELECT jsonb_build_object(",
|
||||||
|
" 'id', entity_17.id,",
|
||||||
|
" 'type', entity_17.type,",
|
||||||
|
" 'archived', entity_17.archived,",
|
||||||
|
" 'created_at', entity_17.created_at,",
|
||||||
|
" 'city', address_18.city",
|
||||||
|
" )",
|
||||||
|
" FROM agreego.entity entity_17",
|
||||||
|
" JOIN agreego.address address_18",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_17.archived",
|
||||||
|
" AND relationship_15.target_id = entity_17.id",
|
||||||
|
" )",
|
||||||
|
" )), '[]'::jsonb)",
|
||||||
|
" FROM agreego.entity entity_14",
|
||||||
|
" JOIN agreego.relationship relationship_15",
|
||||||
|
" JOIN agreego.contact contact_16",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_14.archived",
|
||||||
|
" AND relationship_15.target_type = 'address'",
|
||||||
|
" AND relationship_15.source_id = entity_1.id",
|
||||||
|
" ),",
|
||||||
|
" 'contacts', (",
|
||||||
|
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||||
|
" 'id', entity_19.id,",
|
||||||
|
" 'type', entity_19.type,",
|
||||||
|
" 'archived', entity_19.archived,",
|
||||||
|
" 'created_at', entity_19.created_at,",
|
||||||
|
" 'is_primary', contact_21.is_primary,",
|
||||||
|
" 'target', CASE",
|
||||||
|
" WHEN relationship_20.target_type = 'phone_number' THEN ((",
|
||||||
|
" SELECT jsonb_build_object(",
|
||||||
|
" 'id', entity_22.id,",
|
||||||
|
" 'type', entity_22.type,",
|
||||||
|
" 'archived', entity_22.archived,",
|
||||||
|
" 'created_at', entity_22.created_at,",
|
||||||
|
" 'number', phone_number_23.number",
|
||||||
|
" )",
|
||||||
|
" FROM agreego.entity entity_22",
|
||||||
|
" JOIN agreego.phone_number phone_number_23",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_22.archived",
|
||||||
|
" AND relationship_20.target_id = entity_22.id",
|
||||||
|
" ))",
|
||||||
|
" WHEN relationship_20.target_type = 'email_address' THEN ((",
|
||||||
|
" SELECT jsonb_build_object(",
|
||||||
|
" 'id', entity_24.id,",
|
||||||
|
" 'type', entity_24.type,",
|
||||||
|
" 'archived', entity_24.archived,",
|
||||||
|
" 'created_at', entity_24.created_at,",
|
||||||
|
" 'address', email_address_25.address",
|
||||||
|
" )",
|
||||||
|
" FROM agreego.entity entity_24",
|
||||||
|
" JOIN agreego.email_address email_address_25",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_24.archived",
|
||||||
|
" AND relationship_20.target_id = entity_24.id",
|
||||||
|
" ))",
|
||||||
|
" WHEN relationship_20.target_type = 'address' THEN ((",
|
||||||
|
" SELECT jsonb_build_object(",
|
||||||
|
" 'id', entity_26.id,",
|
||||||
|
" 'type', entity_26.type,",
|
||||||
|
" 'archived', entity_26.archived,",
|
||||||
|
" 'created_at', entity_26.created_at,",
|
||||||
|
" 'city', address_27.city",
|
||||||
|
" )",
|
||||||
|
" FROM agreego.entity entity_26",
|
||||||
|
" JOIN agreego.address address_27",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_26.archived",
|
||||||
|
" AND relationship_20.target_id = entity_26.id",
|
||||||
|
" ))",
|
||||||
|
" ELSE NULL",
|
||||||
|
" END",
|
||||||
|
" )), '[]'::jsonb)",
|
||||||
|
" FROM agreego.entity entity_19",
|
||||||
|
" JOIN agreego.relationship relationship_20",
|
||||||
|
" JOIN agreego.contact contact_21",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_19.archived",
|
||||||
|
" AND relationship_20.source_id = entity_1.id",
|
||||||
|
" )",
|
||||||
|
" )",
|
||||||
|
" FROM agreego.entity entity_1",
|
||||||
|
" JOIN agreego.organization organization_2",
|
||||||
|
" JOIN agreego.person person_3",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_1.archived",
|
||||||
|
" AND person_3.labels ? ($1 #>> '{)",
|
||||||
|
" AND person_3.rating >= ($2 #>> '{}')::NUMERIC",
|
||||||
|
" AND person_3.tags ? ($3 #>> '{)",
|
||||||
|
" AND person_3.tags ?| ARRAY()",
|
||||||
|
" ))))"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -165,8 +165,16 @@ impl Schema {
|
|||||||
} else if db.enums.contains_key(custom) {
|
} else if db.enums.contains_key(custom) {
|
||||||
Some(vec![format!("{}.condition", custom)])
|
Some(vec![format!("{}.condition", custom)])
|
||||||
} else {
|
} else {
|
||||||
// Assume anything else is a Relational cross-boundary that already has its own .filter dynamically built
|
// Only a Table-Backed boundary has a synthesized Composed Filter to proxy to.
|
||||||
|
// A Field-Backed JSONB Bubble has none — omit it like an inline object rather
|
||||||
|
// than emit a dangling proxy reference, which breaks eager consumers of the
|
||||||
|
// exported registry (downstream code generators).
|
||||||
|
let base = custom.split('.').next_back().unwrap_or(custom);
|
||||||
|
if db.types.contains_key(base) {
|
||||||
Some(vec![format!("{}.filter", custom)])
|
Some(vec![format!("{}.filter", custom)])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -549,7 +549,7 @@ impl<'a> Compiler<'a> {
|
|||||||
where_clauses.push(format!("NOT {}.archived", entity_alias));
|
where_clauses.push(format!("NOT {}.archived", entity_alias));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
|
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses)?;
|
||||||
self.compile_polymorphic_bounds(r#type, type_aliases, &node, &mut where_clauses);
|
self.compile_polymorphic_bounds(r#type, type_aliases, &node, &mut where_clauses);
|
||||||
|
|
||||||
let start_len = where_clauses.len();
|
let start_len = where_clauses.len();
|
||||||
@ -689,6 +689,11 @@ impl<'a> Compiler<'a> {
|
|||||||
|| pg_type.contains("int")
|
|| pg_type.contains("int")
|
||||||
|| pg_type == "real"
|
|| pg_type == "real"
|
||||||
|| pg_type == "double precision"
|
|| pg_type == "double precision"
|
||||||
|
// pg catalog typnames for real / double precision — the SQL
|
||||||
|
// names above never appear in field_types (they come from
|
||||||
|
// pg_type.typname), so these are what actually arrives
|
||||||
|
|| pg_type == "float4"
|
||||||
|
|| pg_type == "float8"
|
||||||
{
|
{
|
||||||
cast = "::numeric";
|
cast = "::numeric";
|
||||||
} else if pg_type == "text" || pg_type.contains("char") {
|
} else if pg_type == "text" || pg_type.contains("char") {
|
||||||
@ -715,7 +720,7 @@ impl<'a> Compiler<'a> {
|
|||||||
node: &Node,
|
node: &Node,
|
||||||
base_alias: &str,
|
base_alias: &str,
|
||||||
where_clauses: &mut Vec<String>,
|
where_clauses: &mut Vec<String>,
|
||||||
) {
|
) -> Result<(), String> {
|
||||||
for (i, filter_key) in self.filter_keys.iter().enumerate() {
|
for (i, filter_key) in self.filter_keys.iter().enumerate() {
|
||||||
let mut parts = filter_key.split(':');
|
let mut parts = filter_key.split(':');
|
||||||
let full_field_path = parts.next().unwrap_or(filter_key);
|
let full_field_path = parts.next().unwrap_or(filter_key);
|
||||||
@ -745,6 +750,83 @@ impl<'a> Compiler<'a> {
|
|||||||
let param_index = i + 1;
|
let param_index = i + 1;
|
||||||
let p_val = format!("${}#>>'{{}}'", param_index);
|
let p_val = format!("${}#>>'{{}}'", param_index);
|
||||||
|
|
||||||
|
// jsonb columns never type-check against the text parameter, and for
|
||||||
|
// array-valued properties (tag lists) the meaning of a condition is
|
||||||
|
// CONTAINMENT, not equality. Compile them explicitly; reject the rest
|
||||||
|
// loudly at compile time instead of failing at execution.
|
||||||
|
let is_jsonb = r#type
|
||||||
|
.field_types
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.as_object())
|
||||||
|
.and_then(|ft| ft.get(field_name))
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
== Some("jsonb");
|
||||||
|
if is_jsonb {
|
||||||
|
// Compiled schemas normalize to the list form ("type": ["array"]),
|
||||||
|
// hand-written ones may use the single form — accept both
|
||||||
|
let is_array_prop = node
|
||||||
|
.schema
|
||||||
|
.obj
|
||||||
|
.properties
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|p| p.get(field_name))
|
||||||
|
.map(|ps| match &ps.obj.type_ {
|
||||||
|
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => t == "array",
|
||||||
|
Some(crate::database::object::SchemaTypeOrArray::Multiple(ts)) => {
|
||||||
|
ts.iter().any(|t| t == "array") && ts.iter().all(|t| t == "array" || t == "null")
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if is_array_prop {
|
||||||
|
match op {
|
||||||
|
// "the array contains this value"
|
||||||
|
"$eq" => where_clauses.push(format!(
|
||||||
|
"{}.{} ? ({})",
|
||||||
|
filter_alias, field_name, p_val
|
||||||
|
)),
|
||||||
|
"$ne" => where_clauses.push(format!(
|
||||||
|
"NOT ({}.{} ? ({}))",
|
||||||
|
filter_alias, field_name, p_val
|
||||||
|
)),
|
||||||
|
// "the array contains ANY of these values"
|
||||||
|
"$of" => where_clauses.push(format!(
|
||||||
|
"{}.{} ?| ARRAY(SELECT jsonb_array_elements_text(({})::jsonb))",
|
||||||
|
filter_alias, field_name, p_val
|
||||||
|
)),
|
||||||
|
"$nof" => where_clauses.push(format!(
|
||||||
|
"NOT ({}.{} ?| ARRAY(SELECT jsonb_array_elements_text(({})::jsonb)))",
|
||||||
|
filter_alias, field_name, p_val
|
||||||
|
)),
|
||||||
|
other => {
|
||||||
|
return Err(format!(
|
||||||
|
"operator {} is not supported on array property '{}' (jsonb containment supports $eq/$ne/$of/$nof)",
|
||||||
|
other, field_name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match op {
|
||||||
|
"$eq" => where_clauses.push(format!(
|
||||||
|
"{}.{} = ({})::jsonb",
|
||||||
|
filter_alias, field_name, p_val
|
||||||
|
)),
|
||||||
|
"$ne" => where_clauses.push(format!(
|
||||||
|
"{}.{} != ({})::jsonb",
|
||||||
|
filter_alias, field_name, p_val
|
||||||
|
)),
|
||||||
|
other => {
|
||||||
|
return Err(format!(
|
||||||
|
"operator {} is not supported on jsonb property '{}' (only $eq/$ne)",
|
||||||
|
other, field_name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if op == "$of" || op == "$nof" {
|
if op == "$of" || op == "$nof" {
|
||||||
let sql_op = if op == "$of" { "IN" } else { "NOT IN" };
|
let sql_op = if op == "$of" { "IN" } else { "NOT IN" };
|
||||||
let subquery = format!(
|
let subquery = format!(
|
||||||
@ -796,6 +878,7 @@ impl<'a> Compiler<'a> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_relation_conditions(
|
fn compile_relation_conditions(
|
||||||
|
|||||||
@ -1301,6 +1301,12 @@ fn test_queryer_0_15() {
|
|||||||
crate::tests::runner::run_test_case(&path, 0, 15).unwrap();
|
crate::tests::runner::run_test_case(&path, 0, 15).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_queryer_0_16() {
|
||||||
|
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 0, 16).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_polymorphism_0_0() {
|
fn test_polymorphism_0_0() {
|
||||||
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
|||||||
Reference in New Issue
Block a user