diff --git a/v3/crates/custom-connector/src/query.rs b/v3/crates/custom-connector/src/query.rs index 7976344bdf0..e85fa69138a 100644 --- a/v3/crates/custom-connector/src/query.rs +++ b/v3/crates/custom-connector/src/query.rs @@ -222,19 +222,23 @@ fn eval_aggregate( ndc_models::Aggregate::StarCount {} => Ok(serde_json::Value::from(paginated.len())), ndc_models::Aggregate::ColumnCount { column, - field_path: _, + field_path, distinct, } => { let values = paginated .iter() .map(|row| { - row.get(column).ok_or(( + let column_value = row.get(column).ok_or(( StatusCode::BAD_REQUEST, Json(ndc_models::ErrorResponse { message: "invalid column name".into(), details: serde_json::Value::Null, }), - )) + ))?; + let field_path_slice = field_path + .as_ref() + .map_or_else(|| [].as_ref(), |p| p.as_slice()); + extract_nested_field(column_value, field_path_slice) }) .collect::>>()?; @@ -270,19 +274,23 @@ fn eval_aggregate( } ndc_models::Aggregate::SingleColumn { column, - field_path: _, + field_path, function, } => { let values = paginated .iter() .map(|row| { - row.get(column).ok_or(( + let column_value = row.get(column).ok_or(( StatusCode::BAD_REQUEST, Json(ndc_models::ErrorResponse { message: "invalid column name".into(), details: serde_json::Value::Null, }), - )) + ))?; + let field_path_slice = field_path + .as_ref() + .map_or_else(|| [].as_ref(), |p| p.as_slice()); + extract_nested_field(column_value, field_path_slice) }) .collect::>>()?; eval_aggregate_function(function, &values) @@ -290,33 +298,151 @@ fn eval_aggregate( } } +fn extract_nested_field<'a>( + value: &'a serde_json::Value, + field_path: &[String], +) -> Result<&'a serde_json::Value> { + if let Some((field, remaining_field_path)) = field_path.split_first() { + // Short circuit on null values + if value.is_null() { + return Ok(value); + } + + let object_value = value.as_object().ok_or_else(|| { + ( + StatusCode::BAD_REQUEST, + Json(ndc_models::ErrorResponse { + message: + "expected object value when extracting a nested field from a field path" + .into(), + details: serde_json::Value::Null, + }), + ) + })?; + + let field_value = object_value.get(field).ok_or_else(|| { + ( + StatusCode::BAD_REQUEST, + Json(ndc_models::ErrorResponse { + message: format!("could not find field {} in nested object", field), + details: serde_json::Value::Null, + }), + ) + })?; + + extract_nested_field(field_value, remaining_field_path) + } else { + Ok(value) + } +} + fn eval_aggregate_function( function: &str, values: &[&serde_json::Value], +) -> Result { + if let Some((first_value, _)) = values.split_first() { + if first_value.is_i64() { + eval_aggregate_function_i64(function, values) + } else if first_value.is_string() { + eval_aggregate_function_string(function, values) + } else { + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ndc_models::ErrorResponse { + message: "Can only aggregate over i64 or string values".into(), + details: serde_json::Value::Null, + }), + )) + } + } else { + // This is a bit of a hack, as technically what this value is should be dependent + // on what type the aggregate function operand is, but it is valid while we only + // support min and max aggregate functions. + Ok(serde_json::Value::Null) + } +} + +fn eval_aggregate_function_i64( + function: &str, + values: &[&serde_json::Value], ) -> Result { let int_values = values .iter() - .map(|value| { - value.as_i64().ok_or(( - StatusCode::BAD_REQUEST, - Json(ndc_models::ErrorResponse { - message: "column is not an integer".into(), - details: serde_json::Value::Null, - }), - )) + .filter_map(|value| { + if value.is_null() { + None + } else { + Some(value.as_i64().ok_or_else(|| { + ( + StatusCode::BAD_REQUEST, + Json(ndc_models::ErrorResponse { + message: "aggregate value is not an integer".into(), + details: (*value).clone(), + }), + ) + })) + } }) .collect::>>()?; + let agg_value = match function { - "min" => Ok(int_values.iter().min()), - "max" => Ok(int_values.iter().max()), + "min" => Ok(int_values.into_iter().min()), + "max" => Ok(int_values.into_iter().max()), _ => Err(( StatusCode::BAD_REQUEST, Json(ndc_models::ErrorResponse { - message: "invalid aggregation function".into(), - details: serde_json::Value::Null, + message: "invalid integer aggregation function".into(), + details: Value::String(function.to_string()), }), )), }?; + + serde_json::to_value(agg_value).map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ndc_models::ErrorResponse { + message: " ".into(), + details: serde_json::Value::Null, + }), + ) + }) +} + +fn eval_aggregate_function_string( + function: &str, + values: &[&serde_json::Value], +) -> Result { + let str_values = values + .iter() + .filter_map(|value| { + if value.is_null() { + None + } else { + Some(value.as_str().ok_or_else(|| { + ( + StatusCode::BAD_REQUEST, + Json(ndc_models::ErrorResponse { + message: "aggregate value is not a string".into(), + details: (*value).clone(), + }), + ) + })) + } + }) + .collect::>>()?; + + let agg_value = match function { + "min" => Ok(str_values.into_iter().min()), + "max" => Ok(str_values.into_iter().max()), + _ => Err(( + StatusCode::BAD_REQUEST, + Json(ndc_models::ErrorResponse { + message: "invalid string aggregation function".into(), + details: Value::String(function.to_string()), + }), + )), + }?; + serde_json::to_value(agg_value).map_err(|_| { ( StatusCode::INTERNAL_SERVER_ERROR, diff --git a/v3/crates/custom-connector/src/schema.rs b/v3/crates/custom-connector/src/schema.rs index 4b3e9bb26aa..1e63e2f0dc0 100644 --- a/v3/crates/custom-connector/src/schema.rs +++ b/v3/crates/custom-connector/src/schema.rs @@ -25,7 +25,7 @@ pub fn get_capabilities() -> ndc_models::CapabilitiesResponse { aggregates: Some(ndc_models::LeafCapability {}), variables: Some(ndc_models::LeafCapability {}), nested_fields: ndc_models::NestedFieldCapabilities { - aggregates: None, + aggregates: Some(ndc_models::LeafCapability {}), filter_by: None, order_by: None, }, diff --git a/v3/crates/custom-connector/src/types.rs b/v3/crates/custom-connector/src/types.rs index 20c645392b1..e3924d70c44 100644 --- a/v3/crates/custom-connector/src/types.rs +++ b/v3/crates/custom-connector/src/types.rs @@ -16,7 +16,28 @@ pub(crate) fn scalar_types() -> BTreeMap { "String".into(), ndc_models::ScalarType { representation: Some(ndc_models::TypeRepresentation::String), - aggregate_functions: BTreeMap::new(), + aggregate_functions: BTreeMap::from_iter([ + ( + "max".into(), + ndc_models::AggregateFunctionDefinition { + result_type: ndc_models::Type::Nullable { + underlying_type: Box::new(ndc_models::Type::Named { + name: "String".into(), + }), + }, + }, + ), + ( + "min".into(), + ndc_models::AggregateFunctionDefinition { + result_type: ndc_models::Type::Nullable { + underlying_type: Box::new(ndc_models::Type::Named { + name: "String".into(), + }), + }, + }, + ), + ]), comparison_operators: BTreeMap::from_iter([( "like".into(), ndc_models::ComparisonOperatorDefinition::Custom { diff --git a/v3/crates/engine/tests/execute/aggregates/common_metadata/custom_connector_schema.json b/v3/crates/engine/tests/execute/aggregates/common_metadata/custom_connector_schema.json new file mode 100644 index 00000000000..ff1964c0e87 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/common_metadata/custom_connector_schema.json @@ -0,0 +1,667 @@ +{ + "version": "v2", + "subgraphs": [ + { + "name": "default", + "objects": [ + { + "definition": { + "name": "custom_connector", + "url": { + "singleUrl": { + "value": "http://custom_connector:8101" + } + }, + "schema": { + "version": "v0.1", + "schema": { + "scalar_types": { + "Actor_Name": { + "representation": { + "type": "string" + }, + "aggregate_functions": {}, + "comparison_operators": {} + }, + "Bool": { + "representation": { + "type": "boolean" + }, + "aggregate_functions": {}, + "comparison_operators": { + "eq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Bool" + } + } + } + }, + "HeaderMap": { + "representation": { + "type": "json" + }, + "aggregate_functions": {}, + "comparison_operators": {} + }, + "Int": { + "representation": { + "type": "int32" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int" + } + } + }, + "min": { + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int" + } + } + } + }, + "comparison_operators": {} + }, + "String": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "min": { + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + } + }, + "comparison_operators": { + "like": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + } + } + } + }, + "object_types": { + "actor": { + "description": "An actor", + "fields": { + "favourite_author_id": { + "description": "The actor's favourite author ID", + "type": { + "type": "named", + "name": "Int" + } + }, + "id": { + "description": "The actor's primary key", + "type": { + "type": "named", + "name": "Int" + } + }, + "movie_id": { + "description": "The actor's movie ID", + "type": { + "type": "named", + "name": "Int" + } + }, + "name": { + "description": "The actor's name", + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "institution": { + "description": "An institution", + "fields": { + "departments": { + "description": "The institution's departments", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "String" + } + } + }, + "id": { + "description": "The institution's primary key", + "type": { + "type": "named", + "name": "Int" + } + }, + "location": { + "description": "The institution's location", + "type": { + "type": "named", + "name": "location" + } + }, + "name": { + "description": "The institution's name", + "type": { + "type": "named", + "name": "String" + } + }, + "staff": { + "description": "The institution's staff", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "staff_member" + } + } + } + } + }, + "location": { + "description": "A location", + "fields": { + "campuses": { + "description": "The location's campuses", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "String" + } + } + }, + "city": { + "description": "The location's city", + "type": { + "type": "named", + "name": "String" + } + }, + "country": { + "description": "The location's country", + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "login_response": { + "description": "Response to a login action", + "fields": { + "headers": { + "description": "Response headers to be forwarded", + "type": { + "type": "named", + "name": "HeaderMap" + } + }, + "response": { + "description": "Authentication successful or not", + "type": { + "type": "named", + "name": "Bool" + } + } + } + }, + "movie": { + "description": "A movie", + "fields": { + "id": { + "description": "The movie's primary key", + "type": { + "type": "named", + "name": "Int" + } + }, + "rating": { + "description": "The movie's rating", + "type": { + "type": "named", + "name": "Int" + } + }, + "title": { + "description": "The movie's title", + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "name_query": { + "description": "parameters for querying by name", + "fields": { + "first_name": { + "description": "The actor's first name or null to match any first name", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "last_name": { + "description": "The actor's last name or null to match any last", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + } + } + }, + "staff_member": { + "description": "A staff member", + "fields": { + "first_name": { + "description": "The staff member's first name", + "type": { + "type": "named", + "name": "String" + } + }, + "last_name": { + "description": "The staff member's last name", + "type": { + "type": "named", + "name": "String" + } + }, + "specialities": { + "description": "The staff member's specialities", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "String" + } + } + } + } + } + }, + "collections": [ + { + "name": "actors", + "description": "A collection of actors", + "arguments": {}, + "type": "actor", + "uniqueness_constraints": { + "ActorByID": { + "unique_columns": ["id"] + } + }, + "foreign_keys": {} + }, + { + "name": "movies", + "description": "A collection of movies", + "arguments": {}, + "type": "movie", + "uniqueness_constraints": { + "MovieByID": { + "unique_columns": ["id"] + } + }, + "foreign_keys": {} + }, + { + "name": "institutions", + "description": "A collection of institutions", + "arguments": {}, + "type": "institution", + "uniqueness_constraints": { + "InstitutionByID": { + "unique_columns": ["id"] + } + }, + "foreign_keys": {} + }, + { + "name": "actors_by_movie", + "description": "Actors parameterized by movie", + "arguments": { + "movie_id": { + "type": { + "type": "named", + "name": "Int" + } + } + }, + "type": "actor", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "movies_by_actor_name", + "description": "Movies filtered by actor name search parameters", + "arguments": { + "actor_name": { + "description": "the actor name components to search by", + "type": { + "type": "named", + "name": "name_query" + } + } + }, + "type": "movie", + "uniqueness_constraints": {}, + "foreign_keys": {} + } + ], + "functions": [ + { + "name": "latest_actor_id", + "description": "Get the ID of the most recent actor", + "arguments": {}, + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int" + } + } + }, + { + "name": "latest_actor_name", + "description": "Get the name of the most recent actor", + "arguments": {}, + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Actor_Name" + } + } + }, + { + "name": "latest_actor", + "description": "Get the most recent actor", + "arguments": {}, + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "actor" + } + } + }, + { + "name": "get_actor_by_id", + "description": "Get actor by ID", + "arguments": { + "id": { + "description": "the id of the actor to fetch", + "type": { + "type": "named", + "name": "Int" + } + } + }, + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "actor" + } + } + }, + { + "name": "get_movie_by_id", + "description": "Get movie by ID", + "arguments": { + "movie_id": { + "description": "the id of the movie to fetch", + "type": { + "type": "named", + "name": "Int" + } + } + }, + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "movie" + } + } + }, + { + "name": "get_actors_by_name", + "description": "Get actors by name", + "arguments": { + "name": { + "description": "the name components to search by", + "type": { + "type": "named", + "name": "name_query" + } + } + }, + "result_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "actor" + } + } + }, + { + "name": "get_actors_by_movie_id", + "description": "Get all actors from a movie by movie ID", + "arguments": { + "movie_id": { + "description": "the id of the movie to fetch the actors from", + "type": { + "type": "named", + "name": "Int" + } + } + }, + "result_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "actor" + } + } + }, + { + "name": "get_all_actors", + "description": "Get all the actors", + "arguments": {}, + "result_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "actor" + } + } + }, + { + "name": "get_all_movies", + "description": "Get all the movies", + "arguments": {}, + "result_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "movie" + } + } + }, + { + "name": "get_institutions_by_institution_query", + "description": "Get institutions by specifying parts of institution object. For example by 'location.city'. All fields are optional.", + "arguments": { + "institution_query": { + "description": "The institution query object. All fields are optional", + "type": { + "type": "named", + "name": "institution" + } + } + }, + "result_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "institution" + } + } + } + ], + "procedures": [ + { + "name": "upsert_actor", + "description": "Insert or update an actor", + "arguments": { + "actor": { + "description": "The actor to insert or update", + "type": { + "type": "named", + "name": "actor" + } + } + }, + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "actor" + } + } + }, + { + "name": "update_actor_name_by_id", + "description": "Update an actor name given the ID and new name", + "arguments": { + "id": { + "description": "the id of the actor to update", + "type": { + "type": "named", + "name": "Int" + } + }, + "name": { + "description": "the new name of the actor", + "type": { + "type": "named", + "name": "String" + } + } + }, + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "actor" + } + } + }, + { + "name": "login", + "description": "Perform a user login", + "arguments": { + "headers": { + "description": "headers required for authentication", + "type": { + "type": "named", + "name": "HeaderMap" + } + }, + "password": { + "description": "password of the user", + "type": { + "type": "named", + "name": "String" + } + }, + "username": { + "description": "username of the user", + "type": { + "type": "named", + "name": "String" + } + } + }, + "result_type": { + "type": "named", + "name": "login_response" + } + }, + { + "name": "noop_procedure", + "description": "Procedure which does not perform any actual mutuations on the data", + "arguments": {}, + "result_type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + } + ] + }, + "capabilities": { + "version": "0.1.3", + "capabilities": { + "query": { + "aggregates": {}, + "variables": {}, + "nested_fields": { + "aggregates": {} + } + }, + "mutation": {}, + "relationships": { + "relation_comparisons": {}, + "order_by_aggregate": {} + } + } + } + } + }, + "version": "v1", + "kind": "DataConnectorLink" + } + ] + } + ] +} diff --git a/v3/crates/engine/tests/execute/aggregates/common_metadata/custom_connector_types.json b/v3/crates/engine/tests/execute/aggregates/common_metadata/custom_connector_types.json new file mode 100644 index 00000000000..6bcd8fa6b3a --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/common_metadata/custom_connector_types.json @@ -0,0 +1,217 @@ +{ + "version": "v2", + "subgraphs": [ + { + "name": "default", + "objects": [ + { + "kind": "ObjectType", + "version": "v1", + "definition": { + "name": "Institution", + "fields": [ + { + "name": "Departments", + "type": "[String!]!", + "description": "The institution's departments" + }, + { + "name": "Id", + "type": "Int", + "description": "The institution's primary key" + }, + { + "name": "Location", + "type": "Location!", + "description": "The institution's location" + }, + { + "name": "Name", + "type": "String!", + "description": "The institution's name" + }, + { + "name": "Staff", + "type": "[StaffMember!]!", + "description": "The institution's staff" + } + ], + "description": "An institution", + "graphql": { + "typeName": "Institution" + }, + "dataConnectorTypeMapping": [ + { + "dataConnectorName": "custom_connector", + "dataConnectorObjectType": "institution", + "fieldMapping": { + "Departments": { + "column": { + "name": "departments" + } + }, + "Id": { + "column": { + "name": "id" + } + }, + "Location": { + "column": { + "name": "location" + } + }, + "Name": { + "column": { + "name": "name" + } + }, + "Staff": { + "column": { + "name": "staff" + } + } + } + } + ] + } + } + ] + }, + { + "name": "default", + "objects": [ + { + "kind": "ObjectType", + "version": "v1", + "definition": { + "name": "Location", + "fields": [ + { + "name": "Campuses", + "type": "[String!]!", + "description": "The location's campuses" + }, + { + "name": "City", + "type": "String!", + "description": "The location's city" + }, + { + "name": "Country", + "type": "String!", + "description": "The location's city" + } + ], + "description": "A location", + "graphql": { + "typeName": "Location" + }, + "dataConnectorTypeMapping": [ + { + "dataConnectorName": "custom_connector", + "dataConnectorObjectType": "location", + "fieldMapping": { + "Campuses": { + "column": { + "name": "campuses" + } + }, + "City": { + "column": { + "name": "city" + } + }, + "Country": { + "column": { + "name": "country" + } + } + } + } + ] + } + } + ] + }, + { + "name": "default", + "objects": [ + { + "kind": "ObjectType", + "version": "v1", + "definition": { + "name": "StaffMember", + "fields": [ + { + "name": "FirstName", + "type": "String!", + "description": "The staff member's first name" + }, + { + "name": "LastName", + "type": "String!", + "description": "The staff member's last name" + }, + { + "name": "Specialities", + "type": "[String!]!", + "description": "The staff member's specialities" + } + ], + "description": "A staff member", + "graphql": { + "typeName": "StaffMember" + }, + "dataConnectorTypeMapping": [ + { + "dataConnectorName": "custom_connector", + "dataConnectorObjectType": "staff_member", + "fieldMapping": { + "FirstName": { + "column": { + "name": "first_name" + } + }, + "LastName": { + "column": { + "name": "last_name" + } + }, + "Specialities": { + "column": { + "name": "specialities" + } + } + } + } + ] + } + }, + { + "kind": "DataConnectorScalarRepresentation", + "version": "v1", + "definition": { + "dataConnectorName": "custom_connector", + "dataConnectorScalarType": "String", + "representation": "String", + "graphql": { + "comparisonExpressionTypeName": "custom_connector_String_comparisonexp" + } + } + }, + { + "kind": "DataConnectorScalarRepresentation", + "version": "v1", + "definition": { + "dataConnectorName": "custom_connector", + "dataConnectorScalarType": "Int", + "representation": "Int", + "graphql": { + "comparisonExpressionTypeName": "custom_connector_Int_comparisonexp" + } + } + } + ] + } + ] +} diff --git a/v3/crates/engine/tests/execute/aggregates/common_metadata/pg_types.json b/v3/crates/engine/tests/execute/aggregates/common_metadata/pg_types.json new file mode 100644 index 00000000000..7e894d20a7d --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/common_metadata/pg_types.json @@ -0,0 +1,226 @@ +{ + "version": "v2", + "subgraphs": [ + { + "name": "default", + "objects": [ + { + "kind": "ObjectType", + "version": "v1", + "definition": { + "name": "Invoice", + "fields": [ + { + "name": "BillingAddress", + "type": "String", + "description": "The billing street address" + }, + { + "name": "BillingCity", + "type": "String", + "description": "The billing address city" + }, + { + "name": "BillingCountry", + "type": "String", + "description": "The billing address country" + }, + { + "name": "BillingPostalCode", + "type": "String", + "description": "The billing address postal code" + }, + { + "name": "BillingState", + "type": "String", + "description": "The billing address state" + }, + { + "name": "CustomerId", + "type": "Int", + "description": "The ID of the Customer" + }, + { + "name": "InvoiceDate", + "type": "Timestamp", + "description": "The date of the invoice" + }, + { + "name": "InvoiceId", + "type": "Int", + "description": "The ID of the Invoice" + }, + { + "name": "Total", + "type": "Numeric", + "description": "The total value of the Invoice" + } + ], + "description": "An invoice where a customer purchases things", + "graphql": { + "typeName": "Invoice" + }, + "dataConnectorTypeMapping": [ + { + "dataConnectorName": "db", + "dataConnectorObjectType": "Invoice", + "fieldMapping": { + "BillingAddress": { + "column": { + "name": "BillingAddress" + } + }, + "BillingCity": { + "column": { + "name": "BillingCity" + } + }, + "BillingCountry": { + "column": { + "name": "BillingCountry" + } + }, + "BillingPostalCode": { + "column": { + "name": "BillingPostalCode" + } + }, + "BillingState": { + "column": { + "name": "BillingState" + } + }, + "CustomerId": { + "column": { + "name": "CustomerId" + } + }, + "InvoiceDate": { + "column": { + "name": "InvoiceDate" + } + }, + "InvoiceId": { + "column": { + "name": "InvoiceId" + } + }, + "Total": { + "column": { + "name": "Total" + } + } + } + } + ] + } + }, + { + "kind": "ScalarType", + "version": "v1", + "definition": { + "name": "Timestamp", + "description": "Timestamp type", + "graphql": { + "typeName": "Timestamp" + } + } + }, + { + "kind": "ScalarType", + "version": "v1", + "definition": { + "name": "Numeric", + "description": "Numeric type", + "graphql": { + "typeName": "Numeric" + } + } + }, + { + "kind": "ScalarType", + "version": "v1", + "definition": { + "name": "Int64", + "description": "Int64 type", + "graphql": { + "typeName": "Int64" + } + } + }, + { + "kind": "DataConnectorScalarRepresentation", + "version": "v1", + "definition": { + "dataConnectorName": "db", + "dataConnectorScalarType": "timestamp", + "representation": "Timestamp", + "graphql": { + "comparisonExpressionTypeName": "db_timestamp_comparisonexp" + } + } + }, + { + "kind": "DataConnectorScalarRepresentation", + "version": "v1", + "definition": { + "dataConnectorName": "db", + "dataConnectorScalarType": "numeric", + "representation": "Numeric", + "graphql": { + "comparisonExpressionTypeName": "db_numeric_comparisonexp" + } + } + }, + { + "kind": "DataConnectorScalarRepresentation", + "version": "v1", + "definition": { + "dataConnectorName": "db", + "dataConnectorScalarType": "varchar", + "representation": "String", + "graphql": { + "comparisonExpressionTypeName": "db_varchar_comparisonexp" + } + } + }, + { + "kind": "DataConnectorScalarRepresentation", + "version": "v1", + "definition": { + "dataConnectorName": "db", + "dataConnectorScalarType": "int4", + "representation": "Int", + "graphql": { + "comparisonExpressionTypeName": "db_int4_comparisonexp" + } + } + }, + { + "kind": "DataConnectorScalarRepresentation", + "version": "v1", + "definition": { + "dataConnectorName": "db", + "dataConnectorScalarType": "int8", + "representation": "Int64", + "graphql": { + "comparisonExpressionTypeName": "db_int8_comparisonexp" + } + } + }, + { + "kind": "DataConnectorScalarRepresentation", + "version": "v1", + "definition": { + "dataConnectorName": "db", + "dataConnectorScalarType": "text", + "representation": "String", + "graphql": { + "comparisonExpressionTypeName": "db_text_comparisonexp" + } + } + } + ] + } + ] +} diff --git a/v3/crates/engine/tests/execute/aggregates/common_metadata/postgres_connector_schema.json b/v3/crates/engine/tests/execute/aggregates/common_metadata/postgres_connector_schema.json new file mode 100644 index 00000000000..3cbe5e51ba8 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/common_metadata/postgres_connector_schema.json @@ -0,0 +1,5820 @@ +{ + "version": "v2", + "subgraphs": [ + { + "name": "default", + "objects": [ + { + "definition": { + "name": "db", + "url": { + "singleUrl": { + "value": "http://postgres_connector:8080" + } + }, + "schema": { + "version": "v0.1", + "schema": { + "scalar_types": { + "bool": { + "representation": { + "type": "boolean" + }, + "aggregate_functions": { + "bool_and": { + "result_type": { + "type": "named", + "name": "bool" + } + }, + "bool_or": { + "result_type": { + "type": "named", + "name": "bool" + } + }, + "every": { + "result_type": { + "type": "named", + "name": "bool" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bool" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bool" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bool" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bool" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bool" + } + } + } + }, + "bpchar": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "bpchar" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "bpchar" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "_ilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_in": { + "type": "in" + }, + "_iregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_like": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "_nilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_niregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_nlike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_nregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_regex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "st_coveredby": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "st_covers": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "st_intersects": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "st_relatematch": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "starts_with": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + }, + "ts_match_tt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bpchar" + } + } + } + }, + "char": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "text" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "text" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_ilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_in": { + "type": "in" + }, + "_iregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_like": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_nilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_niregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_nlike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_nregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "_regex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "st_coveredby": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "st_covers": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "st_intersects": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "st_relatematch": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "starts_with": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + }, + "ts_match_tt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "char" + } + } + } + }, + "character varying": { + "aggregate_functions": {}, + "comparison_operators": {} + }, + "date": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "date" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "date" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "date" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "date" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "date" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "date" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "date" + } + } + } + }, + "float4": { + "representation": { + "type": "json" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "float4" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "float4" + } + }, + "stddev": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "float4" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "variance": { + "result_type": { + "type": "named", + "name": "float8" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float4" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float4" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float4" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float4" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float4" + } + } + } + }, + "float8": { + "representation": { + "type": "json" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "stddev": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "float8" + } + }, + "variance": { + "result_type": { + "type": "named", + "name": "float8" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float8" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float8" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float8" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float8" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "float8" + } + } + } + }, + "int2": { + "representation": { + "type": "json" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "bit_and": { + "result_type": { + "type": "named", + "name": "int2" + } + }, + "bit_or": { + "result_type": { + "type": "named", + "name": "int2" + } + }, + "bit_xor": { + "result_type": { + "type": "named", + "name": "int2" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "int2" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "int2" + } + }, + "stddev": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "int8" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "variance": { + "result_type": { + "type": "named", + "name": "numeric" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int2" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int2" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int2" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int2" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int2" + } + } + } + }, + "int4": { + "representation": { + "type": "json" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "bit_and": { + "result_type": { + "type": "named", + "name": "int4" + } + }, + "bit_or": { + "result_type": { + "type": "named", + "name": "int4" + } + }, + "bit_xor": { + "result_type": { + "type": "named", + "name": "int4" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "int4" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "int4" + } + }, + "stddev": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "int8" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "variance": { + "result_type": { + "type": "named", + "name": "numeric" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int4" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int4" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int4" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int4" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int4" + } + } + } + }, + "int8": { + "representation": { + "type": "json" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "bit_and": { + "result_type": { + "type": "named", + "name": "int8" + } + }, + "bit_or": { + "result_type": { + "type": "named", + "name": "int8" + } + }, + "bit_xor": { + "result_type": { + "type": "named", + "name": "int8" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "int8" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "int8" + } + }, + "stddev": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "variance": { + "result_type": { + "type": "named", + "name": "numeric" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int8" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int8" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int8" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int8" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "int8" + } + } + } + }, + "interval": { + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "interval" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "interval" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "interval" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "interval" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "interval" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "interval" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "interval" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "interval" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "interval" + } + } + } + }, + "numeric": { + "representation": { + "type": "json" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "numeric" + } + }, + "variance": { + "result_type": { + "type": "named", + "name": "numeric" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "numeric" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "numeric" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "numeric" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "numeric" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "numeric" + } + } + } + }, + "text": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "text" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "text" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_ilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_in": { + "type": "in" + }, + "_iregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_like": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_nilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_niregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_nlike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_nregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "_regex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "st_coveredby": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "st_covers": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "st_intersects": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "st_relatematch": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "starts_with": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + }, + "ts_match_tt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "text" + } + } + } + }, + "time": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "interval" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "time" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "time" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "interval" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "time" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "time" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "time" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "time" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "time" + } + } + } + }, + "timestamp": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "timestamp" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "timestamp" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamp" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamp" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamp" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamp" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamp" + } + } + } + }, + "timestamptz": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "timestamptz" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "timestamptz" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamptz" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamptz" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamptz" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamptz" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timestamptz" + } + } + } + }, + "timetz": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "timetz" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "timetz" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timetz" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timetz" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timetz" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timetz" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "timetz" + } + } + } + }, + "uuid": { + "representation": { + "type": "string" + }, + "aggregate_functions": {}, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "uuid" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "uuid" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "uuid" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "uuid" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "uuid" + } + } + } + }, + "varchar": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "text" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "text" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_ilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_in": { + "type": "in" + }, + "_iregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_like": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_nilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_niregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_nlike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_nregex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "_regex": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "st_coveredby": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "st_covers": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "st_intersects": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "st_relatematch": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "starts_with": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + }, + "ts_match_tt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "object_types": { + "Album": { + "description": "The record of all albums", + "fields": { + "AlbumId": { + "description": "The identifier of an album", + "type": { + "type": "named", + "name": "int4" + } + }, + "ArtistId": { + "description": "The id of the artist that authored the album", + "type": { + "type": "named", + "name": "int4" + } + }, + "Title": { + "description": "The title of an album", + "type": { + "type": "named", + "name": "varchar" + } + } + } + }, + "Artist": { + "description": "The record of all artists", + "fields": { + "ArtistId": { + "description": "The identifier of an artist", + "type": { + "type": "named", + "name": "int4" + } + }, + "Name": { + "description": "The name of an artist", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "Customer": { + "description": "The record of all customers", + "fields": { + "Address": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "City": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Company": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Country": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "CustomerId": { + "description": "The identifier of customer", + "type": { + "type": "named", + "name": "int4" + } + }, + "Email": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "Fax": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "FirstName": { + "description": "The first name of a customer", + "type": { + "type": "named", + "name": "varchar" + } + }, + "LastName": { + "description": "The last name of a customer", + "type": { + "type": "named", + "name": "varchar" + } + }, + "Phone": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "PostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "State": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "SupportRepId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + } + } + }, + "Employee": { + "fields": { + "Address": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BirthDate": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timestamp" + } + } + }, + "City": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Country": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Email": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "EmployeeId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Fax": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "FirstName": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "HireDate": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timestamp" + } + } + }, + "LastName": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "Phone": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "PostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "ReportsTo": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "State": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Title": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "Genre": { + "fields": { + "GenreId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "Invoice": { + "fields": { + "BillingAddress": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BillingCity": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BillingCountry": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BillingPostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BillingState": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "CustomerId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "InvoiceDate": { + "type": { + "type": "named", + "name": "timestamp" + } + }, + "InvoiceId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Total": { + "type": { + "type": "named", + "name": "numeric" + } + } + } + }, + "InvoiceLine": { + "fields": { + "InvoiceId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "InvoiceLineId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Quantity": { + "type": { + "type": "named", + "name": "int4" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "UnitPrice": { + "type": { + "type": "named", + "name": "numeric" + } + } + } + }, + "MediaType": { + "fields": { + "MediaTypeId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "Playlist": { + "fields": { + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "PlaylistId": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "PlaylistTrack": { + "fields": { + "PlaylistId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "Track": { + "fields": { + "AlbumId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "Bytes": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "Composer": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "GenreId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "MediaTypeId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Milliseconds": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Name": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "UnitPrice": { + "type": { + "type": "named", + "name": "numeric" + } + } + } + }, + "album_by_title": { + "fields": { + "AlbumId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "ArtistId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "Title": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "article": { + "fields": { + "author_id": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + } + } + }, + "articles_by_author": { + "fields": { + "author_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "title": { + "type": { + "type": "named", + "name": "character varying" + } + } + } + }, + "artist": { + "fields": { + "ArtistId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "artist_below_id": { + "fields": { + "ArtistId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "author": { + "fields": { + "first_name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "last_name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + } + } + }, + "experimental_delete_Album_by_AlbumId_response": { + "description": "Responses from the 'experimental_delete_Album_by_AlbumId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Album" + } + } + } + } + }, + "experimental_delete_Artist_by_ArtistId_response": { + "description": "Responses from the 'experimental_delete_Artist_by_ArtistId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Artist" + } + } + } + } + }, + "experimental_delete_Customer_by_CustomerId_response": { + "description": "Responses from the 'experimental_delete_Customer_by_CustomerId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Customer" + } + } + } + } + }, + "experimental_delete_Employee_by_EmployeeId_response": { + "description": "Responses from the 'experimental_delete_Employee_by_EmployeeId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Employee" + } + } + } + } + }, + "experimental_delete_Genre_by_GenreId_response": { + "description": "Responses from the 'experimental_delete_Genre_by_GenreId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Genre" + } + } + } + } + }, + "experimental_delete_InvoiceLine_by_InvoiceLineId_response": { + "description": "Responses from the 'experimental_delete_InvoiceLine_by_InvoiceLineId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "InvoiceLine" + } + } + } + } + }, + "experimental_delete_Invoice_by_InvoiceId_response": { + "description": "Responses from the 'experimental_delete_Invoice_by_InvoiceId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Invoice" + } + } + } + } + }, + "experimental_delete_MediaType_by_MediaTypeId_response": { + "description": "Responses from the 'experimental_delete_MediaType_by_MediaTypeId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "MediaType" + } + } + } + } + }, + "experimental_delete_Playlist_by_PlaylistId_response": { + "description": "Responses from the 'experimental_delete_Playlist_by_PlaylistId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Playlist" + } + } + } + } + }, + "experimental_delete_Track_by_TrackId_response": { + "description": "Responses from the 'experimental_delete_Track_by_TrackId' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Track" + } + } + } + } + }, + "experimental_delete_article_by_id_response": { + "description": "Responses from the 'experimental_delete_article_by_id' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "article" + } + } + } + } + }, + "experimental_delete_author_by_id_response": { + "description": "Responses from the 'experimental_delete_author_by_id' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "author" + } + } + } + } + }, + "experimental_delete_movie_analytics_by_id_response": { + "description": "Responses from the 'experimental_delete_movie_analytics_by_id' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "movie_analytics" + } + } + } + } + }, + "experimental_delete_spatial_ref_sys_by_srid_response": { + "description": "Responses from the 'experimental_delete_spatial_ref_sys_by_srid' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "spatial_ref_sys" + } + } + } + } + }, + "experimental_delete_topology_topology_by_id_response": { + "description": "Responses from the 'experimental_delete_topology_topology_by_id' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "topology_topology" + } + } + } + } + }, + "experimental_delete_topology_topology_by_name_response": { + "description": "Responses from the 'experimental_delete_topology_topology_by_name' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "topology_topology" + } + } + } + } + }, + "experimental_insert_Album_object": { + "fields": { + "AlbumId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "ArtistId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Title": { + "type": { + "type": "named", + "name": "varchar" + } + } + } + }, + "experimental_insert_Album_response": { + "description": "Responses from the 'experimental_insert_Album' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Album" + } + } + } + } + }, + "experimental_insert_Artist_object": { + "fields": { + "ArtistId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "experimental_insert_Artist_response": { + "description": "Responses from the 'experimental_insert_Artist' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Artist" + } + } + } + } + }, + "experimental_insert_Customer_object": { + "fields": { + "Address": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "City": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Company": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Country": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "CustomerId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Email": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "Fax": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "FirstName": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "LastName": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "Phone": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "PostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "State": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "SupportRepId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + } + } + }, + "experimental_insert_Customer_response": { + "description": "Responses from the 'experimental_insert_Customer' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Customer" + } + } + } + } + }, + "experimental_insert_Employee_object": { + "fields": { + "Address": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BirthDate": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timestamp" + } + } + }, + "City": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Country": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Email": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "EmployeeId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Fax": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "FirstName": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "HireDate": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timestamp" + } + } + }, + "LastName": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "Phone": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "PostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "ReportsTo": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "State": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "Title": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "experimental_insert_Employee_response": { + "description": "Responses from the 'experimental_insert_Employee' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Employee" + } + } + } + } + }, + "experimental_insert_Genre_object": { + "fields": { + "GenreId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "experimental_insert_Genre_response": { + "description": "Responses from the 'experimental_insert_Genre' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Genre" + } + } + } + } + }, + "experimental_insert_InvoiceLine_object": { + "fields": { + "InvoiceId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "InvoiceLineId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Quantity": { + "type": { + "type": "named", + "name": "int4" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "UnitPrice": { + "type": { + "type": "named", + "name": "numeric" + } + } + } + }, + "experimental_insert_InvoiceLine_response": { + "description": "Responses from the 'experimental_insert_InvoiceLine' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "InvoiceLine" + } + } + } + } + }, + "experimental_insert_Invoice_object": { + "fields": { + "BillingAddress": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BillingCity": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BillingCountry": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BillingPostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "BillingState": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "CustomerId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "InvoiceDate": { + "type": { + "type": "named", + "name": "timestamp" + } + }, + "InvoiceId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Total": { + "type": { + "type": "named", + "name": "numeric" + } + } + } + }, + "experimental_insert_Invoice_response": { + "description": "Responses from the 'experimental_insert_Invoice' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Invoice" + } + } + } + } + }, + "experimental_insert_MediaType_object": { + "fields": { + "MediaTypeId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "experimental_insert_MediaType_response": { + "description": "Responses from the 'experimental_insert_MediaType' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "MediaType" + } + } + } + } + }, + "experimental_insert_PlaylistTrack_object": { + "fields": { + "PlaylistId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "experimental_insert_PlaylistTrack_response": { + "description": "Responses from the 'experimental_insert_PlaylistTrack' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "PlaylistTrack" + } + } + } + } + }, + "experimental_insert_Playlist_object": { + "fields": { + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "PlaylistId": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "experimental_insert_Playlist_response": { + "description": "Responses from the 'experimental_insert_Playlist' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Playlist" + } + } + } + } + }, + "experimental_insert_Track_object": { + "fields": { + "AlbumId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "Bytes": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "Composer": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "GenreId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "MediaTypeId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Milliseconds": { + "type": { + "type": "named", + "name": "int4" + } + }, + "Name": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "UnitPrice": { + "type": { + "type": "named", + "name": "numeric" + } + } + } + }, + "experimental_insert_Track_response": { + "description": "Responses from the 'experimental_insert_Track' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Track" + } + } + } + } + }, + "experimental_insert_article_object": { + "fields": { + "author_id": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + } + } + }, + "experimental_insert_article_response": { + "description": "Responses from the 'experimental_insert_article' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "article" + } + } + } + } + }, + "experimental_insert_author_object": { + "fields": { + "first_name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "last_name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + } + } + }, + "experimental_insert_author_response": { + "description": "Responses from the 'experimental_insert_author' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "author" + } + } + } + } + }, + "experimental_insert_movie_analytics_object": { + "fields": { + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "movie_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "movie_name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + }, + "num_users_faved": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "num_users_watchlisted": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "num_views_day": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "num_votes_day": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "prev_day_scores": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "total_votes": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + } + } + }, + "experimental_insert_movie_analytics_response": { + "description": "Responses from the 'experimental_insert_movie_analytics' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "movie_analytics" + } + } + } + } + }, + "experimental_insert_spatial_ref_sys_object": { + "fields": { + "auth_name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "auth_srid": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "proj4text": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "srid": { + "type": { + "type": "named", + "name": "int4" + } + }, + "srtext": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "experimental_insert_spatial_ref_sys_response": { + "description": "Responses from the 'experimental_insert_spatial_ref_sys' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "spatial_ref_sys" + } + } + } + } + }, + "experimental_insert_topology_layer_object": { + "fields": { + "child_id": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "feature_column": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "feature_type": { + "type": { + "type": "named", + "name": "int4" + } + }, + "layer_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "level": { + "type": { + "type": "named", + "name": "int4" + } + }, + "schema_name": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "table_name": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "topology_id": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "experimental_insert_topology_layer_response": { + "description": "Responses from the 'experimental_insert_topology_layer' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "topology_layer" + } + } + } + } + }, + "experimental_insert_topology_topology_object": { + "fields": { + "hasz": { + "type": { + "type": "named", + "name": "bool" + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "name": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "precision": { + "type": { + "type": "named", + "name": "float8" + } + }, + "srid": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "experimental_insert_topology_topology_response": { + "description": "Responses from the 'experimental_insert_topology_topology' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "topology_topology" + } + } + } + } + }, + "movie_analytics": { + "fields": { + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "movie_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "movie_name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + }, + "num_users_faved": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "num_users_watchlisted": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "num_views_day": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "num_votes_day": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "prev_day_scores": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "total_votes": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + } + } + }, + "spatial_ref_sys": { + "fields": { + "auth_name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "auth_srid": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "proj4text": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + }, + "srid": { + "type": { + "type": "named", + "name": "int4" + } + }, + "srtext": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + }, + "topology_layer": { + "fields": { + "child_id": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "feature_column": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "feature_type": { + "type": { + "type": "named", + "name": "int4" + } + }, + "layer_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "level": { + "type": { + "type": "named", + "name": "int4" + } + }, + "schema_name": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "table_name": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "topology_id": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "topology_topology": { + "fields": { + "hasz": { + "type": { + "type": "named", + "name": "bool" + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "name": { + "type": { + "type": "named", + "name": "varchar" + } + }, + "precision": { + "type": { + "type": "named", + "name": "float8" + } + }, + "srid": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "value_types": { + "fields": { + "bool": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "bool" + } + } + }, + "char": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "char" + } + } + }, + "date": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "date" + } + } + }, + "float4": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "float4" + } + } + }, + "float8": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "float8" + } + } + }, + "int2": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int2" + } + } + }, + "int4": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "int8": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int8" + } + } + }, + "numeric": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "numeric" + } + } + }, + "text": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + }, + "time": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "time" + } + } + }, + "timestamp": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timestamp" + } + } + }, + "timestamptz": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timestamptz" + } + } + }, + "timetz": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timetz" + } + } + }, + "uuid": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "uuid" + } + } + }, + "varchar": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + } + } + }, + "collections": [ + { + "name": "Album", + "description": "The record of all albums", + "arguments": {}, + "type": "Album", + "uniqueness_constraints": { + "PK_Album": { + "unique_columns": ["AlbumId"] + } + }, + "foreign_keys": { + "FK_AlbumArtistId": { + "column_mapping": { + "ArtistId": "ArtistId" + }, + "foreign_collection": "Artist" + } + } + }, + { + "name": "Artist", + "description": "The record of all artists", + "arguments": {}, + "type": "Artist", + "uniqueness_constraints": { + "PK_Artist": { + "unique_columns": ["ArtistId"] + } + }, + "foreign_keys": {} + }, + { + "name": "Customer", + "description": "The record of all customers", + "arguments": {}, + "type": "Customer", + "uniqueness_constraints": { + "PK_Customer": { + "unique_columns": ["CustomerId"] + } + }, + "foreign_keys": { + "FK_CustomerSupportRepId": { + "column_mapping": { + "SupportRepId": "EmployeeId" + }, + "foreign_collection": "Employee" + } + } + }, + { + "name": "Employee", + "arguments": {}, + "type": "Employee", + "uniqueness_constraints": { + "PK_Employee": { + "unique_columns": ["EmployeeId"] + } + }, + "foreign_keys": { + "FK_EmployeeReportsTo": { + "column_mapping": { + "ReportsTo": "EmployeeId" + }, + "foreign_collection": "Employee" + } + } + }, + { + "name": "Genre", + "arguments": {}, + "type": "Genre", + "uniqueness_constraints": { + "PK_Genre": { + "unique_columns": ["GenreId"] + } + }, + "foreign_keys": {} + }, + { + "name": "Invoice", + "arguments": {}, + "type": "Invoice", + "uniqueness_constraints": { + "PK_Invoice": { + "unique_columns": ["InvoiceId"] + } + }, + "foreign_keys": { + "FK_InvoiceCustomerId": { + "column_mapping": { + "CustomerId": "CustomerId" + }, + "foreign_collection": "Customer" + } + } + }, + { + "name": "InvoiceLine", + "arguments": {}, + "type": "InvoiceLine", + "uniqueness_constraints": { + "PK_InvoiceLine": { + "unique_columns": ["InvoiceLineId"] + } + }, + "foreign_keys": { + "FK_InvoiceLineInvoiceId": { + "column_mapping": { + "InvoiceId": "InvoiceId" + }, + "foreign_collection": "Invoice" + }, + "FK_InvoiceLineTrackId": { + "column_mapping": { + "TrackId": "TrackId" + }, + "foreign_collection": "Track" + } + } + }, + { + "name": "MediaType", + "arguments": {}, + "type": "MediaType", + "uniqueness_constraints": { + "PK_MediaType": { + "unique_columns": ["MediaTypeId"] + } + }, + "foreign_keys": {} + }, + { + "name": "Playlist", + "arguments": {}, + "type": "Playlist", + "uniqueness_constraints": { + "PK_Playlist": { + "unique_columns": ["PlaylistId"] + } + }, + "foreign_keys": {} + }, + { + "name": "PlaylistTrack", + "arguments": {}, + "type": "PlaylistTrack", + "uniqueness_constraints": { + "PK_PlaylistTrack": { + "unique_columns": ["PlaylistId", "TrackId"] + } + }, + "foreign_keys": { + "FK_PlaylistTrackPlaylistId": { + "column_mapping": { + "PlaylistId": "PlaylistId" + }, + "foreign_collection": "Playlist" + }, + "FK_PlaylistTrackTrackId": { + "column_mapping": { + "TrackId": "TrackId" + }, + "foreign_collection": "Track" + } + } + }, + { + "name": "Track", + "arguments": {}, + "type": "Track", + "uniqueness_constraints": { + "PK_Track": { + "unique_columns": ["TrackId"] + } + }, + "foreign_keys": { + "FK_TrackAlbumId": { + "column_mapping": { + "AlbumId": "AlbumId" + }, + "foreign_collection": "Album" + }, + "FK_TrackGenreId": { + "column_mapping": { + "GenreId": "GenreId" + }, + "foreign_collection": "Genre" + }, + "FK_TrackMediaTypeId": { + "column_mapping": { + "MediaTypeId": "MediaTypeId" + }, + "foreign_collection": "MediaType" + } + } + }, + { + "name": "article", + "arguments": {}, + "type": "article", + "uniqueness_constraints": { + "article_pkey": { + "unique_columns": ["id"] + } + }, + "foreign_keys": {} + }, + { + "name": "author", + "arguments": {}, + "type": "author", + "uniqueness_constraints": { + "author_pkey": { + "unique_columns": ["id"] + } + }, + "foreign_keys": {} + }, + { + "name": "movie_analytics", + "arguments": {}, + "type": "movie_analytics", + "uniqueness_constraints": { + "movie_analytics_pkey": { + "unique_columns": ["id"] + } + }, + "foreign_keys": {} + }, + { + "name": "spatial_ref_sys", + "arguments": {}, + "type": "spatial_ref_sys", + "uniqueness_constraints": { + "spatial_ref_sys_pkey": { + "unique_columns": ["srid"] + } + }, + "foreign_keys": {} + }, + { + "name": "topology_layer", + "arguments": {}, + "type": "topology_layer", + "uniqueness_constraints": { + "layer_pkey": { + "unique_columns": ["layer_id", "topology_id"] + }, + "layer_schema_name_table_name_feature_column_key": { + "unique_columns": [ + "feature_column", + "schema_name", + "table_name" + ] + } + }, + "foreign_keys": { + "layer_topology_id_fkey": { + "column_mapping": { + "topology_id": "id" + }, + "foreign_collection": "topology_topology" + } + } + }, + { + "name": "topology_topology", + "arguments": {}, + "type": "topology_topology", + "uniqueness_constraints": { + "topology_name_key": { + "unique_columns": ["name"] + }, + "topology_pkey": { + "unique_columns": ["id"] + } + }, + "foreign_keys": {} + }, + { + "name": "album_by_title", + "arguments": { + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + }, + "type": "album_by_title", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "articles_by_author", + "arguments": { + "author_id": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + } + }, + "type": "articles_by_author", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "artist", + "arguments": {}, + "type": "artist", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "artist_below_id", + "arguments": { + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + } + }, + "type": "artist_below_id", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "value_types", + "arguments": { + "bool": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "bool" + } + } + }, + "char": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "char" + } + } + }, + "date": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "date" + } + } + }, + "float4": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "float4" + } + } + }, + "float8": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "float8" + } + } + }, + "int2": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int2" + } + } + }, + "int4": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int4" + } + } + }, + "int8": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "int8" + } + } + }, + "numeric": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "numeric" + } + } + }, + "text": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + }, + "time": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "time" + } + } + }, + "timestamp": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timestamp" + } + } + }, + "timestamptz": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timestamptz" + } + } + }, + "timetz": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "timetz" + } + } + }, + "uuid": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "uuid" + } + } + }, + "varchar": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "varchar" + } + } + } + }, + "type": "value_types", + "uniqueness_constraints": {}, + "foreign_keys": {} + } + ], + "functions": [], + "procedures": [ + { + "name": "experimental_delete_Album_by_AlbumId", + "description": "Delete any value on the 'Album' collection using the 'AlbumId' key", + "arguments": { + "AlbumId": { + "description": "The identifier of an album", + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'Album' collection", + "type": { + "type": "predicate", + "object_type_name": "Album" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_Album_by_AlbumId_response" + } + }, + { + "name": "experimental_delete_Artist_by_ArtistId", + "description": "Delete any value on the 'Artist' collection using the 'ArtistId' key", + "arguments": { + "ArtistId": { + "description": "The identifier of an artist", + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'Artist' collection", + "type": { + "type": "predicate", + "object_type_name": "Artist" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_Artist_by_ArtistId_response" + } + }, + { + "name": "experimental_delete_Customer_by_CustomerId", + "description": "Delete any value on the 'Customer' collection using the 'CustomerId' key", + "arguments": { + "CustomerId": { + "description": "The identifier of customer", + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'Customer' collection", + "type": { + "type": "predicate", + "object_type_name": "Customer" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_Customer_by_CustomerId_response" + } + }, + { + "name": "experimental_delete_Employee_by_EmployeeId", + "description": "Delete any value on the 'Employee' collection using the 'EmployeeId' key", + "arguments": { + "EmployeeId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'Employee' collection", + "type": { + "type": "predicate", + "object_type_name": "Employee" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_Employee_by_EmployeeId_response" + } + }, + { + "name": "experimental_delete_Genre_by_GenreId", + "description": "Delete any value on the 'Genre' collection using the 'GenreId' key", + "arguments": { + "GenreId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'Genre' collection", + "type": { + "type": "predicate", + "object_type_name": "Genre" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_Genre_by_GenreId_response" + } + }, + { + "name": "experimental_delete_InvoiceLine_by_InvoiceLineId", + "description": "Delete any value on the 'InvoiceLine' collection using the 'InvoiceLineId' key", + "arguments": { + "InvoiceLineId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'InvoiceLine' collection", + "type": { + "type": "predicate", + "object_type_name": "InvoiceLine" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_InvoiceLine_by_InvoiceLineId_response" + } + }, + { + "name": "experimental_delete_Invoice_by_InvoiceId", + "description": "Delete any value on the 'Invoice' collection using the 'InvoiceId' key", + "arguments": { + "InvoiceId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'Invoice' collection", + "type": { + "type": "predicate", + "object_type_name": "Invoice" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_Invoice_by_InvoiceId_response" + } + }, + { + "name": "experimental_delete_MediaType_by_MediaTypeId", + "description": "Delete any value on the 'MediaType' collection using the 'MediaTypeId' key", + "arguments": { + "MediaTypeId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'MediaType' collection", + "type": { + "type": "predicate", + "object_type_name": "MediaType" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_MediaType_by_MediaTypeId_response" + } + }, + { + "name": "experimental_delete_Playlist_by_PlaylistId", + "description": "Delete any value on the 'Playlist' collection using the 'PlaylistId' key", + "arguments": { + "PlaylistId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'Playlist' collection", + "type": { + "type": "predicate", + "object_type_name": "Playlist" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_Playlist_by_PlaylistId_response" + } + }, + { + "name": "experimental_delete_Track_by_TrackId", + "description": "Delete any value on the 'Track' collection using the 'TrackId' key", + "arguments": { + "TrackId": { + "type": { + "type": "named", + "name": "int4" + } + }, + "filter": { + "description": "Delete permission predicate over the 'Track' collection", + "type": { + "type": "predicate", + "object_type_name": "Track" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_Track_by_TrackId_response" + } + }, + { + "name": "experimental_delete_article_by_id", + "description": "Delete any value on the 'article' collection using the 'id' key", + "arguments": { + "filter": { + "description": "Delete permission predicate over the 'article' collection", + "type": { + "type": "predicate", + "object_type_name": "article" + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_article_by_id_response" + } + }, + { + "name": "experimental_delete_author_by_id", + "description": "Delete any value on the 'author' collection using the 'id' key", + "arguments": { + "filter": { + "description": "Delete permission predicate over the 'author' collection", + "type": { + "type": "predicate", + "object_type_name": "author" + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_author_by_id_response" + } + }, + { + "name": "experimental_delete_movie_analytics_by_id", + "description": "Delete any value on the 'movie_analytics' collection using the 'id' key", + "arguments": { + "filter": { + "description": "Delete permission predicate over the 'movie_analytics' collection", + "type": { + "type": "predicate", + "object_type_name": "movie_analytics" + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_movie_analytics_by_id_response" + } + }, + { + "name": "experimental_delete_spatial_ref_sys_by_srid", + "description": "Delete any value on the 'spatial_ref_sys' collection using the 'srid' key", + "arguments": { + "filter": { + "description": "Delete permission predicate over the 'spatial_ref_sys' collection", + "type": { + "type": "predicate", + "object_type_name": "spatial_ref_sys" + } + }, + "srid": { + "type": { + "type": "named", + "name": "int4" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_spatial_ref_sys_by_srid_response" + } + }, + { + "name": "experimental_delete_topology_topology_by_id", + "description": "Delete any value on the 'topology_topology' collection using the 'id' key", + "arguments": { + "filter": { + "description": "Delete permission predicate over the 'topology_topology' collection", + "type": { + "type": "predicate", + "object_type_name": "topology_topology" + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_topology_topology_by_id_response" + } + }, + { + "name": "experimental_delete_topology_topology_by_name", + "description": "Delete any value on the 'topology_topology' collection using the 'name' key", + "arguments": { + "filter": { + "description": "Delete permission predicate over the 'topology_topology' collection", + "type": { + "type": "predicate", + "object_type_name": "topology_topology" + } + }, + "name": { + "type": { + "type": "named", + "name": "varchar" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_delete_topology_topology_by_name_response" + } + }, + { + "name": "experimental_insert_Album", + "description": "Insert into the Album table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_Album_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'Album' collection", + "type": { + "type": "predicate", + "object_type_name": "Album" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_Album_response" + } + }, + { + "name": "experimental_insert_Artist", + "description": "Insert into the Artist table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_Artist_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'Artist' collection", + "type": { + "type": "predicate", + "object_type_name": "Artist" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_Artist_response" + } + }, + { + "name": "experimental_insert_Customer", + "description": "Insert into the Customer table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_Customer_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'Customer' collection", + "type": { + "type": "predicate", + "object_type_name": "Customer" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_Customer_response" + } + }, + { + "name": "experimental_insert_Employee", + "description": "Insert into the Employee table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_Employee_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'Employee' collection", + "type": { + "type": "predicate", + "object_type_name": "Employee" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_Employee_response" + } + }, + { + "name": "experimental_insert_Genre", + "description": "Insert into the Genre table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_Genre_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'Genre' collection", + "type": { + "type": "predicate", + "object_type_name": "Genre" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_Genre_response" + } + }, + { + "name": "experimental_insert_Invoice", + "description": "Insert into the Invoice table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_Invoice_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'Invoice' collection", + "type": { + "type": "predicate", + "object_type_name": "Invoice" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_Invoice_response" + } + }, + { + "name": "experimental_insert_InvoiceLine", + "description": "Insert into the InvoiceLine table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_InvoiceLine_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'InvoiceLine' collection", + "type": { + "type": "predicate", + "object_type_name": "InvoiceLine" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_InvoiceLine_response" + } + }, + { + "name": "experimental_insert_MediaType", + "description": "Insert into the MediaType table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_MediaType_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'MediaType' collection", + "type": { + "type": "predicate", + "object_type_name": "MediaType" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_MediaType_response" + } + }, + { + "name": "experimental_insert_Playlist", + "description": "Insert into the Playlist table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_Playlist_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'Playlist' collection", + "type": { + "type": "predicate", + "object_type_name": "Playlist" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_Playlist_response" + } + }, + { + "name": "experimental_insert_PlaylistTrack", + "description": "Insert into the PlaylistTrack table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_PlaylistTrack_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'PlaylistTrack' collection", + "type": { + "type": "predicate", + "object_type_name": "PlaylistTrack" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_PlaylistTrack_response" + } + }, + { + "name": "experimental_insert_Track", + "description": "Insert into the Track table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_Track_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'Track' collection", + "type": { + "type": "predicate", + "object_type_name": "Track" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_Track_response" + } + }, + { + "name": "experimental_insert_article", + "description": "Insert into the article table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_article_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'article' collection", + "type": { + "type": "predicate", + "object_type_name": "article" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_article_response" + } + }, + { + "name": "experimental_insert_author", + "description": "Insert into the author table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_author_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'author' collection", + "type": { + "type": "predicate", + "object_type_name": "author" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_author_response" + } + }, + { + "name": "experimental_insert_movie_analytics", + "description": "Insert into the movie_analytics table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_movie_analytics_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'movie_analytics' collection", + "type": { + "type": "predicate", + "object_type_name": "movie_analytics" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_movie_analytics_response" + } + }, + { + "name": "experimental_insert_spatial_ref_sys", + "description": "Insert into the spatial_ref_sys table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_spatial_ref_sys_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'spatial_ref_sys' collection", + "type": { + "type": "predicate", + "object_type_name": "spatial_ref_sys" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_spatial_ref_sys_response" + } + }, + { + "name": "experimental_insert_topology_layer", + "description": "Insert into the topology_layer table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_topology_layer_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'topology_layer' collection", + "type": { + "type": "predicate", + "object_type_name": "topology_layer" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_topology_layer_response" + } + }, + { + "name": "experimental_insert_topology_topology", + "description": "Insert into the topology_topology table", + "arguments": { + "_objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "experimental_insert_topology_topology_object" + } + } + }, + "constraint": { + "description": "Insert permission predicate over the 'topology_topology' collection", + "type": { + "type": "predicate", + "object_type_name": "topology_topology" + } + } + }, + "result_type": { + "type": "named", + "name": "experimental_insert_topology_topology_response" + } + } + ] + }, + "capabilities": { + "version": "0.1.3", + "capabilities": { + "query": { + "aggregates": {}, + "variables": {}, + "explain": {}, + "nested_fields": { + "filter_by": {}, + "order_by": {} + } + }, + "mutation": { + "transactional": {}, + "explain": {} + }, + "relationships": { + "relation_comparisons": {}, + "order_by_aggregate": {} + } + } + } + } + }, + "version": "v1", + "kind": "DataConnectorLink" + } + ] + } + ] +} diff --git a/v3/crates/engine/tests/execute/aggregates/common_metadata/supergraph.json b/v3/crates/engine/tests/execute/aggregates/common_metadata/supergraph.json new file mode 100644 index 00000000000..cead584ce86 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/common_metadata/supergraph.json @@ -0,0 +1,56 @@ +{ + "version": "v2", + "supergraph": { + "objects": [ + { + "kind": "GraphqlConfig", + "version": "v1", + "definition": { + "query": { + "rootOperationTypeName": "Query", + "argumentsInput": { + "fieldName": "args" + }, + "limitInput": { + "fieldName": "limit" + }, + "offsetInput": { + "fieldName": "offset" + }, + "filterInput": { + "fieldName": "where", + "operatorNames": { + "and": "_and", + "or": "_or", + "not": "_not", + "isNull": "_is_null" + } + }, + "orderByInput": { + "fieldName": "order_by", + "enumDirectionValues": { + "asc": "Asc", + "desc": "Desc" + }, + "enumTypeNames": [ + { + "directions": ["Asc", "Desc"], + "typeName": "OrderBy" + } + ] + }, + "aggregate": { + "filterInputFieldName": "filter_input", + "countFieldName": "_count", + "countDistinctFieldName": "_count_distinct" + } + }, + "mutation": { + "rootOperationTypeName": "Mutation" + }, + "apolloFederation": null + } + } + ] + } +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/filtering/expected.json b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/expected.json new file mode 100644 index 00000000000..28ee78b01ab --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/expected.json @@ -0,0 +1,33 @@ +[ + { + "data": { + "Invoice_aggregate": { + "BillingState": { + "_min": "AZ", + "_max": "WI", + "_count_distinct": 8 + }, + "InvoiceId": { + "min": 17, + "max": 71, + "count": 10 + }, + "Total": { + "_min": 0.99, + "_max": 13.86, + "_sum": 58.41, + "_stddev": 3.944846004598912 + }, + "count_all": 10 + } + } + }, + { + "data": null, + "errors": [ + { + "message": "validation failed: the field BillingCountry on type Invoice_boolexp is not found" + } + ] + } +] diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/filtering/introspection_expected.json b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/introspection_expected.json new file mode 100644 index 00000000000..9cfe2144ce6 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/introspection_expected.json @@ -0,0 +1,1105 @@ +[ + { + "data": { + "__schema": { + "queryType": { + "name": "Query", + "fields": [ + { + "name": "Invoice", + "description": null, + "args": [ + { + "name": "limit", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "order_by", + "description": null, + "defaultValue": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "Invoice_orderby" + } + } + } + }, + { + "name": "where", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_boolexp", + "ofType": null + } + } + ] + }, + { + "name": "InvoiceByInvoiceId", + "description": null, + "args": [ + { + "name": "InvoiceId", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "Invoice_aggregate", + "description": null, + "args": [ + { + "name": "filter_input", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_filter_input", + "ofType": null + } + } + ] + } + ] + } + }, + "Invoice_filter_input_type": { + "name": "Invoice_filter_input", + "description": null, + "kind": "INPUT_OBJECT", + "inputFields": [ + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "order_by", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "Invoice_orderby" + } + } + } + }, + { + "name": "where", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_boolexp", + "ofType": null + } + } + ] + }, + "Invoice_aggregate_exp_type": { + "name": "Invoice_aggregate_exp", + "description": "Aggregate expression for the Invoice type", + "kind": "OBJECT", + "fields": [ + { + "name": "BillingAddress", + "description": "Aggregation over the billing address", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingCity", + "description": "Aggregation over the billing city", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingCountry", + "description": "Aggregation over the billing country", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingPostalCode", + "description": "Aggregation over the billing postal code", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingState", + "description": "Aggregation over the billing state", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "CustomerId", + "description": "Aggregation over the customer ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceDate", + "description": "Aggregation over the invoice date", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Timestamp_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceId", + "description": "Aggregation over the invoice ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Total", + "description": "Aggregation over the invoice total", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Numeric_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of invoices", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "String_aggregate_exp_type": { + "name": "String_aggregate_exp", + "description": "Aggregate expression for the String type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ] + }, + "Int_aggregate_exp_type": { + "name": "Int_aggregate_exp", + "description": "Aggregate expression for the Int type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": "Count of all non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": "Count of all distinct non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": "Largest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_min", + "description": "Smallest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": "Standard deviation across integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": "Sum of all integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int64", + "ofType": null + } + } + } + ] + }, + "Numeric_aggregate_exp_type": { + "name": "Numeric_aggregate_exp", + "description": "Aggregate expression for the Numeric type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + } + ] + }, + "Timestamp_aggregate_exp_type": { + "name": "Timestamp_aggregate_exp", + "description": "Aggregate expression for the Timestamp type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + } + ] + } + } + }, + { + "data": { + "__schema": { + "queryType": { + "name": "Query", + "fields": [ + { + "name": "Invoice", + "description": null, + "args": [ + { + "name": "limit", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "order_by", + "description": null, + "defaultValue": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "Invoice_orderby" + } + } + } + }, + { + "name": "where", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_boolexp", + "ofType": null + } + } + ] + }, + { + "name": "InvoiceByInvoiceId", + "description": null, + "args": [ + { + "name": "InvoiceId", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "Invoice_aggregate", + "description": null, + "args": [ + { + "name": "filter_input", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_filter_input", + "ofType": null + } + } + ] + } + ] + } + }, + "Invoice_filter_input_type": { + "name": "Invoice_filter_input", + "description": null, + "kind": "INPUT_OBJECT", + "inputFields": [ + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "order_by", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "Invoice_orderby" + } + } + } + }, + { + "name": "where", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_boolexp", + "ofType": null + } + } + ] + }, + "Invoice_aggregate_exp_type": { + "name": "Invoice_aggregate_exp", + "description": "Aggregate expression for the Invoice type", + "kind": "OBJECT", + "fields": [ + { + "name": "BillingPostalCode", + "description": "Aggregation over the billing postal code", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingState", + "description": "Aggregation over the billing state", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "CustomerId", + "description": "Aggregation over the customer ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceDate", + "description": "Aggregation over the invoice date", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Timestamp_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceId", + "description": "Aggregation over the invoice ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Total", + "description": "Aggregation over the invoice total", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Numeric_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of invoices", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "String_aggregate_exp_type": { + "name": "String_aggregate_exp", + "description": "Aggregate expression for the String type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ] + }, + "Int_aggregate_exp_type": { + "name": "Int_aggregate_exp", + "description": "Aggregate expression for the Int type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": "Count of all non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": "Count of all distinct non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": "Largest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_min", + "description": "Smallest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": "Standard deviation across integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": "Sum of all integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int64", + "ofType": null + } + } + } + ] + }, + "Numeric_aggregate_exp_type": { + "name": "Numeric_aggregate_exp", + "description": "Aggregate expression for the Numeric type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + } + ] + }, + "Timestamp_aggregate_exp_type": { + "name": "Timestamp_aggregate_exp", + "description": "Aggregate expression for the Timestamp type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + } + ] + } + } + } +] diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/filtering/introspection_request.gql b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/introspection_request.gql new file mode 100644 index 00000000000..74c36e7ca37 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/introspection_request.gql @@ -0,0 +1,154 @@ +query MyQuery { + __schema { + queryType { + name + fields { + name + description + args { + name + description + defaultValue + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + Invoice_filter_input_type: __type(name: "Invoice_filter_input") { + name + description + kind + inputFields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Invoice_aggregate_exp_type: __type(name: "Invoice_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + String_aggregate_exp_type: __type(name: "String_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Int_aggregate_exp_type: __type(name: "Int_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Numeric_aggregate_exp_type: __type(name: "Numeric_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Timestamp_aggregate_exp_type: __type(name: "Timestamp_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/filtering/metadata.json b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/metadata.json new file mode 100644 index 00000000000..d9a23dec722 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/metadata.json @@ -0,0 +1,521 @@ +{ + "version": "v2", + "subgraphs": [ + { + "name": "default", + "objects": [ + { + "kind": "Model", + "version": "v1", + "definition": { + "name": "Invoice", + "objectType": "Invoice", + "source": { + "dataConnectorName": "db", + "collection": "Invoice" + }, + "filterExpressionType": "Invoice_boolexp", + "aggregateExpression": "Invoice_aggregate_exp", + "orderableFields": [ + { + "fieldName": "BillingAddress", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "BillingCity", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "BillingCountry", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "BillingPostalCode", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "BillingState", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "CustomerId", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "InvoiceDate", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "InvoiceId", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "Total", + "orderByDirections": { + "enableAll": true + } + } + ], + "graphql": { + "filterInputTypeName": "Invoice_filter_input", + "aggregate": { + "queryRootField": "Invoice_aggregate" + }, + "selectMany": { + "queryRootField": "Invoice" + }, + "selectUniques": [ + { + "queryRootField": "InvoiceByInvoiceId", + "uniqueIdentifier": ["InvoiceId"] + } + ], + "orderByExpressionType": "Invoice_orderby" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Invoice_aggregate_exp", + "operand": { + "object": { + "aggregatedType": "Invoice", + "aggregatableFields": [ + { + "fieldName": "BillingAddress", + "description": "Aggregation over the billing address", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "BillingCity", + "description": "Aggregation over the billing city", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "BillingCountry", + "description": "Aggregation over the billing country", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "BillingPostalCode", + "description": "Aggregation over the billing postal code", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "BillingState", + "description": "Aggregation over the billing state", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "CustomerId", + "description": "Aggregation over the customer ID", + "aggregateExpression": "Int_aggregate_exp" + }, + { + "fieldName": "InvoiceDate", + "description": "Aggregation over the invoice date", + "aggregateExpression": "Timestamp_aggregate_exp" + }, + { + "fieldName": "InvoiceId", + "description": "Aggregation over the invoice ID", + "aggregateExpression": "Int_aggregate_exp" + }, + { + "fieldName": "Total", + "description": "Aggregation over the invoice total", + "aggregateExpression": "Numeric_aggregate_exp" + } + ] + } + }, + "count": { + "enable": true, + "description": "Count of invoices" + }, + "description": "Aggregate expression for the Invoice type", + "graphql": { + "selectTypeName": "Invoice_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Int_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "Int", + "aggregationFunctions": [ + { + "name": "_sum", + "description": "Sum of all integers", + "returnType": "Int64!" + }, + { + "name": "_min", + "description": "Smallest integer", + "returnType": "Int!" + }, + { + "name": "_max", + "description": "Largest integer", + "returnType": "Int!" + }, + { + "name": "_stddev", + "description": "Standard deviation across integers", + "returnType": "Numeric!" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "db", + "dataConnectorScalarType": "int4", + "functionMapping": { + "_sum": { + "name": "sum" + }, + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + }, + "_stddev": { + "name": "stddev" + } + } + } + ] + } + }, + "count": { + "enable": true, + "description": "Count of all non-null integers" + }, + "countDistinct": { + "enable": true, + "description": "Count of all distinct non-null integers" + }, + "description": "Aggregate expression for the Int type", + "graphql": { + "selectTypeName": "Int_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Numeric_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "Numeric", + "aggregationFunctions": [ + { + "name": "_sum", + "returnType": "Numeric!" + }, + { + "name": "_min", + "returnType": "Numeric!" + }, + { + "name": "_max", + "returnType": "Numeric!" + }, + { + "name": "_stddev", + "returnType": "Numeric!" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "db", + "dataConnectorScalarType": "numeric", + "functionMapping": { + "_sum": { + "name": "sum" + }, + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + }, + "_stddev": { + "name": "stddev" + } + } + } + ] + } + }, + "count": { + "enable": true + }, + "countDistinct": { + "enable": true + }, + "description": "Aggregate expression for the Numeric type", + "graphql": { + "selectTypeName": "Numeric_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "String_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "String", + "aggregationFunctions": [ + { + "name": "_min", + "returnType": "String!" + }, + { + "name": "_max", + "returnType": "String!" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "db", + "dataConnectorScalarType": "varchar", + "functionMapping": { + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + } + } + }, + { + "dataConnectorName": "db", + "dataConnectorScalarType": "text", + "functionMapping": { + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + } + } + } + ] + } + }, + "count": { + "enable": true + }, + "countDistinct": { + "enable": true + }, + "description": "Aggregate expression for the String type", + "graphql": { + "selectTypeName": "String_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Timestamp_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "Timestamp", + "aggregationFunctions": [ + { + "name": "_min", + "returnType": "Timestamp!" + }, + { + "name": "_max", + "returnType": "Timestamp!" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "db", + "dataConnectorScalarType": "timestamp", + "functionMapping": { + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + } + } + } + ] + } + }, + "count": { + "enable": true + }, + "countDistinct": { + "enable": true + }, + "description": "Aggregate expression for the Timestamp type", + "graphql": { + "selectTypeName": "Timestamp_aggregate_exp" + } + } + }, + { + "kind": "ObjectBooleanExpressionType", + "version": "v1", + "definition": { + "name": "Invoice_boolexp", + "objectType": "Invoice", + "dataConnectorName": "db", + "dataConnectorObjectType": "Invoice", + "comparableFields": [ + { + "fieldName": "BillingAddress", + "operators": { + "enableAll": true + } + }, + { + "fieldName": "BillingCity", + "operators": { + "enableAll": true + } + }, + { + "fieldName": "BillingCountry", + "operators": { + "enableAll": true + } + }, + { + "fieldName": "BillingPostalCode", + "operators": { + "enableAll": true + } + }, + { + "fieldName": "BillingState", + "operators": { + "enableAll": true + } + }, + { + "fieldName": "CustomerId", + "operators": { + "enableAll": true + } + }, + { + "fieldName": "InvoiceDate", + "operators": { + "enableAll": true + } + }, + { + "fieldName": "InvoiceId", + "operators": { + "enableAll": true + } + }, + { + "fieldName": "Total", + "operators": { + "enableAll": true + } + } + ], + "graphql": { + "typeName": "Invoice_boolexp" + } + } + }, + { + "kind": "TypePermissions", + "version": "v1", + "definition": { + "typeName": "Invoice", + "permissions": [ + { + "role": "admin", + "output": { + "allowedFields": [ + "BillingAddress", + "BillingCity", + "BillingCountry", + "BillingPostalCode", + "BillingState", + "CustomerId", + "InvoiceDate", + "InvoiceId", + "Total" + ] + } + }, + { + "role": "user", + "output": { + "allowedFields": [ + "BillingPostalCode", + "BillingState", + "CustomerId", + "InvoiceDate", + "InvoiceId", + "Total" + ] + } + } + ] + } + }, + { + "kind": "ModelPermissions", + "version": "v1", + "definition": { + "modelName": "Invoice", + "permissions": [ + { + "role": "admin", + "select": { + "filter": null + } + }, + { + "role": "user", + "select": { + "filter": null + } + } + ] + } + } + ] + } + ] +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/filtering/request.gql b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/request.gql new file mode 100644 index 00000000000..2fa331b92dd --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/request.gql @@ -0,0 +1,28 @@ +query { + Invoice_aggregate( + filter_input: { + where: { BillingCountry: { _eq: "USA" } } + order_by: { InvoiceId: Asc } + offset: 5 + limit: 10 + } + ) { + BillingState { + _min + _max + _count_distinct + } + InvoiceId { + min: _min + max: _max + count: _count + } + Total { + _min + _max + _sum + _stddev + } + count_all: _count + } +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/filtering/session_variables.json b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/session_variables.json new file mode 100644 index 00000000000..62a7461f503 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/filtering/session_variables.json @@ -0,0 +1,8 @@ +[ + { + "x-hasura-role": "admin" + }, + { + "x-hasura-role": "user" + } +] diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/expected.json b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/expected.json new file mode 100644 index 00000000000..05e2882b6e5 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/expected.json @@ -0,0 +1,28 @@ +[ + { + "data": { + "Institution_aggregate": { + "Location": { + "_count": 2, + "City": { + "_min": "Gothenburg", + "_max": "London" + }, + "Country": { + "min_country": "Sweden", + "max_country": "UK" + } + }, + "count_all": 3 + } + } + }, + { + "data": null, + "errors": [ + { + "message": "validation failed: no such field on type Location_aggregate_exp: City" + } + ] + } +] diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/introspection_expected.json b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/introspection_expected.json new file mode 100644 index 00000000000..c49df199935 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/introspection_expected.json @@ -0,0 +1,559 @@ +[ + { + "data": { + "__schema": { + "queryType": { + "name": "Query", + "fields": [ + { + "name": "Institution", + "description": null, + "args": [ + { + "name": "limit", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "Institution_aggregate", + "description": null, + "args": [ + { + "name": "filter_input", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Institution_filter_input", + "ofType": null + } + } + ] + } + ] + } + }, + "Institution_filter_input_type": { + "name": "Institution_filter_input", + "description": null, + "kind": "INPUT_OBJECT", + "inputFields": [ + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + "Institution_aggregate_exp_type": { + "name": "Institution_aggregate_exp", + "description": "Aggregate expression for the Institution type", + "kind": "OBJECT", + "fields": [ + { + "name": "Id", + "description": "Aggregation over the institution id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Location", + "description": "Aggregation over the institution's location", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Location_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Name", + "description": "Aggregation over the institution name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of institutions", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "Location_aggregate_exp_type": { + "name": "Location_aggregate_exp", + "description": "Aggregate expression for the Location type", + "kind": "OBJECT", + "fields": [ + { + "name": "City", + "description": "Aggregation over the location city", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Country", + "description": "Aggregation over the location country", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of locations", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "String_aggregate_exp_type": { + "name": "String_aggregate_exp", + "description": "Aggregate expression for the String type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + ] + }, + "Int_aggregate_exp_type": { + "name": "Int_aggregate_exp", + "description": "Aggregate expression for the Int type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": "Count of all non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": "Count of all distinct non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": "Largest integer", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "_min", + "description": "Smallest integer", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + } + } + }, + { + "data": { + "__schema": { + "queryType": { + "name": "Query", + "fields": [ + { + "name": "Institution", + "description": null, + "args": [ + { + "name": "limit", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "Institution_aggregate", + "description": null, + "args": [ + { + "name": "filter_input", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Institution_filter_input", + "ofType": null + } + } + ] + } + ] + } + }, + "Institution_filter_input_type": { + "name": "Institution_filter_input", + "description": null, + "kind": "INPUT_OBJECT", + "inputFields": [ + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + "Institution_aggregate_exp_type": { + "name": "Institution_aggregate_exp", + "description": "Aggregate expression for the Institution type", + "kind": "OBJECT", + "fields": [ + { + "name": "Id", + "description": "Aggregation over the institution id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Location", + "description": "Aggregation over the institution's location", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Location_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Name", + "description": "Aggregation over the institution name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of institutions", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "Location_aggregate_exp_type": { + "name": "Location_aggregate_exp", + "description": "Aggregate expression for the Location type", + "kind": "OBJECT", + "fields": [ + { + "name": "Country", + "description": "Aggregation over the location country", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of locations", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "String_aggregate_exp_type": { + "name": "String_aggregate_exp", + "description": "Aggregate expression for the String type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + ] + }, + "Int_aggregate_exp_type": { + "name": "Int_aggregate_exp", + "description": "Aggregate expression for the Int type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": "Count of all non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": "Count of all distinct non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": "Largest integer", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "_min", + "description": "Smallest integer", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + } + } + } +] diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/introspection_request.gql b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/introspection_request.gql new file mode 100644 index 00000000000..582edeed56d --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/introspection_request.gql @@ -0,0 +1,133 @@ +query MyQuery { + __schema { + queryType { + name + fields { + name + description + args { + name + description + defaultValue + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + Institution_filter_input_type: __type(name: "Institution_filter_input") { + name + description + kind + inputFields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Institution_aggregate_exp_type: __type(name: "Institution_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Location_aggregate_exp_type: __type(name: "Location_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + String_aggregate_exp_type: __type(name: "String_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Int_aggregate_exp_type: __type(name: "Int_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/metadata.json b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/metadata.json new file mode 100644 index 00000000000..b2429de17ec --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/metadata.json @@ -0,0 +1,326 @@ +{ + "version": "v2", + "subgraphs": [ + { + "name": "default", + "objects": [ + { + "kind": "Model", + "version": "v1", + "definition": { + "name": "Institution", + "objectType": "Institution", + "source": { + "dataConnectorName": "custom_connector", + "collection": "institutions" + }, + "aggregateExpression": "Institution_aggregate_exp", + "orderableFields": [ + { + "fieldName": "Departments", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "Id", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "Location", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "Name", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "Staff", + "orderByDirections": { + "enableAll": true + } + } + ], + "graphql": { + "filterInputTypeName": "Institution_filter_input", + "aggregate": { + "queryRootField": "Institution_aggregate" + }, + "selectMany": { + "queryRootField": "Institution" + }, + "selectUniques": [] + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Institution_aggregate_exp", + "operand": { + "object": { + "aggregatedType": "Institution", + "aggregatableFields": [ + { + "fieldName": "Id", + "description": "Aggregation over the institution id", + "aggregateExpression": "Int_aggregate_exp" + }, + { + "fieldName": "Name", + "description": "Aggregation over the institution name", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "Location", + "description": "Aggregation over the institution's location", + "aggregateExpression": "Location_aggregate_exp" + } + ] + } + }, + "count": { + "enable": true, + "description": "Count of institutions" + }, + "description": "Aggregate expression for the Institution type", + "graphql": { + "selectTypeName": "Institution_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Location_aggregate_exp", + "operand": { + "object": { + "aggregatedType": "Location", + "aggregatableFields": [ + { + "fieldName": "City", + "description": "Aggregation over the location city", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "Country", + "description": "Aggregation over the location country", + "aggregateExpression": "String_aggregate_exp" + } + ] + } + }, + "count": { + "enable": true, + "description": "Count of locations" + }, + "description": "Aggregate expression for the Location type", + "graphql": { + "selectTypeName": "Location_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Int_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "Int", + "aggregationFunctions": [ + { + "name": "_min", + "description": "Smallest integer", + "returnType": "Int" + }, + { + "name": "_max", + "description": "Largest integer", + "returnType": "Int" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "custom_connector", + "dataConnectorScalarType": "Int", + "functionMapping": { + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + } + } + } + ] + } + }, + "count": { + "enable": true, + "description": "Count of all non-null integers" + }, + "countDistinct": { + "enable": true, + "description": "Count of all distinct non-null integers" + }, + "description": "Aggregate expression for the Int type", + "graphql": { + "selectTypeName": "Int_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "String_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "String", + "aggregationFunctions": [ + { + "name": "_min", + "returnType": "String" + }, + { + "name": "_max", + "returnType": "String" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "custom_connector", + "dataConnectorScalarType": "String", + "functionMapping": { + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + } + } + } + ] + } + }, + "count": { + "enable": true + }, + "countDistinct": { + "enable": true + }, + "description": "Aggregate expression for the String type", + "graphql": { + "selectTypeName": "String_aggregate_exp" + } + } + }, + { + "kind": "TypePermissions", + "version": "v1", + "definition": { + "typeName": "Institution", + "permissions": [ + { + "role": "admin", + "output": { + "allowedFields": [ + "Departments", + "Id", + "Location", + "Name", + "Staff" + ] + } + }, + { + "role": "user", + "output": { + "allowedFields": [ + "Departments", + "Id", + "Location", + "Name", + "Staff" + ] + } + } + ] + } + }, + { + "kind": "TypePermissions", + "version": "v1", + "definition": { + "typeName": "Location", + "permissions": [ + { + "role": "admin", + "output": { + "allowedFields": ["Campuses", "City", "Country"] + } + }, + { + "role": "user", + "output": { + "allowedFields": ["Country"] + } + } + ] + } + }, + { + "kind": "TypePermissions", + "version": "v1", + "definition": { + "typeName": "StaffMember", + "permissions": [ + { + "role": "admin", + "output": { + "allowedFields": ["FirstName", "LastName", "Specialities"] + } + }, + { + "role": "user", + "output": { + "allowedFields": ["FirstName", "LastName", "Specialities"] + } + } + ] + } + }, + { + "kind": "ModelPermissions", + "version": "v1", + "definition": { + "modelName": "Institution", + "permissions": [ + { + "role": "admin", + "select": { + "filter": null + } + }, + { + "role": "user", + "select": { + "filter": null + } + } + ] + } + } + ] + } + ] +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/request.gql b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/request.gql new file mode 100644 index 00000000000..d7a45fae7cc --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/request.gql @@ -0,0 +1,16 @@ +query { + Institution_aggregate { + Location { + _count + City { + _min + _max + } + Country { + min_country: _min + max_country: _max + } + } + count_all: _count + } +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/session_variables.json b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/session_variables.json new file mode 100644 index 00000000000..62a7461f503 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/nested_object/session_variables.json @@ -0,0 +1,8 @@ +[ + { + "x-hasura-role": "admin" + }, + { + "x-hasura-role": "user" + } +] diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/expected.json b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/expected.json new file mode 100644 index 00000000000..fc364b74667 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/expected.json @@ -0,0 +1,56 @@ +[ + { + "data": { + "Invoice_aggregate": { + "BillingCountry": { + "_min": "Argentina", + "_max": "USA", + "_count_distinct": 24 + }, + "InvoiceId": { + "min": 1, + "max": 412, + "count": 412 + }, + "Total": { + "_min": 0.99, + "_max": 25.86, + "_sum": 2328.6, + "_stddev": 4.745319693568106 + }, + "count_all": 412 + } + } + }, + { + "data": { + "Invoice_aggregate": { + "BillingCountry": { + "_min": "Australia", + "_max": "Australia", + "_count_distinct": 1 + }, + "InvoiceId": { + "min": 21, + "max": 305, + "count": 7 + }, + "Total": { + "_min": 0.99, + "_max": 13.86, + "_sum": 37.62, + "_stddev": 4.638483434424291 + }, + "count_all": 7 + } + } + }, + { + "data": null, + "errors": [ + { + "message": "validation failed: no such field on type Invoice_aggregate_exp: BillingCountry" + } + ] + } +] diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/introspection_expected.json b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/introspection_expected.json new file mode 100644 index 00000000000..959c7d66b49 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/introspection_expected.json @@ -0,0 +1,1520 @@ +[ + { + "data": { + "__schema": { + "queryType": { + "name": "Query", + "fields": [ + { + "name": "Invoice", + "description": null, + "args": [ + { + "name": "limit", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "InvoiceByInvoiceId", + "description": null, + "args": [ + { + "name": "InvoiceId", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "Invoice_aggregate", + "description": null, + "args": [ + { + "name": "filter_input", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_filter_input", + "ofType": null + } + } + ] + } + ] + } + }, + "Invoice_filter_input_type": { + "name": "Invoice_filter_input", + "description": null, + "kind": "INPUT_OBJECT", + "inputFields": [ + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + "Invoice_aggregate_exp_type": { + "name": "Invoice_aggregate_exp", + "description": "Aggregate expression for the Invoice type", + "kind": "OBJECT", + "fields": [ + { + "name": "BillingAddress", + "description": "Aggregation over the billing address", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingCity", + "description": "Aggregation over the billing city", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingCountry", + "description": "Aggregation over the billing country", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingPostalCode", + "description": "Aggregation over the billing postal code", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingState", + "description": "Aggregation over the billing state", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "CustomerId", + "description": "Aggregation over the customer ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceDate", + "description": "Aggregation over the invoice date", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Timestamp_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceId", + "description": "Aggregation over the invoice ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Total", + "description": "Aggregation over the invoice total", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Numeric_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of invoices", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "String_aggregate_exp_type": { + "name": "String_aggregate_exp", + "description": "Aggregate expression for the String type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ] + }, + "Int_aggregate_exp_type": { + "name": "Int_aggregate_exp", + "description": "Aggregate expression for the Int type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": "Count of all non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": "Count of all distinct non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": "Largest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_min", + "description": "Smallest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": "Standard deviation across integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": "Sum of all integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int64", + "ofType": null + } + } + } + ] + }, + "Numeric_aggregate_exp_type": { + "name": "Numeric_aggregate_exp", + "description": "Aggregate expression for the Numeric type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + } + ] + }, + "Timestamp_aggregate_exp_type": { + "name": "Timestamp_aggregate_exp", + "description": "Aggregate expression for the Timestamp type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + } + ] + } + } + }, + { + "data": { + "__schema": { + "queryType": { + "name": "Query", + "fields": [ + { + "name": "Invoice", + "description": null, + "args": [ + { + "name": "limit", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "InvoiceByInvoiceId", + "description": null, + "args": [ + { + "name": "InvoiceId", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "Invoice_aggregate", + "description": null, + "args": [ + { + "name": "filter_input", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_filter_input", + "ofType": null + } + } + ] + } + ] + } + }, + "Invoice_filter_input_type": { + "name": "Invoice_filter_input", + "description": null, + "kind": "INPUT_OBJECT", + "inputFields": [ + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + "Invoice_aggregate_exp_type": { + "name": "Invoice_aggregate_exp", + "description": "Aggregate expression for the Invoice type", + "kind": "OBJECT", + "fields": [ + { + "name": "BillingAddress", + "description": "Aggregation over the billing address", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingCity", + "description": "Aggregation over the billing city", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingCountry", + "description": "Aggregation over the billing country", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingPostalCode", + "description": "Aggregation over the billing postal code", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingState", + "description": "Aggregation over the billing state", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "CustomerId", + "description": "Aggregation over the customer ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceDate", + "description": "Aggregation over the invoice date", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Timestamp_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceId", + "description": "Aggregation over the invoice ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Total", + "description": "Aggregation over the invoice total", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Numeric_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of invoices", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "String_aggregate_exp_type": { + "name": "String_aggregate_exp", + "description": "Aggregate expression for the String type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ] + }, + "Int_aggregate_exp_type": { + "name": "Int_aggregate_exp", + "description": "Aggregate expression for the Int type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": "Count of all non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": "Count of all distinct non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": "Largest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_min", + "description": "Smallest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": "Standard deviation across integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": "Sum of all integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int64", + "ofType": null + } + } + } + ] + }, + "Numeric_aggregate_exp_type": { + "name": "Numeric_aggregate_exp", + "description": "Aggregate expression for the Numeric type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + } + ] + }, + "Timestamp_aggregate_exp_type": { + "name": "Timestamp_aggregate_exp", + "description": "Aggregate expression for the Timestamp type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + } + ] + } + } + }, + { + "data": { + "__schema": { + "queryType": { + "name": "Query", + "fields": [ + { + "name": "Invoice", + "description": null, + "args": [ + { + "name": "limit", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "InvoiceByInvoiceId", + "description": null, + "args": [ + { + "name": "InvoiceId", + "description": null, + "defaultValue": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + { + "name": "Invoice_aggregate", + "description": null, + "args": [ + { + "name": "filter_input", + "description": null, + "defaultValue": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Invoice_filter_input", + "ofType": null + } + } + ] + } + ] + } + }, + "Invoice_filter_input_type": { + "name": "Invoice_filter_input", + "description": null, + "kind": "INPUT_OBJECT", + "inputFields": [ + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + }, + "Invoice_aggregate_exp_type": { + "name": "Invoice_aggregate_exp", + "description": "Aggregate expression for the Invoice type", + "kind": "OBJECT", + "fields": [ + { + "name": "BillingPostalCode", + "description": "Aggregation over the billing postal code", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "BillingState", + "description": "Aggregation over the billing state", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "String_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "CustomerId", + "description": "Aggregation over the customer ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceDate", + "description": "Aggregation over the invoice date", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Timestamp_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "InvoiceId", + "description": "Aggregation over the invoice ID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Int_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "Total", + "description": "Aggregation over the invoice total", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Numeric_aggregate_exp", + "ofType": null + } + } + }, + { + "name": "_count", + "description": "Count of invoices", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + } + ] + }, + "String_aggregate_exp_type": { + "name": "String_aggregate_exp", + "description": "Aggregate expression for the String type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ] + }, + "Int_aggregate_exp_type": { + "name": "Int_aggregate_exp", + "description": "Aggregate expression for the Int type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": "Count of all non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": "Count of all distinct non-null integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": "Largest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_min", + "description": "Smallest integer", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": "Standard deviation across integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": "Sum of all integers", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int64", + "ofType": null + } + } + } + ] + }, + "Numeric_aggregate_exp_type": { + "name": "Numeric_aggregate_exp", + "description": "Aggregate expression for the Numeric type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_stddev", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + }, + { + "name": "_sum", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Numeric", + "ofType": null + } + } + } + ] + }, + "Timestamp_aggregate_exp_type": { + "name": "Timestamp_aggregate_exp", + "description": "Aggregate expression for the Timestamp type", + "kind": "OBJECT", + "fields": [ + { + "name": "_count", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_count_distinct", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + { + "name": "_max", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + }, + { + "name": "_min", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Timestamp", + "ofType": null + } + } + } + ] + } + } + } +] diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/introspection_request.gql b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/introspection_request.gql new file mode 100644 index 00000000000..74c36e7ca37 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/introspection_request.gql @@ -0,0 +1,154 @@ +query MyQuery { + __schema { + queryType { + name + fields { + name + description + args { + name + description + defaultValue + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + Invoice_filter_input_type: __type(name: "Invoice_filter_input") { + name + description + kind + inputFields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Invoice_aggregate_exp_type: __type(name: "Invoice_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + String_aggregate_exp_type: __type(name: "String_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Int_aggregate_exp_type: __type(name: "Int_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Numeric_aggregate_exp_type: __type(name: "Numeric_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + Timestamp_aggregate_exp_type: __type(name: "Timestamp_aggregate_exp") { + name + description + kind + fields { + name + description + type { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/metadata.json b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/metadata.json new file mode 100644 index 00000000000..e3b8456c437 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/metadata.json @@ -0,0 +1,480 @@ +{ + "version": "v2", + "subgraphs": [ + { + "name": "default", + "objects": [ + { + "kind": "Model", + "version": "v1", + "definition": { + "name": "Invoice", + "objectType": "Invoice", + "source": { + "dataConnectorName": "db", + "collection": "Invoice" + }, + "aggregateExpression": "Invoice_aggregate_exp", + "orderableFields": [ + { + "fieldName": "BillingAddress", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "BillingCity", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "BillingCountry", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "BillingPostalCode", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "BillingState", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "CustomerId", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "InvoiceDate", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "InvoiceId", + "orderByDirections": { + "enableAll": true + } + }, + { + "fieldName": "Total", + "orderByDirections": { + "enableAll": true + } + } + ], + "graphql": { + "filterInputTypeName": "Invoice_filter_input", + "aggregate": { + "queryRootField": "Invoice_aggregate" + }, + "selectMany": { + "queryRootField": "Invoice" + }, + "selectUniques": [ + { + "queryRootField": "InvoiceByInvoiceId", + "uniqueIdentifier": ["InvoiceId"] + } + ] + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Invoice_aggregate_exp", + "operand": { + "object": { + "aggregatedType": "Invoice", + "aggregatableFields": [ + { + "fieldName": "BillingAddress", + "description": "Aggregation over the billing address", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "BillingCity", + "description": "Aggregation over the billing city", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "BillingCountry", + "description": "Aggregation over the billing country", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "BillingPostalCode", + "description": "Aggregation over the billing postal code", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "BillingState", + "description": "Aggregation over the billing state", + "aggregateExpression": "String_aggregate_exp" + }, + { + "fieldName": "CustomerId", + "description": "Aggregation over the customer ID", + "aggregateExpression": "Int_aggregate_exp" + }, + { + "fieldName": "InvoiceDate", + "description": "Aggregation over the invoice date", + "aggregateExpression": "Timestamp_aggregate_exp" + }, + { + "fieldName": "InvoiceId", + "description": "Aggregation over the invoice ID", + "aggregateExpression": "Int_aggregate_exp" + }, + { + "fieldName": "Total", + "description": "Aggregation over the invoice total", + "aggregateExpression": "Numeric_aggregate_exp" + } + ] + } + }, + "count": { + "enable": true, + "description": "Count of invoices" + }, + "description": "Aggregate expression for the Invoice type", + "graphql": { + "selectTypeName": "Invoice_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Int_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "Int", + "aggregationFunctions": [ + { + "name": "_sum", + "description": "Sum of all integers", + "returnType": "Int64!" + }, + { + "name": "_min", + "description": "Smallest integer", + "returnType": "Int!" + }, + { + "name": "_max", + "description": "Largest integer", + "returnType": "Int!" + }, + { + "name": "_stddev", + "description": "Standard deviation across integers", + "returnType": "Numeric!" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "db", + "dataConnectorScalarType": "int4", + "functionMapping": { + "_sum": { + "name": "sum" + }, + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + }, + "_stddev": { + "name": "stddev" + } + } + } + ] + } + }, + "count": { + "enable": true, + "description": "Count of all non-null integers" + }, + "countDistinct": { + "enable": true, + "description": "Count of all distinct non-null integers" + }, + "description": "Aggregate expression for the Int type", + "graphql": { + "selectTypeName": "Int_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Numeric_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "Numeric", + "aggregationFunctions": [ + { + "name": "_sum", + "returnType": "Numeric!" + }, + { + "name": "_min", + "returnType": "Numeric!" + }, + { + "name": "_max", + "returnType": "Numeric!" + }, + { + "name": "_stddev", + "returnType": "Numeric!" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "db", + "dataConnectorScalarType": "numeric", + "functionMapping": { + "_sum": { + "name": "sum" + }, + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + }, + "_stddev": { + "name": "stddev" + } + } + } + ] + } + }, + "count": { + "enable": true + }, + "countDistinct": { + "enable": true + }, + "description": "Aggregate expression for the Numeric type", + "graphql": { + "selectTypeName": "Numeric_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "String_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "String", + "aggregationFunctions": [ + { + "name": "_min", + "returnType": "String!" + }, + { + "name": "_max", + "returnType": "String!" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "db", + "dataConnectorScalarType": "varchar", + "functionMapping": { + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + } + } + }, + { + "dataConnectorName": "db", + "dataConnectorScalarType": "text", + "functionMapping": { + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + } + } + } + ] + } + }, + "count": { + "enable": true + }, + "countDistinct": { + "enable": true + }, + "description": "Aggregate expression for the String type", + "graphql": { + "selectTypeName": "String_aggregate_exp" + } + } + }, + { + "kind": "AggregateExpression", + "version": "v1", + "definition": { + "name": "Timestamp_aggregate_exp", + "operand": { + "scalar": { + "aggregatedType": "Timestamp", + "aggregationFunctions": [ + { + "name": "_min", + "returnType": "Timestamp!" + }, + { + "name": "_max", + "returnType": "Timestamp!" + } + ], + "dataConnectorAggregationFunctionMapping": [ + { + "dataConnectorName": "db", + "dataConnectorScalarType": "timestamp", + "functionMapping": { + "_min": { + "name": "min" + }, + "_max": { + "name": "max" + } + } + } + ] + } + }, + "count": { + "enable": true + }, + "countDistinct": { + "enable": true + }, + "description": "Aggregate expression for the Timestamp type", + "graphql": { + "selectTypeName": "Timestamp_aggregate_exp" + } + } + }, + { + "kind": "TypePermissions", + "version": "v1", + "definition": { + "typeName": "Invoice", + "permissions": [ + { + "role": "admin", + "output": { + "allowedFields": [ + "BillingAddress", + "BillingCity", + "BillingCountry", + "BillingPostalCode", + "BillingState", + "CustomerId", + "InvoiceDate", + "InvoiceId", + "Total" + ] + } + }, + { + "role": "australianuser", + "output": { + "allowedFields": [ + "BillingAddress", + "BillingCity", + "BillingCountry", + "BillingPostalCode", + "BillingState", + "CustomerId", + "InvoiceDate", + "InvoiceId", + "Total" + ] + } + }, + { + "role": "user", + "output": { + "allowedFields": [ + "BillingPostalCode", + "BillingState", + "CustomerId", + "InvoiceDate", + "InvoiceId", + "Total" + ] + } + } + ] + } + }, + { + "kind": "ModelPermissions", + "version": "v1", + "definition": { + "modelName": "Invoice", + "permissions": [ + { + "role": "admin", + "select": { + "filter": null + } + }, + { + "role": "australianuser", + "select": { + "filter": { + "fieldComparison": { + "field": "BillingCountry", + "operator": "_eq", + "value": { + "literal": "Australia" + } + } + } + } + }, + { + "role": "user", + "select": { + "filter": null + } + } + ] + } + } + ] + } + ] +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/request.gql b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/request.gql new file mode 100644 index 00000000000..c6ae9bcecaf --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/request.gql @@ -0,0 +1,21 @@ +query { + Invoice_aggregate { + BillingCountry { + _min + _max + _count_distinct + } + InvoiceId { + min: _min + max: _max + count: _count + } + Total { + _min + _max + _sum + _stddev + } + count_all: _count + } +} diff --git a/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/session_variables.json b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/session_variables.json new file mode 100644 index 00000000000..cf9865f40f2 --- /dev/null +++ b/v3/crates/engine/tests/execute/aggregates/root_field/simple_select/session_variables.json @@ -0,0 +1,11 @@ +[ + { + "x-hasura-role": "admin" + }, + { + "x-hasura-role": "australianuser" + }, + { + "x-hasura-role": "user" + } +] diff --git a/v3/crates/engine/tests/execution.rs b/v3/crates/engine/tests/execution.rs index 16b4ba89206..302c6200033 100644 --- a/v3/crates/engine/tests/execution.rs +++ b/v3/crates/engine/tests/execution.rs @@ -1103,3 +1103,42 @@ fn test_apollo_federation_entities() -> anyhow::Result<()> { &[common_metadata_path_string, common_apollo_metadata], ) } + +#[test] +fn test_aggregates_root_field_simple_select() -> anyhow::Result<()> { + let test_path_string = "execute/aggregates/root_field/simple_select"; + common::test_execution_expectation( + test_path_string, + &[ + "execute/aggregates/common_metadata/postgres_connector_schema.json", + "execute/aggregates/common_metadata/pg_types.json", + "execute/aggregates/common_metadata/supergraph.json", + ], + ) +} + +#[test] +fn test_aggregates_root_field_filtering() -> anyhow::Result<()> { + let test_path_string = "execute/aggregates/root_field/filtering"; + common::test_execution_expectation( + test_path_string, + &[ + "execute/aggregates/common_metadata/postgres_connector_schema.json", + "execute/aggregates/common_metadata/pg_types.json", + "execute/aggregates/common_metadata/supergraph.json", + ], + ) +} + +#[test] +fn test_aggregates_root_field_nested_object() -> anyhow::Result<()> { + let test_path_string = "execute/aggregates/root_field/nested_object"; + common::test_execution_expectation( + test_path_string, + &[ + "execute/aggregates/common_metadata/custom_connector_schema.json", + "execute/aggregates/common_metadata/custom_connector_types.json", + "execute/aggregates/common_metadata/supergraph.json", + ], + ) +} diff --git a/v3/crates/engine/tests/introspection.rs b/v3/crates/engine/tests/introspection.rs index 148e1d5c2f1..64f4621f97a 100644 --- a/v3/crates/engine/tests/introspection.rs +++ b/v3/crates/engine/tests/introspection.rs @@ -97,3 +97,42 @@ fn test_introspect_boolean_expression_in_command() -> anyhow::Result<()> { ], ) } + +#[test] +fn test_introspect_aggregates_root_field_simple_select() -> anyhow::Result<()> { + let test_path_string = "execute/aggregates/root_field/simple_select"; + common::test_introspection_expectation( + test_path_string, + &[ + "execute/aggregates/common_metadata/postgres_connector_schema.json", + "execute/aggregates/common_metadata/pg_types.json", + "execute/aggregates/common_metadata/supergraph.json", + ], + ) +} + +#[test] +fn test_introspect_aggregates_root_field_filtering() -> anyhow::Result<()> { + let test_path_string = "execute/aggregates/root_field/filtering"; + common::test_introspection_expectation( + test_path_string, + &[ + "execute/aggregates/common_metadata/postgres_connector_schema.json", + "execute/aggregates/common_metadata/pg_types.json", + "execute/aggregates/common_metadata/supergraph.json", + ], + ) +} + +#[test] +fn test_introspect_aggregates_root_field_nested_object() -> anyhow::Result<()> { + let test_path_string = "execute/aggregates/root_field/nested_object"; + common::test_introspection_expectation( + test_path_string, + &[ + "execute/aggregates/common_metadata/custom_connector_schema.json", + "execute/aggregates/common_metadata/custom_connector_types.json", + "execute/aggregates/common_metadata/supergraph.json", + ], + ) +} diff --git a/v3/crates/execute/src/explain.rs b/v3/crates/execute/src/explain.rs index 1dc34630e7e..ae4341fbabd 100644 --- a/v3/crates/execute/src/explain.rs +++ b/v3/crates/execute/src/explain.rs @@ -149,7 +149,7 @@ pub(crate) async fn explain_mutation_plan( async fn get_execution_steps<'s>( http_context: &HttpContext, alias: gql::ast::common::Alias, - process_response_as: &ProcessResponseAs<'s>, + process_response_as: &ProcessResponseAs<'s, 's>, join_locations: JoinLocations<(RemoteJoin<'s, '_>, JoinId)>, ndc_request: types::NDCRequest, data_connector: &metadata_resolve::DataConnectorLink, @@ -167,7 +167,9 @@ async fn get_execution_steps<'s>( }, ))) } - ProcessResponseAs::Array { .. } | ProcessResponseAs::Object { .. } => { + ProcessResponseAs::Array { .. } + | ProcessResponseAs::Object { .. } + | ProcessResponseAs::Aggregates { .. } => { // A model execution node let data_connector_explain = fetch_explain_from_data_connector(http_context, &ndc_request, data_connector).await; diff --git a/v3/crates/execute/src/ir.rs b/v3/crates/execute/src/ir.rs index c412661c822..9232ce2c588 100644 --- a/v3/crates/execute/src/ir.rs +++ b/v3/crates/execute/src/ir.rs @@ -2,6 +2,7 @@ use indexmap::IndexMap; use lang_graphql::ast::common as ast; use serde::Serialize; +pub mod aggregates; pub mod arguments; pub mod commands; pub mod error; diff --git a/v3/crates/execute/src/ir/aggregates.rs b/v3/crates/execute/src/ir/aggregates.rs new file mode 100644 index 00000000000..3d2a6cefdb5 --- /dev/null +++ b/v3/crates/execute/src/ir/aggregates.rs @@ -0,0 +1,239 @@ +use std::collections::BTreeMap; + +use indexmap::IndexMap; +use lang_graphql::{ast::common::Alias, normalized_ast}; +use metadata_resolve::{Qualified, QualifiedTypeName, TypeMapping}; +use open_dds::{ + aggregates::DataConnectorAggregationFunctionName, + data_connector::DataConnectorName, + types::{CustomTypeName, FieldName}, +}; +use schema::{ + AggregateOutputAnnotation, AggregationFunctionAnnotation, Annotation, OutputAnnotation, GDS, +}; +use serde::Serialize; + +use crate::ir::error; + +/// IR that represents the selected fields of an output type. +#[derive(Debug, Serialize, Default, PartialEq)] +pub struct AggregateSelectionSet<'s> { + // The fields in the selection set. They are stored in the form that would + // be converted and sent over the wire. Serialized the map as ordered to + // produce deterministic golden files. + pub fields: IndexMap>, +} + +#[derive(Debug, Serialize, PartialEq)] +pub enum AggregateFieldSelection<'s> { + Count { + column_path: Vec<&'s str>, + graphql_field_path: Vec, + }, + CountDistinct { + column_path: Vec<&'s str>, + graphql_field_path: Vec, + }, + AggregationFunction { + function_name: &'s DataConnectorAggregationFunctionName, + column_path: nonempty::NonEmpty<&'s str>, + graphql_field_path: Vec, + }, +} + +impl<'s> AggregateFieldSelection<'s> { + pub fn get_graphql_field_path(&self) -> &Vec { + match self { + AggregateFieldSelection::Count { + graphql_field_path, .. + } + | AggregateFieldSelection::CountDistinct { + graphql_field_path, .. + } + | AggregateFieldSelection::AggregationFunction { + graphql_field_path, .. + } => graphql_field_path, + } + } +} + +pub fn generate_aggregate_selection_set_ir<'s>( + selection_set: &normalized_ast::SelectionSet<'s, GDS>, + data_connector: &'s metadata_resolve::DataConnectorLink, + type_mappings: &'s BTreeMap, TypeMapping>, + field_mappings: &'s BTreeMap, + aggregate_operand_type: &QualifiedTypeName, +) -> Result, error::Error> { + let mut aggregate_field_selections = IndexMap::new(); + + add_aggregate_selections( + &mut aggregate_field_selections, + selection_set, + aggregate_operand_type, + &data_connector.name, + &[], // column_path + &[], // graphql_field_path + type_mappings, + Some(field_mappings), + )?; + + Ok(AggregateSelectionSet { + fields: aggregate_field_selections, + }) +} + +fn add_aggregate_selections<'s>( + aggregate_field_selections: &mut IndexMap>, + selection_set: &normalized_ast::SelectionSet<'s, GDS>, + aggregate_operand_type: &QualifiedTypeName, + data_connector_name: &Qualified, + column_path: &[&'s metadata_resolve::FieldMapping], + graphql_field_path: &[Alias], + type_mappings: &'s BTreeMap, TypeMapping>, + field_mappings: Option<&'s BTreeMap>, +) -> Result<(), error::Error> { + for field in selection_set.fields.values() { + let graphql_field_path = graphql_field_path + .iter() + .chain(std::iter::once(&field.alias)) + .cloned() + .collect::>(); + + let field_call = field.field_call()?; + match field_call.info.generic { + Annotation::Output(OutputAnnotation::Aggregate( + AggregateOutputAnnotation::AggregationFunctionField(aggregate_function), + )) => match aggregate_function { + AggregationFunctionAnnotation::Count => { + let selection_field_name = + mk_alias_from_graphql_field_path(&graphql_field_path); + let selection = AggregateFieldSelection::Count { + column_path: column_path.iter().map(|m| m.column.0.as_str()).collect(), + graphql_field_path, + }; + aggregate_field_selections.insert(selection_field_name, selection); + } + + AggregationFunctionAnnotation::CountDistinct => { + let selection_field_name = + mk_alias_from_graphql_field_path(&graphql_field_path); + let selection = AggregateFieldSelection::CountDistinct { + column_path: column_path.iter().map(|m| m.column.0.as_str()).collect(), + graphql_field_path, + }; + aggregate_field_selections.insert(selection_field_name, selection); + } + + AggregationFunctionAnnotation::Function { + function_name, + data_connector_functions, + } => { + let selection_field_name = + mk_alias_from_graphql_field_path(&graphql_field_path); + + let column_path = nonempty::NonEmpty::from_slice(column_path) + .ok_or_else(|| error::InternalDeveloperError::ColumnAggregationFunctionUsedOnModelObjectType { + aggregate_operand_type: aggregate_operand_type.clone(), + aggregation_function: function_name.clone(), + })?; + + let column_scalar_type = + get_ndc_underlying_type_name(&column_path.last().column_type); + + let data_connector_function_info = data_connector_functions + .iter() + .find(|fn_info| { + fn_info.data_connector_name == *data_connector_name && + fn_info.operand_scalar_type.0 == *column_scalar_type + }) + .ok_or_else(|| { + error::InternalDeveloperError::DataConnectorAggregationFunctionNotFound { + aggregate_operand_type: aggregate_operand_type.clone(), + aggregation_function: function_name.clone(), + data_connector_name: data_connector_name.clone(), + } + })?; + + let selection = AggregateFieldSelection::AggregationFunction { + function_name: &data_connector_function_info.function_name, + column_path: column_path.map(|m| m.column.0.as_str()), + graphql_field_path, + }; + aggregate_field_selections.insert(selection_field_name, selection); + } + }, + + Annotation::Output(OutputAnnotation::Aggregate( + AggregateOutputAnnotation::AggregatableField { + field_name, + aggregate_operand_type: field_aggregate_operand_type, + }, + )) => { + let field_mapping = field_mappings + .ok_or_else(|| { + error::InternalDeveloperError::AggregatableFieldFoundOnScalarTypedOperand { + field_name: field_name.clone(), + aggregate_operand_type: aggregate_operand_type.clone(), + } + })? + .get(field_name) + .ok_or_else(|| error::InternalEngineError::InternalGeneric { + description: format!("invalid field in annotation: {field_name}"), + })?; + let column_path = column_path + .iter() + .copied() // This just dereferences the double reference: &&FieldMapping -> &FieldMapping + .chain(std::iter::once(field_mapping)) + .collect::>(); + + // If the type name is not in the object type mappings or is inbuilt, it is a scalar type + // and therefore does not have field mappings + let field_operand_field_mappings = match field_aggregate_operand_type { + QualifiedTypeName::Custom(custom_type_name) => { + type_mappings.get(custom_type_name).map(|type_mapping| { + let metadata_resolve::TypeMapping::Object { field_mappings, .. } = + type_mapping; + field_mappings + }) + } + QualifiedTypeName::Inbuilt(_) => None, + }; + + add_aggregate_selections( + aggregate_field_selections, + &field.selection_set, + aggregate_operand_type, + data_connector_name, + &column_path, + &graphql_field_path, + type_mappings, + field_operand_field_mappings, + )?; + } + annotation => Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: annotation.clone(), + })?, + } + } + + Ok(()) +} + +fn mk_alias_from_graphql_field_path(graphql_field_path: &[Alias]) -> String { + graphql_field_path + .iter() + .map(|alias| alias.0.as_str()) + .collect::>() + .join("_") +} + +fn get_ndc_underlying_type_name(result_type: &ndc_models::Type) -> &String { + match result_type { + ndc_models::Type::Named { name } => name, + ndc_models::Type::Array { element_type } => get_ndc_underlying_type_name(element_type), + ndc_models::Type::Nullable { underlying_type } => { + get_ndc_underlying_type_name(underlying_type) + } + ndc_models::Type::Predicate { object_type_name } => object_type_name, + } +} diff --git a/v3/crates/execute/src/ir/error.rs b/v3/crates/execute/src/ir/error.rs index 0858caf8493..60c67ef025d 100644 --- a/v3/crates/execute/src/ir/error.rs +++ b/v3/crates/execute/src/ir/error.rs @@ -1,6 +1,7 @@ use gql::ast::common as ast; use lang_graphql as gql; use open_dds::{ + aggregates::AggregationFunctionName, arguments::ArgumentName, data_connector::{DataConnectorColumnName, DataConnectorName}, relationships::RelationshipName, @@ -12,7 +13,7 @@ use thiserror::Error; use tracing_util::{ErrorVisibility, TraceableError}; use transitive::Transitive; -use metadata_resolve::Qualified; +use metadata_resolve::{Qualified, QualifiedTypeName}; use schema::{Annotation, NamespaceAnnotation}; #[derive(Error, Debug, Transitive)] @@ -153,6 +154,25 @@ pub enum InternalDeveloperError { #[error("The value expression could not be converted to header value. Error: ")] UnableToConvertValueExpressionToHeaderValue, + #[error("The aggregation function {aggregation_function} operating over the {aggregate_operand_type} type is missing a data connector mapping for {data_connector_name}")] + DataConnectorAggregationFunctionNotFound { + aggregate_operand_type: QualifiedTypeName, + aggregation_function: AggregationFunctionName, + data_connector_name: Qualified, + }, + + #[error("A field ({field_name}) with an AggregatableField annotation was found on a scalar-typed ({aggregate_operand_type}) operand's selection set")] + AggregatableFieldFoundOnScalarTypedOperand { + field_name: FieldName, + aggregate_operand_type: QualifiedTypeName, + }, + + #[error("The aggregation function {aggregation_function} was used on the model object type and not on a model field. Aggregation functions operate on columns, not rows")] + ColumnAggregationFunctionUsedOnModelObjectType { + aggregate_operand_type: QualifiedTypeName, + aggregation_function: AggregationFunctionName, + }, + // we'll be adding them shortly, and not advertising the feature until they are complete // however temporarily emitting the error allows merging the work in chunks #[error("boolean expressions not implemented")] diff --git a/v3/crates/execute/src/ir/model_selection.rs b/v3/crates/execute/src/ir/model_selection.rs index 4c6766b43d4..d83518588e9 100644 --- a/v3/crates/execute/src/ir/model_selection.rs +++ b/v3/crates/execute/src/ir/model_selection.rs @@ -2,11 +2,13 @@ use hasura_authn_core::SessionVariables; use lang_graphql::normalized_ast; +use metadata_resolve::QualifiedTypeName; use ndc_models; use open_dds::types::CustomTypeName; use serde::Serialize; use std::collections::BTreeMap; +use super::aggregates; use super::filter::ResolvedFilterExpression; use super::order_by::ResolvedOrderBy; use super::permissions; @@ -42,7 +44,10 @@ pub struct ModelSelection<'s> { pub(crate) order_by: Option>, // Fields requested from the model - pub(crate) selection: selection_set::ResultSelectionSet<'s>, + pub(crate) selection: Option>, + + // Aggregates requested of the model + pub(crate) aggregate_selection: Option>, } /// Generates the IR fragment for selecting from a model. @@ -52,7 +57,7 @@ pub(crate) fn model_selection_ir<'s>( data_type: &Qualified, model_source: &'s metadata_resolve::ModelSource, arguments: BTreeMap, - mut filter_clauses: ResolvedFilterExpression<'s>, + filter_clauses: ResolvedFilterExpression<'s>, permissions_predicate: &'s metadata_resolve::FilterPermission, limit: Option, offset: Option, @@ -61,38 +66,14 @@ pub(crate) fn model_selection_ir<'s>( request_headers: &reqwest::header::HeaderMap, usage_counts: &mut UsagesCounts, ) -> Result, error::Error> { - match permissions_predicate { - metadata_resolve::FilterPermission::AllowAll => {} - metadata_resolve::FilterPermission::Filter(predicate) => { - let mut permissions_predicate_relationships = BTreeMap::new(); - let processed_model_perdicate = permissions::process_model_predicate( - predicate, - session_variables, - &mut permissions_predicate_relationships, - usage_counts, - )?; - filter_clauses.expression = match filter_clauses.expression { - Some(existing) => Some(ndc_models::Expression::And { - expressions: vec![existing, processed_model_perdicate], - }), - None => Some(processed_model_perdicate), - }; - for (rel_name, rel_info) in permissions_predicate_relationships { - filter_clauses.relationships.insert(rel_name, rel_info); - } - } - }; - let field_mappings = model_source - .type_mappings - .get(data_type) - .map(|type_mapping| { - let metadata_resolve::TypeMapping::Object { field_mappings, .. } = type_mapping; - field_mappings - }) - .ok_or_else(|| error::InternalEngineError::InternalGeneric { - description: format!("type '{data_type}' not found in model source type_mappings"), - })?; + let filter_clauses = apply_permissions_predicate( + filter_clauses, + permissions_predicate, + session_variables, + usage_counts, + )?; + let field_mappings = get_field_mappings_for_object_type(model_source, data_type)?; let selection = selection_set::generate_selection_set_ir( selection_set, &model_source.data_connector, @@ -111,6 +92,102 @@ pub(crate) fn model_selection_ir<'s>( limit, offset, order_by, - selection, + selection: Some(selection), + aggregate_selection: None, }) } + +fn apply_permissions_predicate<'s>( + mut filter_clauses: ResolvedFilterExpression<'s>, + permissions_predicate: &'s metadata_resolve::FilterPermission, + session_variables: &SessionVariables, + usage_counts: &mut UsagesCounts, +) -> Result, error::Error> { + match permissions_predicate { + metadata_resolve::FilterPermission::AllowAll => {} + metadata_resolve::FilterPermission::Filter(predicate) => { + let mut permissions_predicate_relationships = BTreeMap::new(); + let processed_model_predicate = permissions::process_model_predicate( + predicate, + session_variables, + &mut permissions_predicate_relationships, + usage_counts, + )?; + filter_clauses.expression = match filter_clauses.expression { + Some(existing) => Some(ndc_models::Expression::And { + expressions: vec![existing, processed_model_predicate], + }), + None => Some(processed_model_predicate), + }; + for (rel_name, rel_info) in permissions_predicate_relationships { + filter_clauses.relationships.insert(rel_name, rel_info); + } + } + }; + + Ok(filter_clauses) +} + +/// Generates the IR fragment for selecting an aggregate of a model. +#[allow(clippy::too_many_arguments)] +pub(crate) fn model_aggregate_selection_ir<'s>( + aggregate_selection_set: &normalized_ast::SelectionSet<'s, GDS>, + data_type: &Qualified, + model_source: &'s metadata_resolve::ModelSource, + arguments: BTreeMap, + filter_clauses: ResolvedFilterExpression<'s>, + permissions_predicate: &'s metadata_resolve::FilterPermission, + limit: Option, + offset: Option, + order_by: Option>, + session_variables: &SessionVariables, + usage_counts: &mut UsagesCounts, +) -> Result, error::Error> { + let filter_clauses = apply_permissions_predicate( + filter_clauses, + permissions_predicate, + session_variables, + usage_counts, + )?; + + let field_mappings = get_field_mappings_for_object_type(model_source, data_type)?; + let aggregate_selection = aggregates::generate_aggregate_selection_set_ir( + aggregate_selection_set, + &model_source.data_connector, + &model_source.type_mappings, + field_mappings, + &QualifiedTypeName::Custom(data_type.clone()), + )?; + + Ok(ModelSelection { + data_connector: &model_source.data_connector, + collection: &model_source.collection, + arguments, + filter_clause: filter_clauses, + limit, + offset, + order_by, + selection: None, + aggregate_selection: Some(aggregate_selection), + }) +} + +fn get_field_mappings_for_object_type<'s>( + model_source: &'s metadata_resolve::ModelSource, + data_type: &Qualified, +) -> Result<&'s BTreeMap, error::Error> +{ + model_source + .type_mappings + .get(data_type) + .map(|type_mapping| { + let metadata_resolve::TypeMapping::Object { field_mappings, .. } = type_mapping; + field_mappings + }) + .ok_or_else(|| { + error::InternalEngineError::InternalGeneric { + description: format!("type '{data_type}' not found in model source type_mappings"), + } + .into() + }) +} diff --git a/v3/crates/execute/src/ir/query_root.rs b/v3/crates/execute/src/ir/query_root.rs index b5b0d9f52e9..f0e56c61eae 100644 --- a/v3/crates/execute/src/ir/query_root.rs +++ b/v3/crates/execute/src/ir/query_root.rs @@ -23,6 +23,7 @@ use schema::{Annotation, NodeFieldTypeNameMapping, OutputAnnotation, RootFieldAn pub mod apollo_federation; pub mod node_field; +pub mod select_aggregate; pub mod select_many; pub mod select_one; @@ -216,6 +217,17 @@ fn generate_model_rootfield_ir<'n, 's>( model_name, )?, }, + RootFieldKind::SelectAggregate => root_field::QueryRootField::ModelSelectAggregate { + selection_set: &field.selection_set, + ir: select_aggregate::select_aggregate_generate_ir( + field, + field_call, + data_type, + source, + &session.variables, + model_name, + )?, + }, }; Ok(ir) } diff --git a/v3/crates/execute/src/ir/query_root/select_aggregate.rs b/v3/crates/execute/src/ir/query_root/select_aggregate.rs new file mode 100644 index 00000000000..629849cc140 --- /dev/null +++ b/v3/crates/execute/src/ir/query_root/select_aggregate.rs @@ -0,0 +1,283 @@ +//! model_source IR for 'select_aggregate' operation +//! +//! A 'select_aggregate' operation fetches a set of aggregates over rows of a model + +/// Generates the IR for a 'select_aggregate' operation +use hasura_authn_core::SessionVariables; +use indexmap::IndexMap; +use lang_graphql::ast::common as ast; +use lang_graphql::normalized_ast; + +use open_dds; +use schema::InputAnnotation; +use serde::Serialize; +use std::collections::BTreeMap; + +use crate::ir::arguments; +use crate::ir::error; +use crate::ir::filter; +use crate::ir::filter::ResolvedFilterExpression; +use crate::ir::model_selection; +use crate::ir::order_by::build_ndc_order_by; +use crate::ir::order_by::ResolvedOrderBy; +use crate::ir::permissions; +use crate::model_tracking::{count_model, UsagesCounts}; +use metadata_resolve; +use metadata_resolve::Qualified; +use schema::GDS; +use schema::{self, Annotation, BooleanExpressionAnnotation, ModelInputAnnotation}; + +/// IR for the 'select_many' operation on a model +#[derive(Debug, Serialize)] +pub struct ModelSelectAggregate<'n, 's> { + // The name of the field as published in the schema + pub field_name: ast::Name, + + pub model_selection: model_selection::ModelSelection<'s>, + + // The Graphql output type of the operation + pub(crate) type_container: &'n ast::TypeContainer, + + // All the models/commands used in this operation. This includes the models/commands + // used via relationships. And in future, the models/commands used in the filter clause + pub(crate) usage_counts: UsagesCounts, +} + +struct ModelSelectAggregateArguments<'s> { + model_arguments: BTreeMap, + filter_input_arguments: FilterInputArguments<'s>, +} + +struct FilterInputArguments<'s> { + limit: Option, + offset: Option, + order_by: Option>, + filter_clause: ResolvedFilterExpression<'s>, +} + +/// Generates the IR for a 'select_aggregate' operation +#[allow(irrefutable_let_patterns)] +pub(crate) fn select_aggregate_generate_ir<'n, 's>( + field: &'n normalized_ast::Field<'s, GDS>, + field_call: &'n normalized_ast::FieldCall<'s, GDS>, + data_type: &Qualified, + model_source: &'s metadata_resolve::ModelSource, + session_variables: &SessionVariables, + model_name: &'s Qualified, +) -> Result, error::Error> { + let mut usage_counts = UsagesCounts::new(); + count_model(model_name, &mut usage_counts); + + let mut arguments = + read_model_select_aggregate_arguments(field_call, model_source, &mut usage_counts)?; + + // If there are model arguments presets from permissions, apply them + if let Some(model_argument_presets) = + permissions::get_argument_presets(field_call.info.namespaced)? + { + arguments::process_model_arguments_presets( + model_argument_presets, + session_variables, + &mut arguments.model_arguments, + &mut usage_counts, + )?; + } + + let model_selection = model_selection::model_aggregate_selection_ir( + &field.selection_set, + data_type, + model_source, + arguments.model_arguments, + arguments.filter_input_arguments.filter_clause, + permissions::get_select_filter_predicate(field_call)?, + arguments.filter_input_arguments.limit, + arguments.filter_input_arguments.offset, + arguments.filter_input_arguments.order_by, + session_variables, + // Get all the models/commands that were used as relationships + &mut usage_counts, + )?; + + Ok(ModelSelectAggregate { + field_name: field_call.name.clone(), + model_selection, + type_container: &field.type_container, + usage_counts, + }) +} + +fn read_model_select_aggregate_arguments<'s>( + field_call: &normalized_ast::FieldCall<'s, GDS>, + model_source: &'s metadata_resolve::ModelSource, + usage_counts: &mut UsagesCounts, +) -> Result, error::Error> { + let mut model_arguments = None; + let mut filter_input_props = None; + + for field_call_argument in field_call.arguments.values() { + match field_call_argument.info.generic { + // Model arguments + Annotation::Input(InputAnnotation::Model( + ModelInputAnnotation::ModelArgumentsExpression, + )) => { + if model_arguments.is_some() { + return Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: field_call_argument.info.generic.clone(), + } + .into()); + } + model_arguments = Some(match &field_call_argument.value { + normalized_ast::Value::Object(model_args_input_props) => { + arguments::build_ndc_model_arguments( + &field_call.name, + model_args_input_props.values(), + &model_source.type_mappings, + ) + } + _ => Err(error::InternalEngineError::InternalGeneric { + description: "Expected object value for model arguments".into(), + } + .into()), + }?); + } + + // Filter input arguments + Annotation::Input(InputAnnotation::Model( + ModelInputAnnotation::ModelFilterInputArgument, + )) => { + if filter_input_props.is_some() { + return Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: field_call_argument.info.generic.clone(), + } + .into()); + } + filter_input_props = Some(match &field_call_argument.value { + normalized_ast::Value::Object(model_args_input_props) => { + Ok(model_args_input_props) + } + _ => Err(error::Error::Internal(error::InternalError::Engine( + error::InternalEngineError::InternalGeneric { + description: "Expected object value for model arguments".into(), + }, + ))), + }?); + } + + _ => { + return Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: field_call_argument.info.generic.clone(), + } + .into()) + } + } + } + + let filter_input_arguments = + read_filter_input_arguments(filter_input_props, model_source, usage_counts)?; + + Ok(ModelSelectAggregateArguments { + model_arguments: model_arguments.unwrap_or_else(BTreeMap::new), + filter_input_arguments, + }) +} + +fn read_filter_input_arguments<'s>( + filter_input_field_props: Option<&IndexMap>>, + model_source: &'s metadata_resolve::ModelSource, + usage_counts: &mut UsagesCounts, +) -> Result, error::Error> { + let mut limit = None; + let mut offset = None; + let mut order_by = None; + let mut filter_clause = None; + + if let Some(filter_input_field_props) = filter_input_field_props { + for filter_input_field_arg in filter_input_field_props.values() { + match filter_input_field_arg.info.generic { + // Limit argument + Annotation::Input(InputAnnotation::Model( + ModelInputAnnotation::ModelLimitArgument, + )) => { + if limit.is_some() { + return Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: filter_input_field_arg.info.generic.clone(), + } + .into()); + } + limit = Some( + filter_input_field_arg + .value + .as_int_u32() + .map_err(error::Error::map_unexpected_value_to_external_error)?, + ); + } + + // Offset argument + Annotation::Input(InputAnnotation::Model( + ModelInputAnnotation::ModelOffsetArgument, + )) => { + if offset.is_some() { + return Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: filter_input_field_arg.info.generic.clone(), + } + .into()); + } + offset = Some( + filter_input_field_arg + .value + .as_int_u32() + .map_err(error::Error::map_unexpected_value_to_external_error)?, + ); + } + + // Order By argument + Annotation::Input(InputAnnotation::Model( + ModelInputAnnotation::ModelOrderByExpression, + )) => { + if order_by.is_some() { + return Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: filter_input_field_arg.info.generic.clone(), + } + .into()); + } + order_by = Some(build_ndc_order_by(filter_input_field_arg, usage_counts)?); + } + + // Where argument + Annotation::Input(InputAnnotation::BooleanExpression( + BooleanExpressionAnnotation::BooleanExpression, + )) => { + if filter_clause.is_some() { + return Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: filter_input_field_arg.info.generic.clone(), + } + .into()); + } + filter_clause = Some(filter::resolve_filter_expression( + filter_input_field_arg.value.as_object()?, + &model_source.data_connector, + &model_source.type_mappings, + usage_counts, + )?); + } + + _ => { + return Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: filter_input_field_arg.info.generic.clone(), + } + .into()) + } + } + } + } + + Ok(FilterInputArguments { + limit, + offset, + order_by, + filter_clause: filter_clause.unwrap_or_else(|| ResolvedFilterExpression { + expression: None, + relationships: BTreeMap::new(), + }), + }) +} diff --git a/v3/crates/execute/src/ir/root_field.rs b/v3/crates/execute/src/ir/root_field.rs index 7f6965225ec..7d1225eb0f6 100644 --- a/v3/crates/execute/src/ir/root_field.rs +++ b/v3/crates/execute/src/ir/root_field.rs @@ -6,7 +6,7 @@ use serde::Serialize; use super::{ commands, - query_root::{apollo_federation, node_field, select_many, select_one}, + query_root::{apollo_federation, node_field, select_aggregate, select_many, select_one}, }; use schema::GDS; @@ -40,6 +40,11 @@ pub enum QueryRootField<'n, 's> { selection_set: &'n gql::normalized_ast::SelectionSet<'s, GDS>, ir: select_many::ModelSelectMany<'n, 's>, }, + // Operation that selects an aggregate of rows from a model + ModelSelectAggregate { + selection_set: &'n gql::normalized_ast::SelectionSet<'s, GDS>, + ir: select_aggregate::ModelSelectAggregate<'n, 's>, + }, // Operation that selects a single row from the model corresponding // to the Global Id input. NodeSelect(Option>), diff --git a/v3/crates/execute/src/ir/selection_set.rs b/v3/crates/execute/src/ir/selection_set.rs index a46e9c6ae00..06391fb2d51 100644 --- a/v3/crates/execute/src/ir/selection_set.rs +++ b/v3/crates/execute/src/ir/selection_set.rs @@ -89,7 +89,7 @@ impl NDCRelationshipName { } /// IR that represents the selected fields of an output type. -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Default)] pub(crate) struct ResultSelectionSet<'s> { // The fields in the selection set. They are stored in the form that would // be converted and sent over the wire. Serialized the map as ordered to diff --git a/v3/crates/execute/src/model_tracking.rs b/v3/crates/execute/src/model_tracking.rs index 1c68cba5241..0584ff2009a 100644 --- a/v3/crates/execute/src/model_tracking.rs +++ b/v3/crates/execute/src/model_tracking.rs @@ -66,6 +66,10 @@ pub fn get_all_usage_counts_in_query(ir: &ir::IR<'_, '_>) -> UsagesCounts { let usage_counts = ir.usage_counts.clone(); extend_usage_count(usage_counts, &mut all_usage_counts); } + root_field::QueryRootField::ModelSelectAggregate { ir, .. } => { + let usage_counts = ir.usage_counts.clone(); + extend_usage_count(usage_counts, &mut all_usage_counts); + } root_field::QueryRootField::NodeSelect(ir1) => match ir1 { None => {} Some(ir2) => { diff --git a/v3/crates/execute/src/ndc.rs b/v3/crates/execute/src/ndc.rs index 1fd859b5c23..4cca4d62c23 100644 --- a/v3/crates/execute/src/ndc.rs +++ b/v3/crates/execute/src/ndc.rs @@ -119,7 +119,7 @@ pub(crate) async fn execute_ndc_mutation<'n, 's, 'ir>( selection_set: &'n normalized_ast::SelectionSet<'s, GDS>, execution_span_attribute: String, field_span_attribute: String, - process_response_as: ProcessResponseAs<'ir>, + process_response_as: ProcessResponseAs<'s, 'ir>, project_id: Option<&ProjectId>, ) -> Result { let tracer = tracing_util::global_tracer(); diff --git a/v3/crates/execute/src/plan.rs b/v3/crates/execute/src/plan.rs index d2bc515a908..3139d08e562 100644 --- a/v3/crates/execute/src/plan.rs +++ b/v3/crates/execute/src/plan.rs @@ -14,6 +14,7 @@ use serde_json as json; use tracing_util::{set_attribute_on_active_span, AttributeVisibility, Traceable}; use super::ir; +use super::ir::aggregates::AggregateFieldSelection; use super::ir::model_selection::ModelSelection; use super::ir::root_field; use super::ndc; @@ -81,7 +82,7 @@ pub struct NDCQueryExecution<'s, 'ir> { pub execution_tree: ExecutionTree<'s, 'ir>, pub execution_span_attribute: &'static str, pub field_span_attribute: String, - pub process_response_as: ProcessResponseAs<'ir>, + pub process_response_as: ProcessResponseAs<'s, 'ir>, // This selection set can either be owned by the IR structures or by the normalized query request itself. // We use the more restrictive lifetime `'ir` here which allows us to construct this struct using the selection // set either from the IR or from the normalized query request. @@ -105,7 +106,7 @@ pub struct NDCMutationExecution<'n, 's, 'ir> { pub data_connector: &'s metadata_resolve::DataConnectorLink, pub execution_span_attribute: String, pub field_span_attribute: String, - pub process_response_as: ProcessResponseAs<'ir>, + pub process_response_as: ProcessResponseAs<'s, 'ir>, pub selection_set: &'n normalized_ast::SelectionSet<'s, GDS>, } @@ -122,7 +123,7 @@ pub struct ExecutionNode<'s> { } #[derive(Clone, Copy, Debug, PartialEq)] -pub enum ProcessResponseAs<'ir> { +pub enum ProcessResponseAs<'s, 'ir> { Object { is_nullable: bool, }, @@ -133,14 +134,18 @@ pub enum ProcessResponseAs<'ir> { command_name: &'ir metadata_resolve::Qualified, type_container: &'ir ast::TypeContainer, }, + Aggregates { + requested_fields: &'ir IndexMap>, + }, } -impl<'ir> ProcessResponseAs<'ir> { +impl<'s, 'ir> ProcessResponseAs<'s, 'ir> { pub fn is_nullable(&self) -> bool { match self { ProcessResponseAs::Object { is_nullable } | ProcessResponseAs::Array { is_nullable } => *is_nullable, ProcessResponseAs::CommandResponse { type_container, .. } => type_container.nullable, + ProcessResponseAs::Aggregates { .. } => false, } } } @@ -263,6 +268,25 @@ fn plan_query<'n, 's, 'ir>( }, }) } + root_field::QueryRootField::ModelSelectAggregate { ir, selection_set } => { + let execution_tree = generate_execution_tree(&ir.model_selection)?; + let requested_fields = ir + .model_selection + .aggregate_selection + .as_ref() + .map(|selection| &selection.fields) + .ok_or_else(|| error::InternalError::InternalGeneric { + description: "Found a ModelSelectAggregate without an aggregate selection" + .to_owned(), + })?; + NodeQueryPlan::NDCQueryExecution(NDCQueryExecution { + execution_tree, + selection_set, + execution_span_attribute: "execute_model_select_aggregate", + field_span_attribute: ir.field_name.to_string(), + process_response_as: ProcessResponseAs::Aggregates { requested_fields }, + }) + } root_field::QueryRootField::NodeSelect(optional_ir) => match optional_ir { Some(ir) => { let execution_tree = generate_execution_tree(&ir.model_selection)?; diff --git a/v3/crates/execute/src/plan/model_selection.rs b/v3/crates/execute/src/plan/model_selection.rs index 212998dd8cb..02ad7afd18c 100644 --- a/v3/crates/execute/src/plan/model_selection.rs +++ b/v3/crates/execute/src/plan/model_selection.rs @@ -2,29 +2,112 @@ use std::collections::BTreeMap; +use indexmap::IndexMap; + use super::error; use super::relationships; use super::selection_set; +use crate::ir::aggregates::{AggregateFieldSelection, AggregateSelectionSet}; use crate::ir::model_selection::ModelSelection; use crate::remote_joins::types::{JoinLocations, MonotonicCounter, RemoteJoin}; +/// Create an NDC `Query` based on the internal IR `ModelSelection` settings pub(crate) fn ndc_query<'s, 'ir>( ir: &'ir ModelSelection<'s>, join_id_counter: &mut MonotonicCounter, ) -> Result<(ndc_models::Query, JoinLocations>), error::Error> { - let (ndc_fields, join_locations) = - selection_set::process_selection_set_ir(&ir.selection, join_id_counter)?; + let (ndc_fields, join_locations) = ir + .selection + .as_ref() + .map(|selection| -> Result<_, error::Error> { + let (ndc_fields, join_locations) = + selection_set::process_selection_set_ir(selection, join_id_counter)?; + Ok((Some(ndc_fields), join_locations)) + }) + .transpose()? + .unwrap_or_else(|| (None, JoinLocations::new())); + + let aggregates = ir.aggregate_selection.as_ref().map(ndc_aggregates); + let ndc_query = ndc_models::Query { - aggregates: None, - fields: Some(ndc_fields), + aggregates, + fields: ndc_fields, limit: ir.limit, offset: ir.offset, order_by: ir.order_by.as_ref().map(|x| x.order_by.clone()), predicate: ir.filter_clause.expression.clone(), }; + Ok((ndc_query, join_locations)) } +/// Translates the internal IR 'AggregateSelectionSet' into an NDC query aggregates selection +fn ndc_aggregates( + aggregate_selection_set: &AggregateSelectionSet, +) -> IndexMap { + aggregate_selection_set + .fields + .iter() + .map(|(field_name, aggregate_selection)| { + let aggregate = match aggregate_selection { + AggregateFieldSelection::Count { column_path, .. } => { + ndc_count_aggregate(column_path, false) + } + AggregateFieldSelection::CountDistinct { column_path, .. } => { + ndc_count_aggregate(column_path, true) + } + AggregateFieldSelection::AggregationFunction { + function_name, + column_path, + .. + } => { + let nonempty::NonEmpty { + head: column, + tail: field_path, + } = column_path; + let nested_field_path = field_path + .iter() + .map(std::string::ToString::to_string) + .collect::>(); + ndc_models::Aggregate::SingleColumn { + column: (*column).to_string(), + field_path: if nested_field_path.is_empty() { + None + } else { + Some(nested_field_path) + }, + function: function_name.0.clone(), + } + } + }; + (field_name.clone(), aggregate) + }) + .collect() +} + +/// Creates the appropriate NDC count aggregation based on whether we're selecting +/// a column (nested or otherwise) or not +fn ndc_count_aggregate(column_path: &[&str], distinct: bool) -> ndc_models::Aggregate { + let mut column_path_iter = column_path.iter(); + if let Some(first_path_element) = column_path_iter.next() { + let remaining_path = column_path_iter + .map(std::string::ToString::to_string) + .collect::>(); + let nested_field_path = if remaining_path.is_empty() { + None + } else { + Some(remaining_path) + }; + ndc_models::Aggregate::ColumnCount { + column: (*first_path_element).to_string(), + field_path: nested_field_path, + distinct, + } + } else { + ndc_models::Aggregate::StarCount {} + } +} + /// Convert the internal IR (`ModelSelection`) into NDC IR (`ndc::models::QueryRequest`) pub(crate) fn ndc_ir<'s, 'ir>( ir: &'ir ModelSelection<'s>, diff --git a/v3/crates/execute/src/plan/relationships.rs b/v3/crates/execute/src/plan/relationships.rs index c7156b53431..b21ae9b497f 100644 --- a/v3/crates/execute/src/plan/relationships.rs +++ b/v3/crates/execute/src/plan/relationships.rs @@ -17,41 +17,43 @@ pub(crate) fn collect_relationships( relationships: &mut BTreeMap, ) -> Result<(), error::Error> { // from selection fields - for field in ir.selection.fields.values() { - match field { - FieldSelection::ModelRelationshipLocal { - query, - name, - relationship_info, - } => { - relationships.insert( - name.to_string(), - process_model_relationship_definition(relationship_info)?, - ); - collect_relationships(query, relationships)?; - } - FieldSelection::CommandRelationshipLocal { - ir, - name, - relationship_info, - } => { - relationships.insert( - name.to_string(), - process_command_relationship_definition(relationship_info)?, - ); - if let Some(nested_selection) = &ir.command_info.selection { - selection_set::collect_relationships_from_nested_selection( - nested_selection, - relationships, - )?; + if let Some(selection) = &ir.selection { + for field in selection.fields.values() { + match field { + FieldSelection::ModelRelationshipLocal { + query, + name, + relationship_info, + } => { + relationships.insert( + name.to_string(), + process_model_relationship_definition(relationship_info)?, + ); + collect_relationships(query, relationships)?; } - } - FieldSelection::Column { .. } - // we ignore remote relationships as we are generating relationship - // definition for one data connector - | FieldSelection::ModelRelationshipRemote { .. } - | FieldSelection::CommandRelationshipRemote { .. } => (), - }; + FieldSelection::CommandRelationshipLocal { + ir, + name, + relationship_info, + } => { + relationships.insert( + name.to_string(), + process_command_relationship_definition(relationship_info)?, + ); + if let Some(nested_selection) = &ir.command_info.selection { + selection_set::collect_relationships_from_nested_selection( + nested_selection, + relationships, + )?; + } + } + FieldSelection::Column { .. } + // we ignore remote relationships as we are generating relationship + // definition for one data connector + | FieldSelection::ModelRelationshipRemote { .. } + | FieldSelection::CommandRelationshipRemote { .. } => (), + }; + } } // from filter clause diff --git a/v3/crates/execute/src/process_response.rs b/v3/crates/execute/src/process_response.rs index d241ec0776e..2671ac63c36 100644 --- a/v3/crates/execute/src/process_response.rs +++ b/v3/crates/execute/src/process_response.rs @@ -14,7 +14,7 @@ use open_dds::types::FieldName; use super::global_id::{global_id_col_format, GLOBAL_ID_VERSION}; use super::ndc::FUNCTION_IR_VALUE_COLUMN_NAME; use super::plan::ProcessResponseAs; -use crate::error; +use crate::error::{self, FieldInternalError}; use metadata_resolve::Qualified; use schema::{Annotation, GlobalID, OutputAnnotation, GDS}; @@ -378,6 +378,62 @@ fn process_command_field_value( } } +fn process_aggregate_requested_fields( + row_set: ndc_models::RowSet, + requested_fields: &IndexMap, +) -> Result, error::FieldError> { + let mut json_object = json::Map::::new(); + let mut aggregate_results = row_set.aggregates + .ok_or_else(|| error::NDCUnexpectedError::BadNDCResponse { + summary: + "Unable to parse response from NDC, RowSet aggregates property was null when it was expected to be an object" + .to_owned(), + })?; + + for (field_name, aggregate_field_selection) in requested_fields { + let aggregate_value = aggregate_results.swap_remove(field_name).ok_or_else(|| { + error::NDCUnexpectedError::BadNDCResponse { + summary: format!("missing aggregate field: {}", field_name), + } + })?; + + let graphql_field_path = aggregate_field_selection.get_graphql_field_path(); + set_value_at_json_path(&mut json_object, graphql_field_path, aggregate_value)?; + } + + Ok(json_object) +} + +fn set_value_at_json_path( + mut json_object: &mut json::Map, + path: &[Alias], + value: json::Value, +) -> Result<(), error::FieldError> { + let mut path_iterator = path.iter(); + let mut path_element = + path_iterator + .next() + .ok_or_else(|| FieldInternalError::InternalGeneric { + description: "Path to set value in JSON was empty".to_owned(), + })?; + + for next_path_element in path_iterator { + let field_value = json_object + .entry(path_element.0.as_str()) + .or_insert_with(|| json::Value::Object(json::Map::new())); + json_object = + field_value + .as_object_mut() + .ok_or_else(|| FieldInternalError::InternalGeneric { + description: "Encountered non-object type in JSON along path".to_owned(), + })?; + path_element = next_path_element; + } + + json_object.insert(path_element.to_string(), value); + Ok(()) +} + pub fn process_response( selection_set: &normalized_ast::SelectionSet<'_, GDS>, rows_sets: Vec, @@ -412,6 +468,10 @@ pub fn process_response( )?; json::to_value(result).map_err(error::FieldError::from) } + ProcessResponseAs::Aggregates { requested_fields } => { + let result = process_aggregate_requested_fields(row_set, requested_fields)?; + Ok(json::Value::Object(result)) + } } }, ) diff --git a/v3/crates/execute/src/remote_joins/collect.rs b/v3/crates/execute/src/remote_joins/collect.rs index 3a415008c9c..bdc61e9d44c 100644 --- a/v3/crates/execute/src/remote_joins/collect.rs +++ b/v3/crates/execute/src/remote_joins/collect.rs @@ -106,6 +106,14 @@ fn collect_argument_from_rows( ProcessResponseAs::Array { .. } | ProcessResponseAs::Object { .. } => { collect_argument_from_row(row, join_fields, path, &mut arguments)?; } + ProcessResponseAs::Aggregates { .. } => { + return Err(error::FieldInternalError::InternalGeneric { + description: + "Unexpected aggregate response on the LHS of a remote join" + .to_owned(), + } + .into()) + } ProcessResponseAs::CommandResponse { command_name: _, type_container, diff --git a/v3/crates/execute/src/remote_joins/types.rs b/v3/crates/execute/src/remote_joins/types.rs index a05d7a4caa6..9ae7e01920d 100644 --- a/v3/crates/execute/src/remote_joins/types.rs +++ b/v3/crates/execute/src/remote_joins/types.rs @@ -120,7 +120,7 @@ pub struct RemoteJoin<'s, 'ir> { /// field or an argument name. pub join_mapping: HashMap, /// Represents how to process the join response. - pub process_response_as: ProcessResponseAs<'ir>, + pub process_response_as: ProcessResponseAs<'s, 'ir>, /// Represents the type of the remote join pub remote_join_type: RemoteJoinType, } diff --git a/v3/crates/execute/tests/generate_ir/get_by_id/expected.json b/v3/crates/execute/tests/generate_ir/get_by_id/expected.json index 63cc9f17a29..54309be54df 100644 --- a/v3/crates/execute/tests/generate_ir/get_by_id/expected.json +++ b/v3/crates/execute/tests/generate_ir/get_by_id/expected.json @@ -152,7 +152,8 @@ } } } - } + }, + "aggregate_selection": null }, "type_container": { "base": { diff --git a/v3/crates/execute/tests/generate_ir/get_many/expected.json b/v3/crates/execute/tests/generate_ir/get_many/expected.json index ea9b7e9a799..a6c5e4e859f 100644 --- a/v3/crates/execute/tests/generate_ir/get_many/expected.json +++ b/v3/crates/execute/tests/generate_ir/get_many/expected.json @@ -126,7 +126,8 @@ } } } - } + }, + "aggregate_selection": null }, "type_container": { "base": { diff --git a/v3/crates/execute/tests/generate_ir/get_many_model_count/expected.json b/v3/crates/execute/tests/generate_ir/get_many_model_count/expected.json index 92a34824499..698f6b9b3df 100644 --- a/v3/crates/execute/tests/generate_ir/get_many_model_count/expected.json +++ b/v3/crates/execute/tests/generate_ir/get_many_model_count/expected.json @@ -611,7 +611,8 @@ } } } - } + }, + "aggregate_selection": null }, "name": "[{\"subgraph\":\"default\",\"name\":\"author\"},\"Articles\"]", "relationship_info": { @@ -755,7 +756,8 @@ } } } - } + }, + "aggregate_selection": null }, "name": "[{\"subgraph\":\"default\",\"name\":\"article\"},\"Author\"]", "relationship_info": { @@ -899,7 +901,8 @@ } } } - } + }, + "aggregate_selection": null }, "name": "[{\"subgraph\":\"default\",\"name\":\"commandArticle\"},\"article\"]", "relationship_info": { diff --git a/v3/crates/execute/tests/generate_ir/get_many_user_2/expected.json b/v3/crates/execute/tests/generate_ir/get_many_user_2/expected.json index d98cc0462e7..c77e0fae101 100644 --- a/v3/crates/execute/tests/generate_ir/get_many_user_2/expected.json +++ b/v3/crates/execute/tests/generate_ir/get_many_user_2/expected.json @@ -164,7 +164,8 @@ } } } - } + }, + "aggregate_selection": null }, "type_container": { "base": { diff --git a/v3/crates/execute/tests/generate_ir/get_many_where/expected.json b/v3/crates/execute/tests/generate_ir/get_many_where/expected.json index c64f7d09bbc..a2d01a4c06a 100644 --- a/v3/crates/execute/tests/generate_ir/get_many_where/expected.json +++ b/v3/crates/execute/tests/generate_ir/get_many_where/expected.json @@ -148,7 +148,8 @@ } } } - } + }, + "aggregate_selection": null }, "type_container": { "base": { @@ -335,7 +336,8 @@ } } } - } + }, + "aggregate_selection": null }, "type_container": { "base": { diff --git a/v3/crates/schema/src/aggregates.rs b/v3/crates/schema/src/aggregates.rs new file mode 100644 index 00000000000..dfdc887d3e2 --- /dev/null +++ b/v3/crates/schema/src/aggregates.rs @@ -0,0 +1,323 @@ +use hasura_authn_core::Role; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; +use strum_macros::Display; + +use lang_graphql::{ + ast::common::{self as ast, TypeContainer}, + schema as gql_schema, +}; +use metadata_resolve::{ + mk_name, AggregateExpression, DataConnectorAggregationFunctionInfo, + ObjectTypeWithRelationships, Qualified, QualifiedTypeName, +}; +use open_dds::{ + aggregates::{AggregateExpressionName, AggregationFunctionName}, + types::{CustomTypeName, FieldName}, +}; + +use crate::{ + mk_deprecation_status, + types::{output_type, TypeId}, + Annotation, Error, NamespaceAnnotation, GDS, +}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)] +pub enum AggregateOutputAnnotation { + AggregationFunctionField(AggregationFunctionAnnotation), + AggregatableField { + field_name: FieldName, + aggregate_operand_type: QualifiedTypeName, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)] +pub enum AggregationFunctionAnnotation { + Count, + CountDistinct, + Function { + function_name: AggregationFunctionName, + data_connector_functions: Vec, + }, +} + +pub fn get_aggregate_select_output_type( + builder: &mut gql_schema::Builder, + aggregate_expression: &metadata_resolve::AggregateExpression, +) -> Result { + Ok(builder.register_type(TypeId::AggregateSelectOutputType { + aggregate_expression_name: aggregate_expression.name.clone(), + graphql_type_name: aggregate_expression + .graphql + .as_ref() + .map(|graphql| &graphql.select_output_type_name) + .ok_or_else(|| Error::NoGraphQlSelectTypeNameForAggregateExpression { + aggregate_expression: aggregate_expression.name.clone(), + })? + .clone(), + })) +} + +pub fn build_aggregate_select_output_type( + gds: &GDS, + builder: &mut gql_schema::Builder, + aggregate_expression_name: &Qualified, + graphql_type_name: &ast::TypeName, +) -> Result, Error> { + let aggregate_expression = gds + .metadata + .aggregate_expressions + .get(aggregate_expression_name) + .ok_or_else(|| Error::InternalAggregateExpressionNotFound { + aggregate_expression: aggregate_expression_name.clone(), + })?; + + let mut aggregate_select_output_type_fields = BTreeMap::new(); + + if let Some((object_type_name, object_type)) = + get_object_type(gds, &aggregate_expression.operand.aggregated_type) + { + add_aggregatable_fields( + &mut aggregate_select_output_type_fields, + gds, + builder, + aggregate_expression, + object_type_name, + object_type, + )?; + } + + add_count_aggregation_fields( + &mut aggregate_select_output_type_fields, + builder, + aggregate_expression, + )?; + + add_aggregation_functions( + &mut aggregate_select_output_type_fields, + gds, + builder, + aggregate_expression, + )?; + + Ok(gql_schema::TypeInfo::Object(gql_schema::Object::new( + builder, + graphql_type_name.clone(), + aggregate_expression.description.clone(), + aggregate_select_output_type_fields, + BTreeMap::new(), // Interfaces + vec![], // Directives + ))) +} + +fn add_aggregatable_fields( + type_fields: &mut BTreeMap>>, + gds: &GDS, + builder: &mut gql_schema::Builder, + aggregate_expression: &AggregateExpression, + object_type_name: &Qualified, + object_type: &ObjectTypeWithRelationships, +) -> Result<(), Error> { + for aggregatable_field_info in &aggregate_expression.operand.aggregatable_fields { + let field_def = object_type + .object_type + .fields + .get(&aggregatable_field_info.field_name) + .ok_or_else(|| Error::InternalObjectTypeFieldNotFound { + type_name: object_type_name.clone(), + field_name: aggregatable_field_info.field_name.clone(), + })?; + + let field_aggregate_expression = gds + .metadata + .aggregate_expressions + .get(&aggregatable_field_info.aggregate_expression) + .ok_or_else(|| Error::InternalAggregateExpressionNotFound { + aggregate_expression: aggregatable_field_info.aggregate_expression.clone(), + })?; + + let field_graphql_name = mk_name(aggregatable_field_info.field_name.0.as_str())?; + let field = gql_schema::Field::::new( + field_graphql_name.clone(), + aggregatable_field_info.description.clone(), + Annotation::Output(super::OutputAnnotation::Aggregate( + AggregateOutputAnnotation::AggregatableField { + field_name: aggregatable_field_info.field_name.clone(), + aggregate_operand_type: field_aggregate_expression + .operand + .aggregated_type + .clone(), + }, + )), + TypeContainer::named_non_null(get_aggregate_select_output_type( + builder, + field_aggregate_expression, + )?), + BTreeMap::new(), // Arguments + mk_deprecation_status(&field_def.deprecated), // Use the field's deprecated status; if the field is deprecated the aggregation of it should be too + ); + + // Only allow access to aggregations of the field if the type permissions allow it + let allowed_roles = object_type + .type_output_permissions + .iter() + .filter(|(_role, perms)| { + perms + .allowed_fields + .contains(&aggregatable_field_info.field_name) + }) + .map(|(role, _perms)| (role.clone(), None)) + .collect::>>(); + let namespaced_field = builder.conditional_namespaced(field, allowed_roles); + + if type_fields + .insert(field_graphql_name.clone(), namespaced_field) + .is_some() + { + return Err(Error::InternalDuplicateAggregatableField { + aggregate_expression: aggregate_expression.name.clone(), + field_name: field_graphql_name.clone(), + }); + } + } + + Ok(()) +} + +fn add_count_aggregation_fields( + type_fields: &mut BTreeMap>>, + builder: &mut gql_schema::Builder, + aggregate_expression: &AggregateExpression, +) -> Result<(), Error> { + // Add the _count aggregation, if enabled and a graphql name has been specified + if aggregate_expression.count.enable { + if let Some(count_field_name) = aggregate_expression + .graphql + .as_ref() + .map(|graphql| &graphql.count_field_name) + { + let field = gql_schema::Field::::new( + count_field_name.clone(), + aggregate_expression.count.description.clone(), + Annotation::Output(super::OutputAnnotation::Aggregate( + AggregateOutputAnnotation::AggregationFunctionField( + AggregationFunctionAnnotation::Count, + ), + )), + TypeContainer::named_non_null(gql_schema::RegisteredTypeName::int()), + BTreeMap::new(), // Arguments + mk_deprecation_status(&None), + ); + + // All roles can use the count aggregation + let namespaced_field = builder.allow_all_namespaced(field); + + if type_fields + .insert(count_field_name.clone(), namespaced_field) + .is_some() + { + return Err(Error::AggregationFunctionFieldNameConflict { + aggregate_expression: aggregate_expression.name.clone(), + field_name: count_field_name.clone(), + }); + } + } + } + + // Add the _count_distinct aggregation, if enabled and a graphql name has been specified + if aggregate_expression.count_distinct.enable { + if let Some(count_distinct_field_name) = aggregate_expression + .graphql + .as_ref() + .map(|graphql| &graphql.count_distinct_field_name) + { + let field = gql_schema::Field::::new( + count_distinct_field_name.clone(), + aggregate_expression.count_distinct.description.clone(), + Annotation::Output(super::OutputAnnotation::Aggregate( + AggregateOutputAnnotation::AggregationFunctionField( + AggregationFunctionAnnotation::CountDistinct, + ), + )), + TypeContainer::named_non_null(gql_schema::RegisteredTypeName::int()), + BTreeMap::new(), // Arguments + mk_deprecation_status(&None), + ); + + // All roles can use the count distinct aggregation + let namespaced_field = builder.allow_all_namespaced(field); + + if type_fields + .insert(count_distinct_field_name.clone(), namespaced_field) + .is_some() + { + return Err(Error::AggregationFunctionFieldNameConflict { + aggregate_expression: aggregate_expression.name.clone(), + field_name: count_distinct_field_name.clone(), + }); + } + } + } + + Ok(()) +} + +fn add_aggregation_functions( + type_fields: &mut BTreeMap>>, + gds: &GDS, + builder: &mut gql_schema::Builder, + aggregate_expression: &AggregateExpression, +) -> Result<(), Error> { + for aggregatable_function_info in &aggregate_expression.operand.aggregation_functions { + let field_graphql_name = mk_name(aggregatable_function_info.name.0.as_str())?; + let field = gql_schema::Field::::new( + field_graphql_name.clone(), + aggregatable_function_info.description.clone(), + Annotation::Output(super::OutputAnnotation::Aggregate( + AggregateOutputAnnotation::AggregationFunctionField( + AggregationFunctionAnnotation::Function { + function_name: aggregatable_function_info.name.clone(), + data_connector_functions: aggregatable_function_info + .data_connector_functions + .clone(), + }, + ), + )), + output_type::get_output_type(gds, builder, &aggregatable_function_info.return_type)?, + BTreeMap::new(), // Arguments + mk_deprecation_status(&None), + ); + + // All roles can access all functions + let namespaced_field = builder.allow_all_namespaced(field); + + if type_fields + .insert(field_graphql_name.clone(), namespaced_field) + .is_some() + { + return Err(Error::AggregationFunctionFieldNameConflict { + aggregate_expression: aggregate_expression.name.clone(), + field_name: field_graphql_name.clone(), + }); + } + } + + Ok(()) +} + +fn get_object_type<'a>( + gds: &'a GDS, + type_name: &'a QualifiedTypeName, +) -> Option<( + &'a Qualified, + &'a ObjectTypeWithRelationships, +)> { + match type_name { + QualifiedTypeName::Inbuilt(_) => None, + QualifiedTypeName::Custom(custom_type_name) => gds + .metadata + .object_types + .get(custom_type_name) + .map(|obj_type| (custom_type_name, obj_type)), + } +} diff --git a/v3/crates/schema/src/lib.rs b/v3/crates/schema/src/lib.rs index 7f46a4bb34b..c9ad261f37e 100644 --- a/v3/crates/schema/src/lib.rs +++ b/v3/crates/schema/src/lib.rs @@ -1,5 +1,6 @@ use lang_graphql::schema::{self as gql_schema, SchemaContext}; use lang_graphql::{ast::common as ast, mk_name}; +use open_dds::aggregates::AggregateExpressionName; use open_dds::types::FieldName; use open_dds::{ commands::CommandName, @@ -19,11 +20,13 @@ use metadata_resolve::{ use self::types::PossibleApolloFederationTypes; // we deliberately do not export these entire modules and instead explicitly export types below +mod aggregates; mod apollo_federation; mod boolean_expression; mod commands; mod model_arguments; mod model_filter; +mod model_filter_input; mod model_order_by; mod mutation_root; mod permissions; @@ -31,6 +34,7 @@ mod query_root; mod relay; mod types; +pub use aggregates::{AggregateOutputAnnotation, AggregationFunctionAnnotation}; pub use types::output_type::relationship::{ CommandRelationshipAnnotation, CommandTargetSource, FilterRelationshipAnnotation, ModelRelationshipAnnotation, OrderByRelationshipAnnotation, @@ -221,6 +225,24 @@ impl gql_schema::SchemaContext for GDS { apollo_federation::apollo_federation_service_schema(builder)?, )) } + types::TypeId::AggregateSelectOutputType { + aggregate_expression_name, + graphql_type_name, + } => aggregates::build_aggregate_select_output_type( + self, + builder, + aggregate_expression_name, + graphql_type_name, + ), + types::TypeId::ModelFilterInputType { + model_name, + graphql_type_name, + } => model_filter_input::build_model_filter_input_type( + self, + builder, + model_name, + graphql_type_name, + ), } } @@ -255,6 +277,13 @@ pub enum Error { InternalTypeNotFound { type_name: Qualified, }, + #[error( + "internal error while building schema, field {field_name} not found in type {type_name}" + )] + InternalObjectTypeFieldNotFound { + type_name: Qualified, + field_name: FieldName, + }, #[error("duplicate field name {field_name} generated while building object type {type_name}")] DuplicateFieldNameGeneratedInObjectType { field_name: ast::Name, @@ -266,6 +295,16 @@ pub enum Error { field_name: ast::Name, type_name: Qualified, }, + #[error("the aggregation function {field_name} conflicts with the aggregatable field {field_name} in the aggregate expression {aggregate_expression} is named {field_name}. Either rename the aggregation function or the field")] + AggregationFunctionFieldNameConflict { + aggregate_expression: Qualified, + field_name: ast::Name, + }, + #[error("internal error: duplicate aggregatable field {field_name} in the aggregate expression {aggregate_expression} is named {field_name}")] + InternalDuplicateAggregatableField { + aggregate_expression: Qualified, + field_name: ast::Name, + }, #[error( "internal error: duplicate models with global id implementing the same type {type_name} are found" )] @@ -296,6 +335,10 @@ pub enum Error { InternalCommandNotFound { command_name: Qualified, }, + #[error("internal error while building schema, aggregate expression not found: {aggregate_expression}")] + InternalAggregateExpressionNotFound { + aggregate_expression: Qualified, + }, #[error("Cannot generate select_many API for model {model_name} since order_by_expression isn't defined")] NoOrderByExpression { model_name: Qualified }, #[error("No graphql type name has been defined for scalar type: {type_name}")] @@ -306,6 +349,10 @@ pub enum Error { NoGraphQlOutputTypeNameForObject { type_name: Qualified, }, + #[error("No graphql select type name has been defined for aggregate expression: {aggregate_expression}")] + NoGraphQlSelectTypeNameForAggregateExpression { + aggregate_expression: Qualified, + }, #[error("No graphql input type name has been defined for object type: {type_name}")] NoGraphQlInputTypeNameForObject { type_name: Qualified, @@ -316,6 +363,10 @@ pub enum Error { "Cannot generate arguments for model {model_name} since argumentsInputType and it's corresponding graphql config argumentsInput isn't defined" )] NoArgumentsInputConfigForSelectMany { model_name: Qualified }, + #[error( + "Cannot generate the filter input type for model {model_name} since filterInputTypeName isn't defined in the graphql config" + )] + NoFilterInputTypeNameConfigNameForModel { model_name: Qualified }, #[error("Internal error: Relationship capabilities are missing for {relationship} on type {type_name}")] InternalMissingRelationshipCapabilities { type_name: Qualified, diff --git a/v3/crates/schema/src/model_filter_input.rs b/v3/crates/schema/src/model_filter_input.rs new file mode 100644 index 00000000000..dfd5568ac43 --- /dev/null +++ b/v3/crates/schema/src/model_filter_input.rs @@ -0,0 +1,240 @@ +use std::collections::BTreeMap; + +use lang_graphql::{ast::common as ast, schema as gql_schema}; +use metadata_resolve::Qualified; + +use crate::{ + model_filter::get_where_expression_input_field, + model_order_by::get_order_by_expression_input_field, + types::{self, TypeId}, + Annotation, Error, ModelInputAnnotation, GDS, +}; + +pub fn add_filter_input_argument_field( + fields: &mut BTreeMap>>, + field_name: &ast::Name, + builder: &mut gql_schema::Builder, + model: &metadata_resolve::ModelWithPermissions, +) -> Result<(), Error> { + let filter_input_type_name = get_model_filter_input_type(builder, model)?; + + let filter_input_argument = gql_schema::InputField::new( + field_name.clone(), + None, + Annotation::Input(types::InputAnnotation::Model( + ModelInputAnnotation::ModelFilterInputArgument, + )), + ast::TypeContainer::named_null(filter_input_type_name), + None, + gql_schema::DeprecationStatus::NotDeprecated, + ); + + fields.insert( + field_name.clone(), + builder.allow_all_namespaced(filter_input_argument), + ); + + Ok(()) +} + +pub fn get_model_filter_input_type( + builder: &mut gql_schema::Builder, + model: &metadata_resolve::ModelWithPermissions, +) -> Result { + model + .model + .graphql_api + .filter_input_type_name + .as_ref() + .ok_or(crate::Error::NoFilterInputTypeNameConfigNameForModel { + model_name: model.model.name.clone(), + }) + .map(|filter_input_type_name| { + builder.register_type(TypeId::ModelFilterInputType { + model_name: model.model.name.clone(), + graphql_type_name: filter_input_type_name.clone(), + }) + }) +} + +pub fn build_model_filter_input_type( + gds: &GDS, + builder: &mut gql_schema::Builder, + model_name: &Qualified, + graphql_type_name: &ast::TypeName, +) -> Result, Error> { + let model_with_permissions = + gds.metadata + .models + .get(model_name) + .ok_or_else(|| Error::InternalModelNotFound { + model_name: model_name.clone(), + })?; + + let mut filter_input_type_fields = BTreeMap::new(); + + add_limit_input_field( + &mut filter_input_type_fields, + builder, + model_with_permissions, + )?; + add_offset_input_field( + &mut filter_input_type_fields, + builder, + model_with_permissions, + )?; + add_order_by_input_field( + &mut filter_input_type_fields, + builder, + model_with_permissions, + )?; + add_where_input_field( + &mut filter_input_type_fields, + builder, + model_with_permissions, + )?; + + Ok(gql_schema::TypeInfo::InputObject( + gql_schema::InputObject::new( + graphql_type_name.clone(), + None, + filter_input_type_fields, + vec![], // Directives + ), + )) +} + +pub fn add_limit_input_field( + fields: &mut BTreeMap>>, + builder: &mut gql_schema::Builder, + model: &metadata_resolve::ModelWithPermissions, +) -> Result<(), Error> { + if let Some(limit_field) = &model.model.graphql_api.limit_field { + let limit_argument = generate_int_input_argument( + limit_field.field_name.as_str(), + Annotation::Input(types::InputAnnotation::Model( + ModelInputAnnotation::ModelLimitArgument, + )), + )?; + fields.insert( + limit_argument.name.clone(), + builder.allow_all_namespaced(limit_argument), + ); + } + + Ok(()) +} + +pub fn add_offset_input_field( + fields: &mut BTreeMap>>, + builder: &mut gql_schema::Builder, + model: &metadata_resolve::ModelWithPermissions, +) -> Result<(), Error> { + if let Some(offset_field) = &model.model.graphql_api.offset_field { + let offset_argument = generate_int_input_argument( + offset_field.field_name.as_str(), + Annotation::Input(types::InputAnnotation::Model( + ModelInputAnnotation::ModelOffsetArgument, + )), + )?; + + fields.insert( + offset_argument.name.clone(), + builder.allow_all_namespaced(offset_argument), + ); + } + + Ok(()) +} + +pub fn add_order_by_input_field( + fields: &mut BTreeMap>>, + builder: &mut gql_schema::Builder, + model: &metadata_resolve::ModelWithPermissions, +) -> Result<(), Error> { + if let Some(order_by_expression_info) = &model.model.graphql_api.order_by_expression { + let order_by_argument = { + get_order_by_expression_input_field( + builder, + model.model.name.clone(), + order_by_expression_info, + ) + }; + + fields.insert( + order_by_argument.name.clone(), + builder.allow_all_namespaced(order_by_argument), + ); + } + + Ok(()) +} + +pub fn add_where_input_field( + fields: &mut BTreeMap>>, + builder: &mut gql_schema::Builder, + model: &metadata_resolve::ModelWithPermissions, +) -> Result<(), Error> { + let boolean_expression_filter_type = + &model + .model + .filter_expression_type + .as_ref() + .and_then(|bool_exp| match bool_exp { + metadata_resolve::ModelExpressionType::ObjectBooleanExpressionType( + object_boolean_expression_type, + ) => object_boolean_expression_type + .graphql + .as_ref() + .map(|graphql_config| { + ( + object_boolean_expression_type.name.clone(), + graphql_config.clone(), + ) + }), + metadata_resolve::ModelExpressionType::BooleanExpressionType( + boolean_expression_object_type, + ) => boolean_expression_object_type + .graphql + .as_ref() + .map(|graphql_config| { + ( + boolean_expression_object_type.name.clone(), + graphql_config.clone(), + ) + }), + }); + + if let Some((boolean_expression_type_name, boolean_expression_graphql_config)) = + boolean_expression_filter_type + { + let where_argument = get_where_expression_input_field( + builder, + boolean_expression_type_name.clone(), + boolean_expression_graphql_config, + ); + + fields.insert( + where_argument.name.clone(), + builder.allow_all_namespaced(where_argument), + ); + } + + Ok(()) +} + +/// Generates the input field for the arguments which are of type int. +fn generate_int_input_argument( + name: &str, + annotation: Annotation, +) -> Result, crate::Error> { + let input_field_name = metadata_resolve::mk_name(name)?; + Ok(gql_schema::InputField::new( + input_field_name, + None, + annotation, + ast::TypeContainer::named_null(gql_schema::RegisteredTypeName::int()), + None, + gql_schema::DeprecationStatus::NotDeprecated, + )) +} diff --git a/v3/crates/schema/src/query_root.rs b/v3/crates/schema/src/query_root.rs index 910ed5751c8..5fed4110dfc 100644 --- a/v3/crates/schema/src/query_root.rs +++ b/v3/crates/schema/src/query_root.rs @@ -13,6 +13,7 @@ use self::node_field::RelayNodeFieldOutput; pub mod apollo_federation; pub mod node_field; +pub mod select_aggregate; pub mod select_many; pub mod select_one; @@ -44,6 +45,16 @@ pub fn query_root_schema( )?; fields.insert(field_name, field); } + if let Some(select_aggregate) = &model.model.graphql_api.select_aggregate { + let (field_name, field) = select_aggregate::select_aggregate_field( + gds, + builder, + model, + select_aggregate, + query_root_type_name, + )?; + fields.insert(field_name, field); + } } // Add node field for only the commands which have a query root field diff --git a/v3/crates/schema/src/query_root/select_aggregate.rs b/v3/crates/schema/src/query_root/select_aggregate.rs new file mode 100644 index 00000000000..34150a7cd8e --- /dev/null +++ b/v3/crates/schema/src/query_root/select_aggregate.rs @@ -0,0 +1,118 @@ +//! model_source.Schema for 'select_aggregate' operation +//! +//! A 'select_aggregate' operation fetches aggregations over the model's data +//! +use std::collections::BTreeMap; + +use lang_graphql::{ast::common as ast, schema as gql_schema}; + +use crate::aggregates::get_aggregate_select_output_type; +use crate::types::{self, Annotation}; +use crate::{ + mk_deprecation_status, model_arguments, model_filter_input, permissions, Error, + RootFieldAnnotation, RootFieldKind, GDS, +}; +use metadata_resolve; + +pub(crate) fn select_aggregate_field( + gds: &GDS, + builder: &mut gql_schema::Builder, + model: &metadata_resolve::ModelWithPermissions, + select_aggregate: &metadata_resolve::SelectAggregateGraphQlDefinition, + parent_type: &ast::TypeName, +) -> Result< + ( + ast::Name, + gql_schema::Namespaced>, + ), + Error, +> { + let aggregate_expression = gds + .metadata + .aggregate_expressions + .get(&select_aggregate.aggregate_expression_name) + .ok_or_else(|| Error::InternalAggregateExpressionNotFound { + aggregate_expression: select_aggregate.aggregate_expression_name.clone(), + })?; + + let root_field_name = select_aggregate.query_root_field.clone(); + + let mut arguments = + BTreeMap::>>::new(); + + add_model_arguments_field( + &mut arguments, + gds, + builder, + model, + &root_field_name, + parent_type, + )?; + model_filter_input::add_filter_input_argument_field( + &mut arguments, + &select_aggregate.filter_input_field_name, + builder, + model, + )?; + + let field_permissions = permissions::get_select_permissions_namespace_annotations( + model, + &gds.metadata.object_types, + )?; + + let output_typename = get_aggregate_select_output_type(builder, aggregate_expression)?; + + let field = builder.conditional_namespaced( + gql_schema::Field::new( + root_field_name.clone(), + select_aggregate.description.clone(), + Annotation::Output(types::OutputAnnotation::RootField( + RootFieldAnnotation::Model { + data_type: model.model.data_type.clone(), + source: model.model.source.clone(), + kind: RootFieldKind::SelectAggregate, + name: model.model.name.clone(), + }, + )), + ast::TypeContainer::named_null(output_typename), + arguments, + mk_deprecation_status(&select_aggregate.deprecated), + ), + field_permissions, + ); + Ok((root_field_name, field)) +} + +fn add_model_arguments_field( + arguments: &mut BTreeMap>>, + gds: &GDS, + builder: &mut gql_schema::Builder, + model: &metadata_resolve::ModelWithPermissions, + root_field_name: &ast::Name, + parent_type: &ast::TypeName, +) -> Result<(), Error> { + if !model.model.arguments.is_empty() { + let model_arguments_input = + model_arguments::get_model_arguments_input_field(builder, model)?; + + let name = model_arguments_input.name.clone(); + + let model_arguments = builder.conditional_namespaced( + model_arguments_input, + permissions::get_select_permissions_namespace_annotations( + model, + &gds.metadata.object_types, + )?, + ); + + if arguments.insert(name.clone(), model_arguments).is_some() { + return Err(crate::Error::GraphQlArgumentConflict { + argument_name: name, + field_name: root_field_name.clone(), + type_name: parent_type.clone(), + }); + } + } + + Ok(()) +} diff --git a/v3/crates/schema/src/query_root/select_many.rs b/v3/crates/schema/src/query_root/select_many.rs index f3491e91051..95c116e89d0 100644 --- a/v3/crates/schema/src/query_root/select_many.rs +++ b/v3/crates/schema/src/query_root/select_many.rs @@ -8,12 +8,12 @@ use lang_graphql::schema as gql_schema; use std::collections::BTreeMap; use crate::mk_deprecation_status; +use crate::model_filter_input::{ + add_limit_input_field, add_offset_input_field, add_order_by_input_field, add_where_input_field, +}; use crate::{ - model_arguments, - model_filter::get_where_expression_input_field, - model_order_by::get_order_by_expression_input_field, - permissions, - types::{self, output_type::get_custom_output_type, Annotation, ModelInputAnnotation}, + model_arguments, permissions, + types::{self, output_type::get_custom_output_type, Annotation}, GDS, }; use metadata_resolve; @@ -27,96 +27,10 @@ pub(crate) fn generate_select_many_arguments( { let mut arguments = BTreeMap::new(); - // insert limit argument - if let Some(limit_field) = &model.model.graphql_api.limit_field { - let limit_argument = generate_int_input_argument( - limit_field.field_name.as_str(), - Annotation::Input(types::InputAnnotation::Model( - ModelInputAnnotation::ModelLimitArgument, - )), - )?; - arguments.insert( - limit_argument.name.clone(), - builder.allow_all_namespaced(limit_argument), - ); - } - - // insert offset argument - if let Some(offset_field) = &model.model.graphql_api.offset_field { - let offset_argument = generate_int_input_argument( - offset_field.field_name.as_str(), - Annotation::Input(types::InputAnnotation::Model( - ModelInputAnnotation::ModelOffsetArgument, - )), - )?; - - arguments.insert( - offset_argument.name.clone(), - builder.allow_all_namespaced(offset_argument), - ); - } - - // generate and insert order_by argument - if let Some(order_by_expression_info) = &model.model.graphql_api.order_by_expression { - let order_by_argument = { - get_order_by_expression_input_field( - builder, - model.model.name.clone(), - order_by_expression_info, - ) - }; - - arguments.insert( - order_by_argument.name.clone(), - builder.allow_all_namespaced(order_by_argument), - ); - } - - // generate and insert where argument - let boolean_expression_filter_type = - &model - .model - .filter_expression_type - .as_ref() - .and_then(|bool_exp| match bool_exp { - metadata_resolve::ModelExpressionType::ObjectBooleanExpressionType( - object_boolean_expression_type, - ) => object_boolean_expression_type - .graphql - .as_ref() - .map(|graphql_config| { - ( - object_boolean_expression_type.name.clone(), - graphql_config.clone(), - ) - }), - metadata_resolve::ModelExpressionType::BooleanExpressionType( - boolean_expression_object_type, - ) => boolean_expression_object_type - .graphql - .as_ref() - .map(|graphql_config| { - ( - boolean_expression_object_type.name.clone(), - graphql_config.clone(), - ) - }), - }); - - if let Some((boolean_expression_type_name, boolean_expression_graphql_config)) = - boolean_expression_filter_type - { - let where_argument = get_where_expression_input_field( - builder, - boolean_expression_type_name.clone(), - boolean_expression_graphql_config, - ); - - arguments.insert( - where_argument.name.clone(), - builder.allow_all_namespaced(where_argument), - ); - } + add_limit_input_field(&mut arguments, builder, model)?; + add_offset_input_field(&mut arguments, builder, model)?; + add_order_by_input_field(&mut arguments, builder, model)?; + add_where_input_field(&mut arguments, builder, model)?; Ok(arguments) } @@ -190,19 +104,3 @@ pub(crate) fn select_many_field( ); Ok((query_root_field, field)) } - -/// Generates the input field for the arguments which are of type int. -pub(crate) fn generate_int_input_argument( - name: &str, - annotation: Annotation, -) -> Result, crate::Error> { - let input_field_name = metadata_resolve::mk_name(name)?; - Ok(gql_schema::InputField::new( - input_field_name, - None, - annotation, - ast::TypeContainer::named_null(gql_schema::RegisteredTypeName::int()), - None, - gql_schema::DeprecationStatus::NotDeprecated, - )) -} diff --git a/v3/crates/schema/src/types.rs b/v3/crates/schema/src/types.rs index 767bfe87b2f..85a9c1860eb 100644 --- a/v3/crates/schema/src/types.rs +++ b/v3/crates/schema/src/types.rs @@ -10,6 +10,7 @@ use std::{ }; use open_dds::{ + aggregates, arguments::ArgumentName, commands, data_connector::{DataConnectorColumnName, DataConnectorName, DataConnectorOperatorName}, @@ -74,6 +75,7 @@ pub struct EntityFieldTypeNameMapping { pub enum RootFieldKind { SelectOne, SelectMany, + SelectAggregate, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -186,6 +188,7 @@ pub enum OutputAnnotation { typename_mappings: HashMap>, }, SDL, + Aggregate(crate::aggregates::AggregateOutputAnnotation), } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)] @@ -219,6 +222,7 @@ pub enum ModelInputAnnotation { // Optional because we allow building schema without specifying a data source ndc_column: Option, }, + ModelFilterInputArgument, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)] @@ -371,6 +375,14 @@ pub enum TypeId { graphql_type_name: ast::TypeName, }, ApolloFederationType(PossibleApolloFederationTypes), + AggregateSelectOutputType { + aggregate_expression_name: Qualified, + graphql_type_name: ast::TypeName, + }, + ModelFilterInputType { + model_name: Qualified, + graphql_type_name: ast::TypeName, + }, } #[derive(Serialize, Clone, Debug, Hash, PartialEq, Eq)] @@ -412,6 +424,12 @@ impl TypeId { } | TypeId::OrderByEnumType { graphql_type_name, .. + } + | TypeId::AggregateSelectOutputType { + graphql_type_name, .. + } + | TypeId::ModelFilterInputType { + graphql_type_name, .. } => graphql_type_name.clone(), TypeId::NodeRoot => ast::TypeName(mk_name!("Node")), TypeId::ModelArgumentsInput { type_name, .. } => type_name.clone(),