mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
Resolve nested object boolean expressions (#680)
<!-- Thank you for submitting this PR! :) --> ## Description This adds the ability to describe nested object boolean expressions, which become `fieldPath` items in the generated `ndc_models::ComparisonTarget::Column` items. This allows us to describe filtering a `User` based on some element in their nested `address` field (like `postcode`, for example). Like the other `BooleanExpressionType` work, this remains behind a feature flag so should make no user-facing changes. It is also missing a whole heap of metadata resolve checks, going to follow with these after doing the happy path to unblock other work. V3_GIT_ORIGIN_REV_ID: c89e2942a651d349fca97706affcf40d91afeefb
This commit is contained in:
parent
1c6b1dffc8
commit
75ced29d11
@ -103,8 +103,9 @@ ALTER SEQUENCE public.movie_analytics_id_seq OWNED BY public.movie_analytics.id;
|
||||
ALTER TABLE ONLY public.movie_analytics ALTER COLUMN id SET DEFAULT nextval('public.movie_analytics_id_seq'::regclass);
|
||||
|
||||
-- `institution` table for testing queries into JSON objects
|
||||
CREATE TYPE public.country AS (name text, continent text);
|
||||
|
||||
CREATE TYPE public.location AS (city text, country text, campuses text []);
|
||||
CREATE TYPE public.location AS (city text, country country, campuses text []);
|
||||
|
||||
CREATE TYPE public.staff AS (
|
||||
first_name text,
|
||||
@ -128,7 +129,7 @@ VALUES (
|
||||
'Queen Mary University of London',
|
||||
ROW(
|
||||
'London',
|
||||
'UK',
|
||||
ROW('UK','Europe') :: country,
|
||||
ARRAY ['Mile End','Whitechapel','Charterhouse Square','West Smithfield']
|
||||
)::location,
|
||||
ARRAY [ROW('Peter','Landin',ARRAY['Computer Science','Education'],
|
||||
@ -139,9 +140,9 @@ VALUES (
|
||||
(
|
||||
2,
|
||||
'Chalmers University of Technology',
|
||||
Row(
|
||||
ROW(
|
||||
'Gothenburg',
|
||||
'Sweden',
|
||||
ROW('Sweden','Europe') :: country,
|
||||
ARRAY ['Johanneberg','Lindholmen']
|
||||
)::location,
|
||||
ARRAY [ROW('John','Hughes',ARRAY['Computer Science','Functional Programming','Software Testing'],
|
||||
|
@ -379,6 +379,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"country": {
|
||||
"description": "A country",
|
||||
"fields": {
|
||||
"name": {
|
||||
"description": "The country's name",
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "String"
|
||||
}
|
||||
},
|
||||
"continent": {
|
||||
"description": "The country's continent",
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "String"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"description": "A location",
|
||||
"fields": {
|
||||
@ -395,7 +416,7 @@
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "String"
|
||||
"name": "country"
|
||||
}
|
||||
},
|
||||
"campuses": {
|
||||
|
@ -13,7 +13,9 @@
|
||||
]
|
||||
},
|
||||
"location_country": {
|
||||
"country": "UK"
|
||||
"country": {
|
||||
"name": "UK"
|
||||
}
|
||||
},
|
||||
"staff": [
|
||||
{
|
||||
@ -39,7 +41,9 @@
|
||||
"campuses": ["Johanneberg", "Lindholmen"]
|
||||
},
|
||||
"location_country": {
|
||||
"country": "Sweden"
|
||||
"country": {
|
||||
"name": "Sweden"
|
||||
}
|
||||
},
|
||||
"staff": [
|
||||
{
|
||||
@ -82,7 +86,9 @@
|
||||
"campuses": null
|
||||
},
|
||||
"location_country": {
|
||||
"country": null
|
||||
"country": {
|
||||
"name": null
|
||||
}
|
||||
},
|
||||
"staff": null,
|
||||
"staff_first_name": null,
|
||||
|
@ -111,6 +111,49 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "country",
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "continent",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Country"
|
||||
},
|
||||
"dataConnectorTypeMapping": [
|
||||
{
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorObjectType": "country"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"kind": "TypePermissions",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"typeName": "country",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": ["name", "continent"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
@ -123,7 +166,7 @@
|
||||
},
|
||||
{
|
||||
"name": "country",
|
||||
"type": "String"
|
||||
"type": "country"
|
||||
},
|
||||
{
|
||||
"name": "campuses",
|
||||
|
@ -6,7 +6,9 @@ query MyQuery {
|
||||
campuses
|
||||
}
|
||||
location_country: location {
|
||||
country
|
||||
country {
|
||||
name
|
||||
}
|
||||
}
|
||||
staff {
|
||||
last_name
|
||||
|
@ -0,0 +1,84 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"match_london": [
|
||||
{
|
||||
"id": 1,
|
||||
"location": {
|
||||
"city": "London",
|
||||
"campuses": [
|
||||
"Mile End",
|
||||
"Whitechapel",
|
||||
"Charterhouse Square",
|
||||
"West Smithfield"
|
||||
]
|
||||
},
|
||||
"location_country": {
|
||||
"country": {
|
||||
"name": "UK"
|
||||
}
|
||||
},
|
||||
"staff": [
|
||||
{
|
||||
"last_name": "Landin",
|
||||
"specialities": ["Computer Science", "Education"]
|
||||
}
|
||||
],
|
||||
"staff_first_name": [
|
||||
{
|
||||
"first_name": "Peter"
|
||||
}
|
||||
],
|
||||
"departments": [
|
||||
"Humanities and Social Sciences",
|
||||
"Science and Engineering",
|
||||
"Medicine and Dentistry"
|
||||
]
|
||||
}
|
||||
],
|
||||
"match_uk": [
|
||||
{
|
||||
"id": 1,
|
||||
"location": {
|
||||
"city": "London",
|
||||
"campuses": [
|
||||
"Mile End",
|
||||
"Whitechapel",
|
||||
"Charterhouse Square",
|
||||
"West Smithfield"
|
||||
]
|
||||
},
|
||||
"location_country": {
|
||||
"country": {
|
||||
"name": "UK"
|
||||
}
|
||||
},
|
||||
"staff": [
|
||||
{
|
||||
"last_name": "Landin",
|
||||
"specialities": ["Computer Science", "Education"]
|
||||
}
|
||||
],
|
||||
"staff_first_name": [
|
||||
{
|
||||
"first_name": "Peter"
|
||||
}
|
||||
],
|
||||
"departments": [
|
||||
"Humanities and Social Sciences",
|
||||
"Science and Engineering",
|
||||
"Medicine and Dentistry"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": null,
|
||||
"errors": [
|
||||
{
|
||||
"message": "validation failed: no such field on type Query: InstitutionMany"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,481 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "BooleanExpressionType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "string_bool_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"type": "String",
|
||||
"comparisonOperators": [
|
||||
{ "name": "_eq", "argumentType": "String!" }
|
||||
],
|
||||
"dataConnectorOperatorMapping": [
|
||||
{
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorScalarType": "String",
|
||||
"operatorMapping": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"logicalOperators": { "enable": true },
|
||||
"isNull": { "enable": true },
|
||||
"graphql": {
|
||||
"typeName": "String_Comparison_Exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "BooleanExpressionType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "int_bool_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"type": "Int",
|
||||
"comparisonOperators": [
|
||||
{ "name": "_eq", "argumentType": "Int!" }
|
||||
],
|
||||
"dataConnectorOperatorMapping": [
|
||||
{
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorScalarType": "Int",
|
||||
"operatorMapping": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"logicalOperators": { "enable": true },
|
||||
"isNull": { "enable": true },
|
||||
"graphql": {
|
||||
"typeName": "Int_Comparison_Exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "BooleanExpressionType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "institution_bool_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"type": "institution",
|
||||
"comparableFields": [
|
||||
{
|
||||
"fieldName": "id",
|
||||
"booleanExpressionType": "int_bool_exp"
|
||||
},
|
||||
{
|
||||
"fieldName": "name",
|
||||
"booleanExpressionType": "string_bool_exp"
|
||||
},
|
||||
{
|
||||
"fieldName": "location",
|
||||
"booleanExpressionType": "location_bool_exp"
|
||||
}
|
||||
],
|
||||
"comparableRelationships": []
|
||||
}
|
||||
},
|
||||
"logicalOperators": { "enable": true },
|
||||
"isNull": { "enable": true },
|
||||
"graphql": { "typeName": "InstitutionBoolExp" }
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "institution",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "Int!"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "String!"
|
||||
},
|
||||
{
|
||||
"name": "location",
|
||||
"type": "location"
|
||||
},
|
||||
{
|
||||
"name": "staff",
|
||||
"type": "[staff_member]"
|
||||
},
|
||||
{
|
||||
"name": "departments",
|
||||
"type": "[String]"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Institution"
|
||||
},
|
||||
"dataConnectorTypeMapping": [
|
||||
{
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorObjectType": "institution",
|
||||
"fieldMapping": {
|
||||
"id": {
|
||||
"column": {
|
||||
"name": "id"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"column": {
|
||||
"name": "name"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"column": {
|
||||
"name": "location"
|
||||
}
|
||||
},
|
||||
"staff": {
|
||||
"column": {
|
||||
"name": "staff"
|
||||
}
|
||||
},
|
||||
"departments": {
|
||||
"column": {
|
||||
"name": "departments"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "TypePermissions",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"typeName": "institution",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"id",
|
||||
"name",
|
||||
"location",
|
||||
"staff",
|
||||
"departments"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "BooleanExpressionType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "country_bool_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"type": "country",
|
||||
"comparableFields": [
|
||||
{
|
||||
"fieldName": "name",
|
||||
"booleanExpressionType": "string_bool_exp"
|
||||
},
|
||||
{
|
||||
"fieldName": "continent",
|
||||
"booleanExpressionType": "string_bool_exp"
|
||||
}
|
||||
],
|
||||
"comparableRelationships": []
|
||||
}
|
||||
},
|
||||
"logicalOperators": { "enable": true },
|
||||
"isNull": { "enable": true },
|
||||
"graphql": { "typeName": "CountryBoolExp" }
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"kind": "BooleanExpressionType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "location_bool_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"type": "location",
|
||||
"comparableFields": [
|
||||
{
|
||||
"fieldName": "city",
|
||||
"booleanExpressionType": "string_bool_exp"
|
||||
},
|
||||
{
|
||||
"fieldName": "country",
|
||||
"booleanExpressionType": "country_bool_exp"
|
||||
}
|
||||
],
|
||||
"comparableRelationships": []
|
||||
}
|
||||
},
|
||||
"logicalOperators": { "enable": true },
|
||||
"isNull": { "enable": true },
|
||||
"graphql": { "typeName": "LocationBoolExp" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "country",
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "continent",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Country"
|
||||
},
|
||||
"dataConnectorTypeMapping": [
|
||||
{
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorObjectType": "country",
|
||||
"fieldMapping": {
|
||||
"name": {
|
||||
"column": {
|
||||
"name": "name"
|
||||
}
|
||||
},
|
||||
"continent": {
|
||||
"column": {
|
||||
"name": "continent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "location",
|
||||
"fields": [
|
||||
{
|
||||
"name": "city",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "country",
|
||||
"type": "country"
|
||||
},
|
||||
{
|
||||
"name": "campuses",
|
||||
"type": "[String]"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Location"
|
||||
},
|
||||
"dataConnectorTypeMapping": [
|
||||
{
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorObjectType": "location",
|
||||
"fieldMapping": {
|
||||
"city": {
|
||||
"column": {
|
||||
"name": "city"
|
||||
}
|
||||
},
|
||||
"country": {
|
||||
"column": {
|
||||
"name": "country"
|
||||
}
|
||||
},
|
||||
"campuses": {
|
||||
"column": {
|
||||
"name": "campuses"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "TypePermissions",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"typeName": "country",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": ["name", "continent"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"kind": "TypePermissions",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"typeName": "location",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": ["city", "country", "campuses"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "staff_member",
|
||||
"fields": [
|
||||
{
|
||||
"name": "first_name",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "last_name",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "specialities",
|
||||
"type": "[String]"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "StaffMember"
|
||||
},
|
||||
"dataConnectorTypeMapping": [
|
||||
{
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorObjectType": "staff",
|
||||
"fieldMapping": {
|
||||
"first_name": {
|
||||
"column": {
|
||||
"name": "first_name"
|
||||
}
|
||||
},
|
||||
"last_name": {
|
||||
"column": {
|
||||
"name": "last_name"
|
||||
}
|
||||
},
|
||||
"specialities": {
|
||||
"column": {
|
||||
"name": "specialities"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "TypePermissions",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"typeName": "staff_member",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": ["first_name", "last_name", "specialities"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "Model",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "institutions",
|
||||
"arguments": [],
|
||||
"objectType": "institution",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "institution",
|
||||
"argumentMapping": {}
|
||||
},
|
||||
"filterExpressionType": "institution_bool_exp",
|
||||
"graphql": {
|
||||
"selectUniques": [],
|
||||
"selectMany": {
|
||||
"queryRootField": "InstitutionMany"
|
||||
}
|
||||
},
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "id",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "location",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "staff",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "departments",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ModelPermissions",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"modelName": "institutions",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
query MyQuery {
|
||||
match_london: InstitutionMany(
|
||||
where: { location: { city: { _eq: "London" } } }
|
||||
) {
|
||||
id
|
||||
location {
|
||||
city
|
||||
campuses
|
||||
}
|
||||
location_country: location {
|
||||
country {
|
||||
name
|
||||
}
|
||||
}
|
||||
staff {
|
||||
last_name
|
||||
specialities
|
||||
}
|
||||
staff_first_name: staff {
|
||||
first_name
|
||||
}
|
||||
departments
|
||||
}
|
||||
match_uk: InstitutionMany(
|
||||
where: { location: { country: { name: { _eq: "UK" } } } }
|
||||
) {
|
||||
id
|
||||
location {
|
||||
city
|
||||
campuses
|
||||
}
|
||||
location_country: location {
|
||||
country {
|
||||
name
|
||||
}
|
||||
}
|
||||
staff {
|
||||
last_name
|
||||
specialities
|
||||
}
|
||||
staff_first_name: staff {
|
||||
first_name
|
||||
}
|
||||
departments
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user_1",
|
||||
"x-hasura-user-id": "1"
|
||||
}
|
||||
]
|
@ -230,6 +230,13 @@ fn test_model_select_many_boolean_expression_type() -> anyhow::Result<()> {
|
||||
common::test_execution_expectation(test_path_string, &[common_metadata_path_string])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_where_nested_select() -> anyhow::Result<()> {
|
||||
let test_path_string = "execute/models/select_many/where/nested_select";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
common::test_execution_expectation(test_path_string, &[common_metadata_path_string])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_where_is_null() -> anyhow::Result<()> {
|
||||
let test_path_string = "execute/models/select_many/where/is_null";
|
||||
|
@ -1138,6 +1138,26 @@
|
||||
}
|
||||
},
|
||||
"compositeTypes": {
|
||||
"country": {
|
||||
"name": "country",
|
||||
"fields": {
|
||||
"continent": {
|
||||
"name": "continent",
|
||||
"type": {
|
||||
"scalarType": "text"
|
||||
},
|
||||
"description": null
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": {
|
||||
"scalarType": "text"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
},
|
||||
"description": null
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"fields": {
|
||||
@ -1160,7 +1180,7 @@
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": {
|
||||
"scalarType": "text"
|
||||
"compositeType": "country"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
|
@ -95,13 +95,16 @@ impl FieldError {
|
||||
pub fn to_graphql_error(&self, path: Option<Vec<gql::http::PathSegment>>) -> GraphQLError {
|
||||
let details = self.get_details();
|
||||
match self {
|
||||
Self::InternalError(_internal) => GraphQLError {
|
||||
message: "internal error".into(),
|
||||
path,
|
||||
// Internal errors showing up in the API response is not desirable.
|
||||
// Hence, extensions are masked for internal errors.
|
||||
extensions: None,
|
||||
},
|
||||
Self::InternalError(_internal) => {
|
||||
println!("{_internal}");
|
||||
GraphQLError {
|
||||
message: "internal error".into(),
|
||||
path,
|
||||
// Internal errors showing up in the API response is not desirable.
|
||||
// Hence, extensions are masked for internal errors.
|
||||
extensions: None,
|
||||
}
|
||||
}
|
||||
e => GraphQLError {
|
||||
message: e.to_string(),
|
||||
path,
|
||||
|
@ -56,6 +56,20 @@ pub(crate) fn resolve_filter_expression<'s>(
|
||||
Ok(resolved_filter_expression)
|
||||
}
|
||||
|
||||
fn get_boolean_expression_annotation(
|
||||
annotation: &schema::Annotation,
|
||||
) -> Result<&BooleanExpressionAnnotation, error::Error> {
|
||||
match annotation {
|
||||
schema::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
boolean_expression_annotation,
|
||||
)) => Ok(boolean_expression_annotation),
|
||||
_ => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
// Build the NDC filter expression by traversing the relationships when present
|
||||
fn build_filter_expression<'s>(
|
||||
field: &normalized_ast::InputField<'s, GDS>,
|
||||
@ -64,13 +78,33 @@ fn build_filter_expression<'s>(
|
||||
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, metadata_resolve::TypeMapping>,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ndc_models::Expression, error::Error> {
|
||||
match field.info.generic {
|
||||
let boolean_expression_annotation = get_boolean_expression_annotation(field.info.generic)?;
|
||||
build_filter_expression_from_boolean_expression(
|
||||
boolean_expression_annotation,
|
||||
field,
|
||||
relationships,
|
||||
data_connector_link,
|
||||
type_mappings,
|
||||
&mut vec![],
|
||||
usage_counts,
|
||||
)
|
||||
}
|
||||
|
||||
// build filter expression, specifically matching on BooleanExpressionAnnotation
|
||||
fn build_filter_expression_from_boolean_expression<'s>(
|
||||
boolean_expression_annotation: &'s BooleanExpressionAnnotation,
|
||||
field: &normalized_ast::InputField<'s, GDS>,
|
||||
relationships: &mut BTreeMap<NDCRelationshipName, LocalModelRelationshipInfo<'s>>,
|
||||
data_connector_link: &'s DataConnectorLink,
|
||||
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, metadata_resolve::TypeMapping>,
|
||||
field_path: &mut Vec<DataConnectorColumnName>,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ndc_models::Expression, error::Error> {
|
||||
match boolean_expression_annotation {
|
||||
// "_and"
|
||||
schema::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field: schema::ModelFilterArgument::AndOp,
|
||||
},
|
||||
)) => {
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field: schema::ModelFilterArgument::AndOp,
|
||||
} => {
|
||||
let mut and_expressions = Vec::new();
|
||||
// The "_and" field value should be a list
|
||||
let and_values = field.value.as_list()?;
|
||||
@ -91,11 +125,9 @@ fn build_filter_expression<'s>(
|
||||
})
|
||||
}
|
||||
// "_or"
|
||||
schema::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field: schema::ModelFilterArgument::OrOp,
|
||||
},
|
||||
)) => {
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field: schema::ModelFilterArgument::OrOp,
|
||||
} => {
|
||||
let mut or_expressions = Vec::new();
|
||||
// The "_or" field value should be a list
|
||||
let or_values = field.value.as_list()?;
|
||||
@ -116,11 +148,9 @@ fn build_filter_expression<'s>(
|
||||
})
|
||||
}
|
||||
// "_not"
|
||||
schema::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field: schema::ModelFilterArgument::NotOp,
|
||||
},
|
||||
)) => {
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field: schema::ModelFilterArgument::NotOp,
|
||||
} => {
|
||||
// The "_not" field value should be an object
|
||||
let not_value = field.value.as_object()?;
|
||||
|
||||
@ -136,73 +166,38 @@ fn build_filter_expression<'s>(
|
||||
})
|
||||
}
|
||||
// The column that we want to use for filtering.
|
||||
schema::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field:
|
||||
schema::ModelFilterArgument::Field {
|
||||
field_name,
|
||||
object_type,
|
||||
},
|
||||
},
|
||||
)) => {
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field:
|
||||
schema::ModelFilterArgument::Field {
|
||||
field_name,
|
||||
object_type,
|
||||
},
|
||||
} => {
|
||||
let FieldMapping { column, .. } =
|
||||
get_field_mapping_of_field_name(type_mappings, object_type, field_name)?;
|
||||
|
||||
let mut expressions = Vec::new();
|
||||
|
||||
for (_op_name, op_value) in field.value.as_object()? {
|
||||
match op_value.info.generic {
|
||||
schema::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::IsNullOperation,
|
||||
)) => {
|
||||
let expression = build_is_null_expression(column.clone(), &op_value.value)?;
|
||||
expressions.push(expression);
|
||||
}
|
||||
schema::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ComparisonOperation { operator_mapping },
|
||||
)) => {
|
||||
let operator =
|
||||
operator_mapping
|
||||
.get(&data_connector_link.name)
|
||||
.ok_or_else(|| {
|
||||
error::InternalEngineError::OperatorMappingError(
|
||||
error::OperatorMappingError::MissingEntryForDataConnector {
|
||||
column_name: column.clone(),
|
||||
data_connector_name: data_connector_link.name.clone(),
|
||||
},
|
||||
)
|
||||
})?;
|
||||
|
||||
let expression = build_binary_comparison_expression(
|
||||
operator,
|
||||
column.clone(),
|
||||
&op_value.value,
|
||||
);
|
||||
expressions.push(expression)
|
||||
}
|
||||
annotation => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
Ok(ndc_models::Expression::And { expressions })
|
||||
build_comparison_expression(
|
||||
field,
|
||||
field_path,
|
||||
&column,
|
||||
data_connector_link,
|
||||
type_mappings,
|
||||
)
|
||||
}
|
||||
// Relationship field used for filtering.
|
||||
// This relationship can either point to another relationship or a column.
|
||||
schema::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field:
|
||||
schema::ModelFilterArgument::RelationshipField(FilterRelationshipAnnotation {
|
||||
relationship_name,
|
||||
relationship_type,
|
||||
source_type,
|
||||
target_source,
|
||||
target_type,
|
||||
target_model_name,
|
||||
mappings,
|
||||
}),
|
||||
},
|
||||
)) => {
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field:
|
||||
schema::ModelFilterArgument::RelationshipField(FilterRelationshipAnnotation {
|
||||
relationship_name,
|
||||
relationship_type,
|
||||
source_type,
|
||||
target_source,
|
||||
target_type,
|
||||
target_model_name,
|
||||
mappings,
|
||||
}),
|
||||
} => {
|
||||
// Add the target model being used in the usage counts
|
||||
count_model(target_model_name, usage_counts);
|
||||
|
||||
@ -244,17 +239,104 @@ fn build_filter_expression<'s>(
|
||||
relationship: ndc_relationship_name.0,
|
||||
arguments: BTreeMap::new(),
|
||||
};
|
||||
|
||||
Ok(ndc_models::Expression::Exists {
|
||||
in_collection: exists_in_relationship,
|
||||
predicate: Some(Box::new(exists_filter_clause)),
|
||||
})
|
||||
}
|
||||
annotation => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
other_boolean_annotation => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: schema::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
other_boolean_annotation.clone(),
|
||||
)),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_comparison_expression<'s>(
|
||||
field: &normalized_ast::InputField<'s, GDS>,
|
||||
field_path: &mut Vec<DataConnectorColumnName>,
|
||||
column: &DataConnectorColumnName,
|
||||
data_connector_link: &'s DataConnectorLink,
|
||||
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, metadata_resolve::TypeMapping>,
|
||||
) -> Result<ndc_models::Expression, error::Error> {
|
||||
let mut expressions = Vec::new();
|
||||
|
||||
println!("build_comparison_expression");
|
||||
for (_op_name, op_value) in field.value.as_object()? {
|
||||
println!("{op_value:?}");
|
||||
match op_value.info.generic {
|
||||
schema::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::IsNullOperation,
|
||||
)) => {
|
||||
let expression =
|
||||
build_is_null_expression(column.clone(), &op_value.value, field_path.clone())?;
|
||||
expressions.push(expression);
|
||||
}
|
||||
schema::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ComparisonOperation { operator_mapping },
|
||||
)) => {
|
||||
let operator =
|
||||
operator_mapping
|
||||
.get(&data_connector_link.name)
|
||||
.ok_or_else(|| {
|
||||
error::InternalEngineError::OperatorMappingError(
|
||||
error::OperatorMappingError::MissingEntryForDataConnector {
|
||||
column_name: column.clone(),
|
||||
data_connector_name: data_connector_link.name.clone(),
|
||||
},
|
||||
)
|
||||
})?;
|
||||
|
||||
let expression = build_binary_comparison_expression(
|
||||
operator,
|
||||
column.clone(),
|
||||
&op_value.value,
|
||||
field_path.clone(),
|
||||
);
|
||||
expressions.push(expression)
|
||||
}
|
||||
schema::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field:
|
||||
schema::ModelFilterArgument::Field {
|
||||
field_name: inner_field_name,
|
||||
object_type: inner_object_type,
|
||||
},
|
||||
},
|
||||
)) => {
|
||||
// get correct inner column name
|
||||
let FieldMapping {
|
||||
column: inner_column,
|
||||
..
|
||||
} = get_field_mapping_of_field_name(
|
||||
type_mappings,
|
||||
inner_object_type,
|
||||
inner_field_name,
|
||||
)?;
|
||||
|
||||
// add it to the path
|
||||
field_path.push(inner_column);
|
||||
|
||||
let inner_expression = build_comparison_expression(
|
||||
op_value,
|
||||
field_path,
|
||||
column,
|
||||
data_connector_link,
|
||||
type_mappings,
|
||||
)?;
|
||||
|
||||
expressions.push(inner_expression);
|
||||
}
|
||||
|
||||
annotation => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
Ok(ndc_models::Expression::And { expressions })
|
||||
}
|
||||
|
||||
/// get column name for field name
|
||||
fn get_field_mapping_of_field_name(
|
||||
type_mappings: &BTreeMap<Qualified<CustomTypeName>, metadata_resolve::TypeMapping>,
|
||||
@ -299,17 +381,27 @@ fn resolve_filter_object<'s>(
|
||||
Ok(ndc_models::Expression::And { expressions })
|
||||
}
|
||||
|
||||
/// Only pass a path if there are items in it
|
||||
fn to_ndc_field_path(field_path: Vec<DataConnectorColumnName>) -> Option<Vec<String>> {
|
||||
if field_path.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(field_path.into_iter().map(|s| s.0).collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a binary comparison operator
|
||||
fn build_binary_comparison_expression(
|
||||
operator: &DataConnectorOperatorName,
|
||||
column: DataConnectorColumnName,
|
||||
value: &normalized_ast::Value<'_, GDS>,
|
||||
field_path: Vec<DataConnectorColumnName>,
|
||||
) -> ndc_models::Expression {
|
||||
ndc_models::Expression::BinaryComparisonOperator {
|
||||
column: ndc_models::ComparisonTarget::Column {
|
||||
name: column.0,
|
||||
path: Vec::new(),
|
||||
field_path: None,
|
||||
field_path: to_ndc_field_path(field_path),
|
||||
},
|
||||
operator: operator.0.clone(),
|
||||
value: ndc_models::ComparisonValue::Scalar {
|
||||
@ -322,13 +414,14 @@ fn build_binary_comparison_expression(
|
||||
fn build_is_null_expression(
|
||||
column: DataConnectorColumnName,
|
||||
value: &normalized_ast::Value<'_, GDS>,
|
||||
field_path: Vec<DataConnectorColumnName>,
|
||||
) -> Result<ndc_models::Expression, error::Error> {
|
||||
// Build an 'IsNull' unary comparison expression
|
||||
let unary_comparison_expression = ndc_models::Expression::UnaryComparisonOperator {
|
||||
column: ndc_models::ComparisonTarget::Column {
|
||||
name: column.0,
|
||||
path: Vec::new(),
|
||||
field_path: None,
|
||||
field_path: to_ndc_field_path(field_path),
|
||||
},
|
||||
operator: ndc_models::UnaryComparisonOperator::IsNull,
|
||||
};
|
||||
|
@ -160,6 +160,7 @@ pub async fn query_post(
|
||||
configuration: Configuration<'_>,
|
||||
query_request: &ndc_models::QueryRequest,
|
||||
) -> Result<ndc_models::QueryResponse, Error> {
|
||||
println!("{query_request:?}");
|
||||
let tracer = tracing_util::global_tracer();
|
||||
tracer
|
||||
.in_span_async(
|
||||
|
@ -9,8 +9,8 @@ mod scalar;
|
||||
mod types;
|
||||
pub use types::{
|
||||
BooleanExpressionGraphqlConfig, BooleanExpressionGraphqlFieldConfig, BooleanExpressionTypes,
|
||||
BooleanExpressionsOutput, ComparisonExpressionInfo, ResolvedObjectBooleanExpressionType,
|
||||
ResolvedScalarBooleanExpressionType,
|
||||
BooleanExpressionsOutput, ComparisonExpressionInfo, ObjectComparisonExpressionInfo,
|
||||
ResolvedObjectBooleanExpressionType, ResolvedScalarBooleanExpressionType,
|
||||
};
|
||||
|
||||
pub fn resolve(
|
||||
@ -43,9 +43,6 @@ pub fn resolve(
|
||||
);
|
||||
}
|
||||
|
||||
let all_boolean_expression_names: BTreeSet<_> =
|
||||
raw_boolean_expression_types.keys().cloned().collect();
|
||||
|
||||
// let's resolve scalar types first
|
||||
|
||||
let mut boolean_expression_scalar_types = BTreeMap::new();
|
||||
@ -74,19 +71,19 @@ pub fn resolve(
|
||||
let mut boolean_expression_object_types = BTreeMap::new();
|
||||
|
||||
for (boolean_expression_type_name, (subgraph, boolean_expression_type)) in
|
||||
raw_boolean_expression_types
|
||||
&raw_boolean_expression_types
|
||||
{
|
||||
if let BooleanExpressionOperand::Object(boolean_expression_object_operand) =
|
||||
&boolean_expression_type.operand
|
||||
{
|
||||
let object_boolean_expression_type = object::resolve_object_boolean_expression_type(
|
||||
&boolean_expression_type_name,
|
||||
boolean_expression_type_name,
|
||||
boolean_expression_object_operand,
|
||||
subgraph,
|
||||
&boolean_expression_type.graphql,
|
||||
object_types,
|
||||
&boolean_expression_scalar_types,
|
||||
&all_boolean_expression_names,
|
||||
&raw_boolean_expression_types,
|
||||
graphql_config,
|
||||
)?;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub use super::{
|
||||
BooleanExpressionGraphqlConfig, BooleanExpressionGraphqlFieldConfig, ComparisonExpressionInfo,
|
||||
ResolvedObjectBooleanExpressionType, ResolvedScalarBooleanExpressionType,
|
||||
ObjectComparisonExpressionInfo, ResolvedObjectBooleanExpressionType,
|
||||
ResolvedScalarBooleanExpressionType,
|
||||
};
|
||||
use crate::helpers::types::mk_name;
|
||||
use crate::stages::{graphql_config, object_types, type_permissions};
|
||||
@ -10,7 +11,7 @@ use crate::Qualified;
|
||||
use lang_graphql::ast::common::{self as ast};
|
||||
use open_dds::{
|
||||
boolean_expression::{
|
||||
BooleanExpressionComparableField, BooleanExpressionObjectOperand,
|
||||
BooleanExpressionComparableField, BooleanExpressionObjectOperand, BooleanExpressionOperand,
|
||||
BooleanExpressionTypeGraphQlConfiguration, DataConnectorOperatorMapping,
|
||||
},
|
||||
data_connector::{DataConnectorName, DataConnectorOperatorName},
|
||||
@ -29,7 +30,13 @@ pub(crate) fn resolve_object_boolean_expression_type(
|
||||
Qualified<CustomTypeName>,
|
||||
ResolvedScalarBooleanExpressionType,
|
||||
>,
|
||||
all_boolean_expression_names: &BTreeSet<Qualified<CustomTypeName>>,
|
||||
raw_boolean_expression_types: &BTreeMap<
|
||||
Qualified<CustomTypeName>,
|
||||
(
|
||||
&String,
|
||||
&open_dds::boolean_expression::BooleanExpressionTypeV1,
|
||||
),
|
||||
>,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
) -> Result<ResolvedObjectBooleanExpressionType, Error> {
|
||||
let qualified_object_type_name = Qualified::new(
|
||||
@ -53,7 +60,7 @@ pub(crate) fn resolve_object_boolean_expression_type(
|
||||
&object_type_representation.object_type,
|
||||
boolean_expression_type_name,
|
||||
subgraph,
|
||||
all_boolean_expression_names,
|
||||
raw_boolean_expression_types,
|
||||
)?;
|
||||
|
||||
let _allowed_data_connectors = resolve_data_connector_types(
|
||||
@ -67,9 +74,11 @@ pub(crate) fn resolve_object_boolean_expression_type(
|
||||
.as_ref()
|
||||
.map(|object_boolean_graphql_config| {
|
||||
resolve_object_boolean_graphql(
|
||||
boolean_expression_type_name,
|
||||
object_boolean_graphql_config,
|
||||
&comparable_fields,
|
||||
scalar_boolean_expression_types,
|
||||
raw_boolean_expression_types,
|
||||
subgraph,
|
||||
graphql_config,
|
||||
)
|
||||
@ -90,7 +99,6 @@ pub(crate) fn resolve_object_boolean_expression_type(
|
||||
// has information for this data connector
|
||||
fn resolve_data_connector_types(
|
||||
boolean_expression_type_name: &Qualified<CustomTypeName>,
|
||||
|
||||
object_type_representation: &type_permissions::ObjectTypeWithPermissions,
|
||||
scalar_boolean_expression_types: &BTreeMap<
|
||||
Qualified<CustomTypeName>,
|
||||
@ -108,34 +116,30 @@ fn resolve_data_connector_types(
|
||||
.data_connector_names()
|
||||
{
|
||||
for (comparable_field_name, comparable_field_type) in comparable_fields {
|
||||
let scalar_boolean_expression = scalar_boolean_expression_types
|
||||
.get(comparable_field_type)
|
||||
.ok_or_else(|| Error::BooleanExpressionError {
|
||||
boolean_expression_error:
|
||||
BooleanExpressionError::ScalarBooleanExpressionCouldNotBeFound {
|
||||
boolean_expression: boolean_expression_type_name.clone(),
|
||||
scalar_boolean_expression: comparable_field_type.clone(),
|
||||
},
|
||||
})?;
|
||||
|
||||
// currently this throws an error if any data connector specified in the ObjectType
|
||||
// does not have matching data connector entries for each scalar boolean expression
|
||||
// type
|
||||
// Not sure if this is correct behaviour, or if we only want to make this a problem
|
||||
// when making these available in the GraphQL schema
|
||||
if !scalar_boolean_expression
|
||||
.data_connector_operator_mappings
|
||||
.contains_key(data_connector_name)
|
||||
// we don't mind if we can't find this, we've already checked validity so anything
|
||||
// else will be an object-flavoured boolean expression
|
||||
if let Some(scalar_boolean_expression) =
|
||||
scalar_boolean_expression_types.get(comparable_field_type)
|
||||
{
|
||||
return Err(Error::BooleanExpressionError {
|
||||
boolean_expression_error:
|
||||
BooleanExpressionError::DataConnectorMappingMissingForField {
|
||||
field: comparable_field_name.clone(),
|
||||
boolean_expression_name: boolean_expression_type_name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
},
|
||||
});
|
||||
};
|
||||
// currently this throws an error if any data connector specified in the ObjectType
|
||||
// does not have matching data connector entries for each scalar boolean expression
|
||||
// type
|
||||
// Not sure if this is correct behaviour, or if we only want to make this a problem
|
||||
// when making these available in the GraphQL schema
|
||||
if !scalar_boolean_expression
|
||||
.data_connector_operator_mappings
|
||||
.contains_key(data_connector_name)
|
||||
{
|
||||
return Err(Error::BooleanExpressionError {
|
||||
boolean_expression_error:
|
||||
BooleanExpressionError::DataConnectorMappingMissingForField {
|
||||
field: comparable_field_name.clone(),
|
||||
boolean_expression_name: boolean_expression_type_name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
working_data_connectors.insert(data_connector_name.clone());
|
||||
@ -145,13 +149,22 @@ fn resolve_data_connector_types(
|
||||
}
|
||||
|
||||
// validate graphql config
|
||||
// we use the raw boolean expression types for lookup
|
||||
fn resolve_object_boolean_graphql(
|
||||
boolean_expression_type_name: &Qualified<CustomTypeName>,
|
||||
boolean_expression_graphql_config: &BooleanExpressionTypeGraphQlConfiguration,
|
||||
comparable_fields: &BTreeMap<FieldName, Qualified<CustomTypeName>>,
|
||||
scalar_boolean_expression_types: &BTreeMap<
|
||||
Qualified<CustomTypeName>,
|
||||
ResolvedScalarBooleanExpressionType,
|
||||
>,
|
||||
raw_boolean_expression_types: &BTreeMap<
|
||||
Qualified<CustomTypeName>,
|
||||
(
|
||||
&String,
|
||||
&open_dds::boolean_expression::BooleanExpressionTypeV1,
|
||||
),
|
||||
>,
|
||||
subgraph: &str,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
) -> Result<BooleanExpressionGraphqlConfig, Error> {
|
||||
@ -160,6 +173,8 @@ fn resolve_object_boolean_graphql(
|
||||
|
||||
let mut scalar_fields = BTreeMap::new();
|
||||
|
||||
let mut object_fields = BTreeMap::new();
|
||||
|
||||
let filter_graphql_config = graphql_config
|
||||
.query
|
||||
.filter_input_config
|
||||
@ -169,10 +184,10 @@ fn resolve_object_boolean_graphql(
|
||||
})?;
|
||||
|
||||
for (comparable_field_name, comparable_field_type_name) in comparable_fields {
|
||||
// Generate comparison expression for fields mapped to simple scalar type
|
||||
if let Some(scalar_boolean_expression_type) =
|
||||
scalar_boolean_expression_types.get(comparable_field_type_name)
|
||||
{
|
||||
// Generate comparison expression for fields mapped to simple scalar type
|
||||
if let Some(graphql_name) = &scalar_boolean_expression_type.graphql_name {
|
||||
let mut operators = BTreeMap::new();
|
||||
for (op_name, op_definition) in &scalar_boolean_expression_type.comparison_operators
|
||||
@ -204,12 +219,42 @@ fn resolve_object_boolean_graphql(
|
||||
);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// if this field isn't a scalar, let's see if it's an object instead
|
||||
let (field_subgraph, raw_boolean_expression_type) = lookup_raw_boolean_expression(
|
||||
boolean_expression_type_name,
|
||||
comparable_field_type_name,
|
||||
raw_boolean_expression_types,
|
||||
)?;
|
||||
|
||||
if let (Some(graphql_name), BooleanExpressionOperand::Object(object_operand)) = (
|
||||
raw_boolean_expression_type
|
||||
.graphql
|
||||
.as_ref()
|
||||
.map(|gql| gql.type_name.clone()),
|
||||
&raw_boolean_expression_type.operand,
|
||||
) {
|
||||
let graphql_type_name = mk_name(&graphql_name.0).map(ast::TypeName)?;
|
||||
|
||||
object_fields.insert(
|
||||
comparable_field_name.clone(),
|
||||
ObjectComparisonExpressionInfo {
|
||||
object_type_name: comparable_field_type_name.clone(),
|
||||
underlying_object_type_name: Qualified::new(
|
||||
(*field_subgraph).to_string(),
|
||||
object_operand.r#type.clone(),
|
||||
),
|
||||
graphql_type_name: graphql_type_name.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(BooleanExpressionGraphqlConfig {
|
||||
type_name: boolean_expression_graphql_name,
|
||||
scalar_fields,
|
||||
object_fields,
|
||||
graphql_config: (BooleanExpressionGraphqlFieldConfig {
|
||||
where_field_name: filter_graphql_config.where_field_name.clone(),
|
||||
and_operator_name: filter_graphql_config.operator_names.and.clone(),
|
||||
@ -244,7 +289,13 @@ fn resolve_comparable_fields(
|
||||
object_type_representation: &object_types::ObjectTypeRepresentation,
|
||||
boolean_expression_type_name: &Qualified<CustomTypeName>,
|
||||
subgraph: &str,
|
||||
all_boolean_expression_names: &BTreeSet<Qualified<CustomTypeName>>,
|
||||
raw_boolean_expression_types: &BTreeMap<
|
||||
Qualified<CustomTypeName>,
|
||||
(
|
||||
&String,
|
||||
&open_dds::boolean_expression::BooleanExpressionTypeV1,
|
||||
),
|
||||
>,
|
||||
) -> Result<BTreeMap<FieldName, Qualified<CustomTypeName>>, Error> {
|
||||
let mut resolved_comparable_fields = BTreeMap::new();
|
||||
|
||||
@ -269,21 +320,45 @@ fn resolve_comparable_fields(
|
||||
);
|
||||
|
||||
// lookup the boolean expression type to check it exists
|
||||
if all_boolean_expression_names.contains(&field_boolean_expression_type) {
|
||||
resolved_comparable_fields.insert(
|
||||
comparable_field.field_name.clone(),
|
||||
field_boolean_expression_type,
|
||||
);
|
||||
} else {
|
||||
return Err(
|
||||
BooleanExpressionError::ScalarBooleanExpressionCouldNotBeFound {
|
||||
boolean_expression: boolean_expression_type_name.clone(),
|
||||
scalar_boolean_expression: field_boolean_expression_type.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let _raw_boolean_expression_type = lookup_raw_boolean_expression(
|
||||
boolean_expression_type_name,
|
||||
&field_boolean_expression_type,
|
||||
raw_boolean_expression_types,
|
||||
)?;
|
||||
|
||||
resolved_comparable_fields.insert(
|
||||
comparable_field.field_name.clone(),
|
||||
field_boolean_expression_type,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(resolved_comparable_fields)
|
||||
}
|
||||
|
||||
fn lookup_raw_boolean_expression<'a>(
|
||||
parent_boolean_expression_name: &Qualified<CustomTypeName>,
|
||||
boolean_expression_name: &Qualified<CustomTypeName>,
|
||||
raw_boolean_expression_types: &'a BTreeMap<
|
||||
Qualified<CustomTypeName>,
|
||||
(
|
||||
&String,
|
||||
&open_dds::boolean_expression::BooleanExpressionTypeV1,
|
||||
),
|
||||
>,
|
||||
) -> Result<
|
||||
&'a (
|
||||
&'a String,
|
||||
&'a open_dds::boolean_expression::BooleanExpressionTypeV1,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
raw_boolean_expression_types
|
||||
.get(boolean_expression_name)
|
||||
.ok_or_else(|| {
|
||||
BooleanExpressionError::BooleanExpressionCouldNotBeFound {
|
||||
parent_boolean_expression: parent_boolean_expression_name.clone(),
|
||||
child_boolean_expression: boolean_expression_name.clone(),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
@ -55,6 +55,13 @@ pub struct ComparisonExpressionInfo {
|
||||
pub is_null_operator_name: ast::Name,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ObjectComparisonExpressionInfo {
|
||||
pub graphql_type_name: ast::TypeName,
|
||||
pub object_type_name: Qualified<CustomTypeName>,
|
||||
pub underlying_object_type_name: Qualified<CustomTypeName>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BooleanExpressionGraphqlFieldConfig {
|
||||
pub where_field_name: ast::Name,
|
||||
@ -66,6 +73,7 @@ pub struct BooleanExpressionGraphqlFieldConfig {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BooleanExpressionGraphqlConfig {
|
||||
pub type_name: ast::TypeName,
|
||||
pub object_fields: BTreeMap<FieldName, ObjectComparisonExpressionInfo>,
|
||||
pub scalar_fields: BTreeMap<FieldName, ComparisonExpressionInfo>,
|
||||
pub graphql_config: BooleanExpressionGraphqlFieldConfig,
|
||||
}
|
||||
|
@ -318,6 +318,7 @@ pub fn resolve_boolean_expression_graphql_config(
|
||||
Ok(boolean_expressions::BooleanExpressionGraphqlConfig {
|
||||
type_name: where_type_name,
|
||||
scalar_fields,
|
||||
object_fields: BTreeMap::new(),
|
||||
graphql_config: (boolean_expressions::BooleanExpressionGraphqlFieldConfig {
|
||||
where_field_name: filter_graphql_config.where_field_name.clone(),
|
||||
and_operator_name: filter_graphql_config.operator_names.and.clone(),
|
||||
|
@ -642,10 +642,10 @@ pub enum BooleanExpressionError {
|
||||
name: Qualified<CustomTypeName>,
|
||||
model: Qualified<ModelName>,
|
||||
},
|
||||
#[error("unknown scalar boolean expression type {scalar_boolean_expression:} is used in boolean expression {boolean_expression:}")]
|
||||
ScalarBooleanExpressionCouldNotBeFound {
|
||||
boolean_expression: Qualified<CustomTypeName>,
|
||||
scalar_boolean_expression: Qualified<CustomTypeName>,
|
||||
#[error("could not find boolean expression type {child_boolean_expression:} referenced within boolean expression {parent_boolean_expression:}")]
|
||||
BooleanExpressionCouldNotBeFound {
|
||||
parent_boolean_expression: Qualified<CustomTypeName>,
|
||||
child_boolean_expression: Qualified<CustomTypeName>,
|
||||
},
|
||||
#[error("the boolean expression type {name:} used in model {model:} corresponds to object type {boolean_expression_object_type:} whereas the model's object type is {model_object_type:}")]
|
||||
BooleanExpressionTypeForInvalidObjectTypeInModel {
|
||||
|
@ -1 +1 @@
|
||||
unknown scalar boolean expression type postgres_int_comparison_bool_exp (in subgraph __unknown_namespace) is used in boolean expression author_bool_exp (in subgraph __unknown_namespace)
|
||||
could not find boolean expression type postgres_int_comparison_bool_exp (in subgraph __unknown_namespace) referenced within boolean expression author_bool_exp (in subgraph __unknown_namespace)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -101,6 +101,7 @@ fn build_builtin_operator_schema(
|
||||
|
||||
// Build input fields for fields that we are allowed to compare
|
||||
fn build_comparable_fields_schema(
|
||||
gds: &GDS,
|
||||
object_type_name: &Qualified<CustomTypeName>,
|
||||
object_type_representation: &ObjectTypeWithRelationships,
|
||||
boolean_expression_info: &BooleanExpressionGraphqlConfig,
|
||||
@ -108,7 +109,7 @@ fn build_comparable_fields_schema(
|
||||
) -> Result<BTreeMap<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>, Error> {
|
||||
let mut input_fields = BTreeMap::new();
|
||||
|
||||
// column fields
|
||||
// scalar column fields
|
||||
for (field_name, comparison_expression) in &boolean_expression_info.scalar_fields {
|
||||
let field_graphql_name = mk_name(field_name.clone().0.as_str())?;
|
||||
let registered_type_name =
|
||||
@ -140,6 +141,53 @@ fn build_comparable_fields_schema(
|
||||
);
|
||||
input_fields.insert(field_graphql_name, input_field);
|
||||
}
|
||||
|
||||
// object column fields
|
||||
for (field_name, object_comparison_expression) in &boolean_expression_info.object_fields {
|
||||
let field_graphql_name = mk_name(field_name.clone().0.as_str())?;
|
||||
|
||||
let registered_type_name =
|
||||
builder.register_type(TypeId::InputObjectBooleanExpressionType {
|
||||
graphql_type_name: object_comparison_expression.graphql_type_name.clone(),
|
||||
gds_type_name: object_comparison_expression.object_type_name.clone(),
|
||||
});
|
||||
|
||||
let field_object_type_representation = gds
|
||||
.metadata
|
||||
.object_types
|
||||
.get(&object_comparison_expression.underlying_object_type_name)
|
||||
.unwrap();
|
||||
|
||||
let field_type = ast::TypeContainer::named_null(registered_type_name);
|
||||
|
||||
let annotation = types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||
field: types::ModelFilterArgument::Field {
|
||||
field_name: field_name.clone(),
|
||||
object_type: object_type_name.clone(),
|
||||
},
|
||||
},
|
||||
));
|
||||
|
||||
let field_permissions: HashMap<Role, Option<types::NamespaceAnnotation>> =
|
||||
permissions::get_allowed_roles_for_type(field_object_type_representation)
|
||||
.map(|role| (role.clone(), None))
|
||||
.collect();
|
||||
|
||||
let input_field = builder.conditional_namespaced(
|
||||
gql_schema::InputField::<GDS>::new(
|
||||
field_graphql_name.clone(),
|
||||
None,
|
||||
annotation,
|
||||
field_type,
|
||||
None,
|
||||
gql_schema::DeprecationStatus::NotDeprecated,
|
||||
),
|
||||
field_permissions,
|
||||
);
|
||||
input_fields.insert(field_graphql_name, input_field);
|
||||
}
|
||||
|
||||
Ok(input_fields)
|
||||
}
|
||||
|
||||
@ -328,6 +376,7 @@ fn build_schema_with_object_boolean_expression_type(
|
||||
|
||||
// add in all fields that are directly comparable
|
||||
input_fields.extend(build_comparable_fields_schema(
|
||||
gds,
|
||||
&object_boolean_expression_type.object_type,
|
||||
object_type_representation,
|
||||
boolean_expression_info,
|
||||
@ -375,6 +424,7 @@ fn build_schema_with_boolean_expression_type(
|
||||
|
||||
// add in all fields that are directly comparable
|
||||
input_fields.extend(build_comparable_fields_schema(
|
||||
gds,
|
||||
&boolean_expression_object_type.object_type,
|
||||
object_type_representation,
|
||||
boolean_expression_info,
|
||||
|
@ -455,6 +455,18 @@ pub(crate) fn get_entity_union_permissions(
|
||||
permissions
|
||||
}
|
||||
|
||||
/// Are we allowed to access a given type at all?
|
||||
/// If we are allowed to access at least one field, yes
|
||||
pub(crate) fn get_allowed_roles_for_type(
|
||||
object_type_representation: &metadata_resolve::ObjectTypeWithRelationships,
|
||||
) -> impl Iterator<Item = &'_ Role> {
|
||||
object_type_representation
|
||||
.object_type
|
||||
.fields
|
||||
.keys()
|
||||
.flat_map(|field_name| get_allowed_roles_for_field(object_type_representation, field_name))
|
||||
}
|
||||
|
||||
/// Build namespace annotations for each field based on the type permissions
|
||||
pub(crate) fn get_allowed_roles_for_field<'a>(
|
||||
object_type_representation: &'a metadata_resolve::ObjectTypeWithRelationships,
|
||||
|
Loading…
Reference in New Issue
Block a user