mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
Aggregates Root Field - Part 3: GraphQL API (#685)
This is Part 3 in a stacked PR set that delivers aggregate root field support. * Part 1: OpenDD: https://github.com/hasura/v3-engine/pull/683 * Part 2: Metadata Resolve: https://github.com/hasura/v3-engine/pull/684 JIRA: [V3ENGINE-159](https://hasurahq.atlassian.net/browse/V3ENGINE-159) ## Description This PR implements the GraphQL API for aggregate root fields. The GraphQL schema matches the design in the [Aggregate and Grouping RFC](https://github.com/hasura/v3-engine/blob/main/rfcs/aggregations.md#aggregations-walkthrough). ### Schema Generation The main new part of the GraphQL schema generation can be found in `crates/schema/src/aggregates.rs`. This is where we generate the new aggregate selection types. However, the root field generation can be found in `crates/schema/src/query_root/select_aggregate.rs`. The new `filter_input` type generation lives in `crates/schema/src/model_filter_input.rs`. As this type effectively encapsulates the existing field arguments used on the Select Many root field, the code to generate them has moved into `model_filter_input.rs` and `select_many.rs` simply reuses the functionality from there (without actually using the filter input type!). ### IR The main aggregates IR generation for the aggregate root field happens in `crates/execute/src/ir/query_root/select_aggregate.rs`. It reads all the input arguments to the root field and then kicks the selection logic over to `model_aggregate_selection_ir` from `crates/execute/src/ir/model_selection.rs`. `crates/execute/src/ir/model_selection.rs` has received some refactoring to facilitate that new `model_aggregate_selection_ir` function; it mostly shares functionality with the existing `model_selection_ir`, except instead of creating fields IR, it creates aggregates IR instead. The actual reading of the aggregate selection happens in `crates/execute/src/ir/aggregates.rs`. The aggregates selection IR captures the nested JSON structure of the aggregate selection, because NDC does not return aggregates in the same nested JSON structure as the GraphQL request. NDC takes a flat list of aggregate operations to run. This captured nested JSON structure is used during response rewriting to convert NDC's flat list into the nested structure that matches the GraphQL request. The aggregate selection IR is placed onto `ModelSelection` alongside the existing fields IR. Since both fields and aggregates can be put into the one NDC request (even though they are not right now), this made sense. They both translate onto one NDC `Query`. This necessitated making the field selection optional on the `ModelSelection` (`ModelSelection.selection`), since aggregate requests currently don't use them. ### Planning `crates/execute/src/plan/model_selection.rs` takes care of mapping the aggregates into the NDC request from the generated IR. There has been a new `ProcessResponseAs` variant added in `crates/execute/src/plan.rs` to capture how to read and reshape an NDC aggregates response. This is handled in `crates/execute/src/process_response.rs` where the captured JSON structure in the IR is used to restore NDC's flat aggregates list into the required nested JSON output structure. ### Testing The Custom Connector has been updated with functionality to allow aggregates over nested object fields (`crates/custom-connector/src/query.rs`). New execution and introspection tests have been added to `crates/engine/tests/execute/aggregates/` to test aggregates against Postgres and the Custom Connector. [V3ENGINE-159]: https://hasurahq.atlassian.net/browse/V3ENGINE-159?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ V3_GIT_ORIGIN_REV_ID: ff47f13eaca70d10de21e102a6667110f8f8af40
This commit is contained in:
parent
81ac867d16
commit
1c5008df7c
@ -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::<Result<Vec<_>>>()?;
|
||||
|
||||
@ -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::<Result<Vec<_>>>()?;
|
||||
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<serde_json::Value> {
|
||||
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<serde_json::Value> {
|
||||
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::<Result<Vec<_>>>()?;
|
||||
|
||||
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<serde_json::Value> {
|
||||
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::<Result<Vec<_>>>()?;
|
||||
|
||||
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,
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -16,7 +16,28 @@ pub(crate) fn scalar_types() -> BTreeMap<String, ndc_models::ScalarType> {
|
||||
"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 {
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user"
|
||||
}
|
||||
]
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
query {
|
||||
Institution_aggregate {
|
||||
Location {
|
||||
_count
|
||||
City {
|
||||
_min
|
||||
_max
|
||||
}
|
||||
Country {
|
||||
min_country: _min
|
||||
max_country: _max
|
||||
}
|
||||
}
|
||||
count_all: _count
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user"
|
||||
}
|
||||
]
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "australianuser"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user"
|
||||
}
|
||||
]
|
@ -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",
|
||||
],
|
||||
)
|
||||
}
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
239
v3/crates/execute/src/ir/aggregates.rs
Normal file
239
v3/crates/execute/src/ir/aggregates.rs
Normal file
@ -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<String, AggregateFieldSelection<'s>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
pub enum AggregateFieldSelection<'s> {
|
||||
Count {
|
||||
column_path: Vec<&'s str>,
|
||||
graphql_field_path: Vec<Alias>,
|
||||
},
|
||||
CountDistinct {
|
||||
column_path: Vec<&'s str>,
|
||||
graphql_field_path: Vec<Alias>,
|
||||
},
|
||||
AggregationFunction {
|
||||
function_name: &'s DataConnectorAggregationFunctionName,
|
||||
column_path: nonempty::NonEmpty<&'s str>,
|
||||
graphql_field_path: Vec<Alias>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'s> AggregateFieldSelection<'s> {
|
||||
pub fn get_graphql_field_path(&self) -> &Vec<Alias> {
|
||||
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<Qualified<CustomTypeName>, TypeMapping>,
|
||||
field_mappings: &'s BTreeMap<FieldName, metadata_resolve::FieldMapping>,
|
||||
aggregate_operand_type: &QualifiedTypeName,
|
||||
) -> Result<AggregateSelectionSet<'s>, 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<String, AggregateFieldSelection<'s>>,
|
||||
selection_set: &normalized_ast::SelectionSet<'s, GDS>,
|
||||
aggregate_operand_type: &QualifiedTypeName,
|
||||
data_connector_name: &Qualified<DataConnectorName>,
|
||||
column_path: &[&'s metadata_resolve::FieldMapping],
|
||||
graphql_field_path: &[Alias],
|
||||
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, TypeMapping>,
|
||||
field_mappings: Option<&'s BTreeMap<FieldName, metadata_resolve::FieldMapping>>,
|
||||
) -> 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::<Vec<Alias>>();
|
||||
|
||||
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::<Vec<&metadata_resolve::FieldMapping>>();
|
||||
|
||||
// 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::<Vec<_>>()
|
||||
.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,
|
||||
}
|
||||
}
|
@ -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<DataConnectorName>,
|
||||
},
|
||||
|
||||
#[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")]
|
||||
|
@ -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<ResolvedOrderBy<'s>>,
|
||||
|
||||
// Fields requested from the model
|
||||
pub(crate) selection: selection_set::ResultSelectionSet<'s>,
|
||||
pub(crate) selection: Option<selection_set::ResultSelectionSet<'s>>,
|
||||
|
||||
// Aggregates requested of the model
|
||||
pub(crate) aggregate_selection: Option<aggregates::AggregateSelectionSet<'s>>,
|
||||
}
|
||||
|
||||
/// Generates the IR fragment for selecting from a model.
|
||||
@ -52,7 +57,7 @@ pub(crate) fn model_selection_ir<'s>(
|
||||
data_type: &Qualified<CustomTypeName>,
|
||||
model_source: &'s metadata_resolve::ModelSource,
|
||||
arguments: BTreeMap<ConnectorArgumentName, ndc_models::Argument>,
|
||||
mut filter_clauses: ResolvedFilterExpression<'s>,
|
||||
filter_clauses: ResolvedFilterExpression<'s>,
|
||||
permissions_predicate: &'s metadata_resolve::FilterPermission,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
@ -61,38 +66,14 @@ pub(crate) fn model_selection_ir<'s>(
|
||||
request_headers: &reqwest::header::HeaderMap,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ModelSelection<'s>, 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<ResolvedFilterExpression<'s>, 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<CustomTypeName>,
|
||||
model_source: &'s metadata_resolve::ModelSource,
|
||||
arguments: BTreeMap<ConnectorArgumentName, ndc_models::Argument>,
|
||||
filter_clauses: ResolvedFilterExpression<'s>,
|
||||
permissions_predicate: &'s metadata_resolve::FilterPermission,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
order_by: Option<ResolvedOrderBy<'s>>,
|
||||
session_variables: &SessionVariables,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ModelSelection<'s>, 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<CustomTypeName>,
|
||||
) -> Result<&'s BTreeMap<open_dds::types::FieldName, metadata_resolve::FieldMapping>, 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()
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
283
v3/crates/execute/src/ir/query_root/select_aggregate.rs
Normal file
283
v3/crates/execute/src/ir/query_root/select_aggregate.rs
Normal file
@ -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<ast::TypeName>,
|
||||
|
||||
// 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<metadata_resolve::ConnectorArgumentName, ndc_models::Argument>,
|
||||
filter_input_arguments: FilterInputArguments<'s>,
|
||||
}
|
||||
|
||||
struct FilterInputArguments<'s> {
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
order_by: Option<ResolvedOrderBy<'s>>,
|
||||
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<open_dds::types::CustomTypeName>,
|
||||
model_source: &'s metadata_resolve::ModelSource,
|
||||
session_variables: &SessionVariables,
|
||||
model_name: &'s Qualified<open_dds::models::ModelName>,
|
||||
) -> Result<ModelSelectAggregate<'n, 's>, 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<ModelSelectAggregateArguments<'s>, 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<ast::Name, normalized_ast::InputField<'s, GDS>>>,
|
||||
model_source: &'s metadata_resolve::ModelSource,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<FilterInputArguments<'s>, 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(),
|
||||
}),
|
||||
})
|
||||
}
|
@ -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<node_field::NodeSelect<'n, 's>>),
|
||||
|
@ -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
|
||||
|
@ -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) => {
|
||||
|
@ -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<json::Value, error::FieldError> {
|
||||
let tracer = tracing_util::global_tracer();
|
||||
|
@ -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<open_dds::commands::CommandName>,
|
||||
type_container: &'ir ast::TypeContainer<ast::TypeName>,
|
||||
},
|
||||
Aggregates {
|
||||
requested_fields: &'ir IndexMap<String, AggregateFieldSelection<'s>>,
|
||||
},
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
@ -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<RemoteJoin<'s, 'ir>>), 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<String, ndc_models::Aggregate> {
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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>,
|
||||
|
@ -17,41 +17,43 @@ pub(crate) fn collect_relationships(
|
||||
relationships: &mut BTreeMap<String, ndc_models::Relationship>,
|
||||
) -> 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
|
||||
|
@ -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<String, crate::ir::aggregates::AggregateFieldSelection>,
|
||||
) -> Result<json::Map<String, json::Value>, error::FieldError> {
|
||||
let mut json_object = json::Map::<String, json::Value>::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<String, json::Value>,
|
||||
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<ndc_models::RowSet>,
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -120,7 +120,7 @@ pub struct RemoteJoin<'s, 'ir> {
|
||||
/// field or an argument name.
|
||||
pub join_mapping: HashMap<SourceFieldName, (SourceFieldAlias, TargetField)>,
|
||||
/// 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,
|
||||
}
|
||||
|
@ -152,7 +152,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aggregate_selection": null
|
||||
},
|
||||
"type_container": {
|
||||
"base": {
|
||||
|
@ -126,7 +126,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aggregate_selection": null
|
||||
},
|
||||
"type_container": {
|
||||
"base": {
|
||||
|
@ -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": {
|
||||
|
@ -164,7 +164,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aggregate_selection": null
|
||||
},
|
||||
"type_container": {
|
||||
"base": {
|
||||
|
@ -148,7 +148,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aggregate_selection": null
|
||||
},
|
||||
"type_container": {
|
||||
"base": {
|
||||
@ -335,7 +336,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aggregate_selection": null
|
||||
},
|
||||
"type_container": {
|
||||
"base": {
|
||||
|
323
v3/crates/schema/src/aggregates.rs
Normal file
323
v3/crates/schema/src/aggregates.rs
Normal file
@ -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<DataConnectorAggregationFunctionInfo>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn get_aggregate_select_output_type(
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
aggregate_expression: &metadata_resolve::AggregateExpression,
|
||||
) -> Result<gql_schema::RegisteredTypeName, Error> {
|
||||
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<GDS>,
|
||||
aggregate_expression_name: &Qualified<AggregateExpressionName>,
|
||||
graphql_type_name: &ast::TypeName,
|
||||
) -> Result<gql_schema::TypeInfo<GDS>, 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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::Field<GDS>>>,
|
||||
gds: &GDS,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
aggregate_expression: &AggregateExpression,
|
||||
object_type_name: &Qualified<CustomTypeName>,
|
||||
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::<GDS>::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::<HashMap<Role, Option<NamespaceAnnotation>>>();
|
||||
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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::Field<GDS>>>,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
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::<GDS>::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::<GDS>::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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::Field<GDS>>>,
|
||||
gds: &GDS,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
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::<GDS>::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<CustomTypeName>,
|
||||
&'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)),
|
||||
}
|
||||
}
|
@ -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<CustomTypeName>,
|
||||
},
|
||||
#[error(
|
||||
"internal error while building schema, field {field_name} not found in type {type_name}"
|
||||
)]
|
||||
InternalObjectTypeFieldNotFound {
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
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<CustomTypeName>,
|
||||
},
|
||||
#[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<AggregateExpressionName>,
|
||||
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<AggregateExpressionName>,
|
||||
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<CommandName>,
|
||||
},
|
||||
#[error("internal error while building schema, aggregate expression not found: {aggregate_expression}")]
|
||||
InternalAggregateExpressionNotFound {
|
||||
aggregate_expression: Qualified<AggregateExpressionName>,
|
||||
},
|
||||
#[error("Cannot generate select_many API for model {model_name} since order_by_expression isn't defined")]
|
||||
NoOrderByExpression { model_name: Qualified<ModelName> },
|
||||
#[error("No graphql type name has been defined for scalar type: {type_name}")]
|
||||
@ -306,6 +349,10 @@ pub enum Error {
|
||||
NoGraphQlOutputTypeNameForObject {
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
},
|
||||
#[error("No graphql select type name has been defined for aggregate expression: {aggregate_expression}")]
|
||||
NoGraphQlSelectTypeNameForAggregateExpression {
|
||||
aggregate_expression: Qualified<AggregateExpressionName>,
|
||||
},
|
||||
#[error("No graphql input type name has been defined for object type: {type_name}")]
|
||||
NoGraphQlInputTypeNameForObject {
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
@ -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<ModelName> },
|
||||
#[error(
|
||||
"Cannot generate the filter input type for model {model_name} since filterInputTypeName isn't defined in the graphql config"
|
||||
)]
|
||||
NoFilterInputTypeNameConfigNameForModel { model_name: Qualified<ModelName> },
|
||||
#[error("Internal error: Relationship capabilities are missing for {relationship} on type {type_name}")]
|
||||
InternalMissingRelationshipCapabilities {
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
|
240
v3/crates/schema/src/model_filter_input.rs
Normal file
240
v3/crates/schema/src/model_filter_input.rs
Normal file
@ -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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||
field_name: &ast::Name,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
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<GDS>,
|
||||
model: &metadata_resolve::ModelWithPermissions,
|
||||
) -> Result<gql_schema::RegisteredTypeName, Error> {
|
||||
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<GDS>,
|
||||
model_name: &Qualified<open_dds::models::ModelName>,
|
||||
graphql_type_name: &ast::TypeName,
|
||||
) -> Result<gql_schema::TypeInfo<GDS>, 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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
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<gql_schema::InputField<GDS>, 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,
|
||||
))
|
||||
}
|
@ -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
|
||||
|
118
v3/crates/schema/src/query_root/select_aggregate.rs
Normal file
118
v3/crates/schema/src/query_root/select_aggregate.rs
Normal file
@ -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<GDS>,
|
||||
model: &metadata_resolve::ModelWithPermissions,
|
||||
select_aggregate: &metadata_resolve::SelectAggregateGraphQlDefinition,
|
||||
parent_type: &ast::TypeName,
|
||||
) -> Result<
|
||||
(
|
||||
ast::Name,
|
||||
gql_schema::Namespaced<GDS, gql_schema::Field<GDS>>,
|
||||
),
|
||||
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::<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>::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<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||
gds: &GDS,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
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(())
|
||||
}
|
@ -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<gql_schema::InputField<GDS>, 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,
|
||||
))
|
||||
}
|
||||
|
@ -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<ast::TypeName, Vec<types::FieldName>>,
|
||||
},
|
||||
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<NdcColumnForComparison>,
|
||||
},
|
||||
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<aggregates::AggregateExpressionName>,
|
||||
graphql_type_name: ast::TypeName,
|
||||
},
|
||||
ModelFilterInputType {
|
||||
model_name: Qualified<models::ModelName>,
|
||||
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(),
|
||||
|
Loading…
Reference in New Issue
Block a user