mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
Aggregates Root Field - Part 2: Metadata Resolve (#684)
This is Part 2 in a stacked PR set that delivers aggregate root field support. * Part 1: OpenDD: https://github.com/hasura/v3-engine/pull/683 * Part 3: GraphQL API: https://github.com/hasura/v3-engine/pull/685 JIRA: [V3ENGINE-159](https://hasurahq.atlassian.net/browse/V3ENGINE-159) ## Description This PR implements the metadata resolve phase of the engine and adds support for resolving `AggregateExpression`s and validates their use when linked to a `Model`. The bulk of the changes can be found in: * `crates/metadata-resolve/src/stages/aggregates/*` - This is where the `AggregateExpression`s are resolved * `crates/metadata-resolve/src/stages/models/mod.rs` - This is where we validate the `AggregateExpression` specified for use in the model is actually compatible with the model and its data connector The `ndc-spec` version used has been lifted to the latest version that adds support for aggregates over nested objects (https://github.com/hasura/ndc-spec/pull/144). This necessitated changes in the Custom Connector, but actual functionality to implement aggregation over nested objects is implemented in Part 3. There are also some changes in `crates/metadata-resolve/src/types/subgraph.rs` where the `Display` trait for the various `Qualified<T>`, `QualifiedTypeReference`, etc types has been reworked so that they print more cleanly, with the subgraph being put outside the type syntax, and array types getting formatted correctly. For example, previous an array of Varchars would have printed as `Varchar (in subgraph default)!`, now it properly formats as `[Varchar!]! (in subgraph default)`. This was necessary to make useful error messages using these types. A tonne of tests have been added in `crates/engine/tests/validate_metadata_artifacts/aggregate_expressions` to test every error condition of the metadata resolve process. [V3ENGINE-159]: https://hasurahq.atlassian.net/browse/V3ENGINE-159?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ V3_GIT_ORIGIN_REV_ID: ffd859127a3f1560707f06ef01906c9d1b183d31
This commit is contained in:
parent
e07197e1be
commit
81ac867d16
3
v3/Cargo.lock
generated
3
v3/Cargo.lock
generated
@ -929,6 +929,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"test-each",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
@ -1952,7 +1953,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ndc-models"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/hasura/ndc-spec.git?rev=622c643b4f0b6bbe4601c0f065d6d93a4bd3e9db#622c643b4f0b6bbe4601c0f065d6d93a4bd3e9db"
|
||||
source = "git+https://github.com/hasura/ndc-spec.git?rev=aa8ad48e42aefd9e585a4c923bebfd56eee40204#aa8ad48e42aefd9e585a4c923bebfd56eee40204"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"schemars",
|
||||
|
@ -17,7 +17,7 @@ anyhow = "1"
|
||||
axum = { version = "0.6", features = ["http2"] }
|
||||
env_logger = "0.11"
|
||||
indexmap = "2"
|
||||
ndc-models = { git = "https://github.com/hasura/ndc-spec.git", rev = "622c643b4f0b6bbe4601c0f065d6d93a4bd3e9db" }
|
||||
ndc-models = { git = "https://github.com/hasura/ndc-spec.git", rev = "aa8ad48e42aefd9e585a4c923bebfd56eee40204" }
|
||||
regex = "1"
|
||||
serde_json = "1"
|
||||
serde = "1"
|
||||
|
@ -220,7 +220,11 @@ fn eval_aggregate(
|
||||
) -> Result<serde_json::Value> {
|
||||
match aggregate {
|
||||
ndc_models::Aggregate::StarCount {} => Ok(serde_json::Value::from(paginated.len())),
|
||||
ndc_models::Aggregate::ColumnCount { column, distinct } => {
|
||||
ndc_models::Aggregate::ColumnCount {
|
||||
column,
|
||||
field_path: _,
|
||||
distinct,
|
||||
} => {
|
||||
let values = paginated
|
||||
.iter()
|
||||
.map(|row| {
|
||||
@ -264,7 +268,11 @@ fn eval_aggregate(
|
||||
)
|
||||
})
|
||||
}
|
||||
ndc_models::Aggregate::SingleColumn { column, function } => {
|
||||
ndc_models::Aggregate::SingleColumn {
|
||||
column,
|
||||
field_path: _,
|
||||
function,
|
||||
} => {
|
||||
let values = paginated
|
||||
.iter()
|
||||
.map(|row| {
|
||||
@ -428,6 +436,7 @@ fn eval_order_by_element(
|
||||
} => eval_order_by_column(collection_relationships, variables, state, item, path, name),
|
||||
ndc_models::OrderByTarget::SingleColumnAggregate {
|
||||
column,
|
||||
field_path: _,
|
||||
function,
|
||||
path,
|
||||
} => eval_order_by_single_column_aggregate(
|
||||
|
@ -25,6 +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,
|
||||
filter_by: None,
|
||||
order_by: None,
|
||||
},
|
||||
|
@ -59,6 +59,7 @@ build-data = "0.2.1" # To set short commit-sha at build time
|
||||
criterion = { version = "0.5", features = ["html_reports", "async_tokio"] }
|
||||
goldenfile = "1.7.1"
|
||||
pretty_assertions = "1.3.0"
|
||||
test-each = "*"
|
||||
tokio-test = "0.4.2"
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
|
@ -131,7 +131,7 @@ fn test_filter_error_filter_expression_type_present_filter_input_not_present() -
|
||||
|
||||
assert_eq!(
|
||||
gds.unwrap_err().to_string(),
|
||||
"metadata is not consistent: the filterInput need to be defined in GraphqlConfig, when models have filterExpressionType"
|
||||
"metadata is not consistent: the filterInput needs to be defined in GraphqlConfig, when models have filterExpressionType"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@ -149,7 +149,7 @@ fn test_order_by_error_order_by_expression_type_present_order_by_input_not_prese
|
||||
|
||||
assert_eq!(
|
||||
gds.unwrap_err().to_string(),
|
||||
"metadata is not consistent: the orderByInput need to be defined in GraphqlConfig, when models have orderByExpressionType"
|
||||
"metadata is not consistent: the orderByInput needs to be defined in GraphqlConfig, when models have orderByExpressionType"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@ -183,7 +183,7 @@ fn test_arguments_error_arguments_input_type_present_arguments_input_not_present
|
||||
|
||||
assert_eq!(
|
||||
gds.unwrap_err().to_string(),
|
||||
"metadata is not consistent: the fieldName for argumentsInput need to be defined in GraphqlConfig, when models have argumentsInputType"
|
||||
"metadata is not consistent: the fieldName for argumentsInput needs to be defined in GraphqlConfig, when models have argumentsInputType"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@ -364,6 +364,32 @@ fn test_allow_metadata_with_deprecated_field() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Aggregate Expression Tests
|
||||
#[test_each::path(
|
||||
glob = "crates/engine/tests/validate_metadata_artifacts/aggregate_expressions/*/",
|
||||
name(segments = 1)
|
||||
)]
|
||||
#[allow(clippy::needless_pass_by_value)] // must receive a `PathBuf`
|
||||
fn test_aggregate_expressions(test_folder_path: PathBuf) -> anyhow::Result<()> {
|
||||
let metadata_json = fs::read_to_string(test_folder_path.join("metadata.json"))?;
|
||||
let metadata = open_dds::Metadata::from_json_str(&metadata_json)?;
|
||||
|
||||
let gds = GDS::new_with_default_flags(metadata);
|
||||
|
||||
match fs::read_to_string(test_folder_path.join("error.txt")) {
|
||||
Ok(error_message) => {
|
||||
assert_eq!(gds.unwrap_err().to_string(), error_message.trim());
|
||||
}
|
||||
Err(err) => match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
gds?;
|
||||
} // Assert that it validated okay
|
||||
_ => Err(err)?,
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_metadata(path: &str) -> anyhow::Result<open_dds::Metadata> {
|
||||
let json = read_file(path)?;
|
||||
let value = open_dds::Metadata::from_json_str(&json)?;
|
||||
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Invoice_aggregate_exp (in subgraph default) specifies an aggregatable field 'invoiceId' of type Int4! (in subgraph default), however the aggregation expression used to aggregate that field (Int8_aggregate_exp (in subgraph default)) is for aggregating a different type: Int8 (in subgraph default)
|
@ -0,0 +1,154 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int8_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int8_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int8",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int8"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int8",
|
||||
"graphql": {
|
||||
"typeName": "App_Int8"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Invoice_aggregate_exp (in subgraph default) specifies an aggregatable field 'randoField' that does not exist on its operand type Invoice (in subgraph default)
|
@ -0,0 +1,147 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
},
|
||||
{
|
||||
"fieldName": "randoField",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_top5' which is mapped to the data connector 'mypg (in subgraph default)' but the Open DD return type [Int4] (in subgraph default) is not compatible with the data connector's return type. Reason: The data connector's return type is the named type 'int4', but the Open DD return type is an array
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_top5' which is mapped to the data connector 'mypg (in subgraph default)' but the Open DD return type Int4 (in subgraph default) is not compatible with the data connector's return type. Reason: The data connector's return type is an array, but the Open DD return type is not
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_sum' which is mapped to the data connector 'mypg (in subgraph default)' but the Open DD return type Int8! (in subgraph default) is not compatible with the data connector's return type. Reason: The data connector's return type is nullable, but the Open DD return type is not
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_minmax' which is mapped to the data connector 'mypg (in subgraph default)' but the Open DD return type MinMax (in subgraph default) is not compatible with the data connector's return type. Reason: There is no type mapping defined from the Open DD return object type to the data connector's object type 'MinMax'
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_sum' which is mapped to the data connector 'mypg (in subgraph default)' but the Open DD return type Int4! (in subgraph default) is not compatible with the data connector's return type. Reason: The data connector's return scalar type representation (Int8 (in subgraph default)) does not match the Open DD return type
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the name used by query.aggregate.countFieldName from the GraphqlConfig conflicts with the aggregatable field name _count in the aggregate expression Invoice_aggregate_exp (in subgraph default)
|
@ -0,0 +1,208 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "_count",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"graphql": {
|
||||
"selectTypeName": "Invoice_aggregate_exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "_count",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the name used by query.aggregate.countFieldName from the GraphqlConfig conflicts with the aggregation function name _count in the aggregate expression Int4_aggregate_exp (in subgraph default)
|
@ -0,0 +1,110 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "_count",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
},
|
||||
"graphql": {
|
||||
"selectTypeName": "Int4_aggregate_exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the name used by query.aggregate.countDistinctFieldName from the GraphqlConfig conflicts with the aggregatable field name _count_distinct in the aggregate expression Invoice_aggregate_exp (in subgraph default)
|
@ -0,0 +1,208 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "_count_distinct",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
},
|
||||
"graphql": {
|
||||
"selectTypeName": "Invoice_aggregate_exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "_count_distinct",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the name used by query.aggregate.countDistinctFieldName from the GraphqlConfig conflicts with the aggregation function name _count_distinct in the aggregate expression Int4_aggregate_exp (in subgraph default)
|
@ -0,0 +1,110 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "_count_distinct",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
},
|
||||
"graphql": {
|
||||
"selectTypeName": "Int4_aggregate_exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_min' which is mapped to the data connector 'mypg (in subgraph default)' but the Open DD return type Int4! (in subgraph default) is not compatible with the data connector's return type. Reason: The data connector's return type (Invoice) isn't a scalar type
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) defines an aggregation function mapping to a data connector that does not support aggregates: mypg (in subgraph default)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the data connector mypg (in subgraph default) does not support aggregates over nested object fields, such as the field billingAddress used in aggregate expression Invoice_aggregate_exp (in subgraph default)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the model Invoice (in subgraph default) is using the aggregate expression Invoice_aggregate_exp (in subgraph default) which has the countDistinct aggregation enabled, but countDistinct is not valid when aggregating a model as every object is already logically distinct
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: multiple graphql types found with the same name: App_Invoice
|
@ -0,0 +1,146 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectTypeName": "App_Invoice"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: multiple graphql types found with the same name: App_Invoice
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the following aggregate expression is defined more than once: Invoice_aggregate_exp (in subgraph default)
|
@ -0,0 +1,161 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: an unnecessary filter input type name graphql configuration has been specified for model Invoice (in subgraph default) that does not use aggregates
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Invoice_aggregate_exp (in subgraph default) specifies an aggregatable field 'doubleNestedInt' of type [[Int4]] (in subgraph default), however arrays of arrays are not supported for aggregation
|
@ -0,0 +1,149 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "doubleNestedInt",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "doubleNestedInt",
|
||||
"type": "[[Int4]]",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Invoice_aggregate_exp (in subgraph default) has duplicate definitions of the aggregatable field 'invoiceId'
|
@ -0,0 +1,151 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
},
|
||||
{
|
||||
"fieldName": "customerId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
},
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) has duplicate definitions of the aggregation function 'sum'
|
@ -0,0 +1,54 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Invoice_aggregate_exp (in subgraph default) for model Invoice (in subgraph default) has not been defined
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Invoice_aggregate_exp (in subgraph default) defines a graphql section and so query.aggregate must be set in the GraphqlConfig
|
@ -0,0 +1,197 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"mutation": {
|
||||
"rootOperationTypeName": "Mutation"
|
||||
},
|
||||
"apolloFederation": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"graphql": {
|
||||
"selectTypeName": "Invoice_aggregate_exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) defines an aggregation function mapping to an unknown data connector: mypg (in subgraph default)
|
@ -0,0 +1,81 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "_sum",
|
||||
"returnType": "Int8!"
|
||||
},
|
||||
{
|
||||
"name": "_min",
|
||||
"returnType": "Int4!"
|
||||
},
|
||||
{
|
||||
"name": "_max",
|
||||
"returnType": "Int4!"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": [
|
||||
{
|
||||
"dataConnectorName": "mypg",
|
||||
"dataConnectorScalarType": "int4",
|
||||
"functionMapping": {
|
||||
"_sum": {
|
||||
"name": "sum"
|
||||
},
|
||||
"_min": {
|
||||
"name": "min"
|
||||
},
|
||||
"_max": {
|
||||
"name": "max"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int8",
|
||||
"graphql": {
|
||||
"typeName": "App_Int8"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_sum' which is mapped to the data connector 'mypg (in subgraph default)', however the mapped data connector aggregate function cannot be found: schum
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_min' but there is no mapping defined to an aggregation function in the data connector 'mypg (in subgraph default)'
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_sum' but the mapping to the data connector 'mypg (in subgraph default)' specifies a data connector scalar type that does not exist: schmint
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the model Invoice (in subgraph default) is using the aggregate expression Int4_aggregate_exp (in subgraph default) but for the data connector mypg (in subgraph default) and scalar type int4, mappings are not provided for all aggregation functions in the aggregate expression
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function 'sum' that uses an unknown type for its return type: Int8
|
@ -0,0 +1,54 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an aggregation function '_min' which is mapped to the data connector 'mypg (in subgraph default)' but the Open DD return type Int4! (in subgraph default) is not compatible with the data connector's return type. Reason: The data connector's return scalar type (int4) doesn't have a type representation
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the filterInputFieldName for aggregate needs to be defined in GraphqlConfig, when models have a selectAggregate graphql API
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: filter input type name graphql configuration must be specified for model Invoice (in subgraph default) because it uses aggregates
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: a source must be defined for model Invoice (in subgraph default) in order to use aggregate expressions
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Invoice_aggregate_exp (in subgraph default) specifies an aggregatable field 'invoiceId' that references an aggregate expression that cannot be found: Int4_aggregate_exp (in subgraph default)
|
@ -0,0 +1,110 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice",
|
||||
"fields": [
|
||||
{
|
||||
"name": "billingAddress",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCity",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingCountry",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingPostalCode",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "billingState",
|
||||
"type": "Varchar",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "customerId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceDate",
|
||||
"type": "Timestamp!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "invoiceId",
|
||||
"type": "Int4!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"type": "Numeric!",
|
||||
"description": null,
|
||||
"deprecated": null
|
||||
}
|
||||
],
|
||||
"globalIdFields": null,
|
||||
"graphql": {
|
||||
"typeName": "App_Invoice",
|
||||
"inputTypeName": "App_InvoiceInput",
|
||||
"apolloFederation": null
|
||||
},
|
||||
"description": null,
|
||||
"dataConnectorTypeMapping": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Invoice_aggregate_exp (in subgraph default) specifies an operand object type that cannot be found: Invoice (in subgraph default)
|
@ -0,0 +1,72 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Invoice_aggregate_exp",
|
||||
"operand": {
|
||||
"object": {
|
||||
"aggregatedType": "Invoice",
|
||||
"aggregatableFields": [
|
||||
{
|
||||
"fieldName": "invoiceId",
|
||||
"aggregateExpression": "Int4_aggregate_exp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4",
|
||||
"graphql": {
|
||||
"typeName": "App_Int4"
|
||||
},
|
||||
"description": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the aggregate expression Int4_aggregate_exp (in subgraph default) specifies an operand scalar type that cannot be found: Int4 (in subgraph default)
|
@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"kind": "AggregateExpression",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "Int4_aggregate_exp",
|
||||
"operand": {
|
||||
"scalar": {
|
||||
"aggregatedType": "Int4",
|
||||
"aggregationFunctions": [
|
||||
{
|
||||
"name": "sum",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"returnType": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"returnType": "Int4"
|
||||
}
|
||||
],
|
||||
"dataConnectorAggregationFunctionMapping": []
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"enable": true
|
||||
},
|
||||
"countDistinct": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
metadata is not consistent: the model Invoice (in subgraph default) is using the aggregate expression Int4_aggregate_exp (in subgraph default) but its operand type Int4 (in subgraph default) does not match the model's type Invoice (in subgraph default)
|
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,7 @@ bytes = "1.6.0"
|
||||
derive_more = "0.99.17"
|
||||
futures-util = "0.3"
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
ndc-models = { git = "https://github.com/hasura/ndc-spec.git", rev = "622c643b4f0b6bbe4601c0f065d6d93a4bd3e9db" }
|
||||
ndc-models = { git = "https://github.com/hasura/ndc-spec.git", rev = "aa8ad48e42aefd9e585a4c923bebfd56eee40204" }
|
||||
nonempty = "0.10"
|
||||
reqwest = { version = "0.11", features = ["json", "multipart"] }
|
||||
schemars = { version = "0.8.20", features = ["smol_str"] }
|
||||
|
@ -107,7 +107,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
|
@ -98,7 +98,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
|
@ -39,10 +39,12 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
"collection_type": "article",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"article\"}": {
|
||||
"Object": {
|
||||
@ -157,10 +159,12 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "author",
|
||||
"collection_type": "author",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"author\"}": {
|
||||
"Object": {
|
||||
@ -275,10 +279,12 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
"collection_type": "article",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"article\"}": {
|
||||
"Object": {
|
||||
@ -491,7 +497,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"arguments": {},
|
||||
@ -514,7 +521,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
@ -544,7 +552,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "author",
|
||||
@ -574,7 +583,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
@ -624,7 +634,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"source_type_mappings": {
|
||||
@ -675,10 +686,12 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
"collection_type": "article",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"article\"}": {
|
||||
"Object": {
|
||||
@ -765,7 +778,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"source_type_mappings": {
|
||||
@ -816,10 +830,12 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "author",
|
||||
"collection_type": "author",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"author\"}": {
|
||||
"Object": {
|
||||
@ -906,7 +922,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"source_type_mappings": {
|
||||
@ -957,10 +974,12 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
"collection_type": "article",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"article\"}": {
|
||||
"Object": {
|
||||
|
@ -107,7 +107,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
|
@ -98,7 +98,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "article",
|
||||
@ -285,7 +286,8 @@
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_explaining_queries": true,
|
||||
"supports_explaining_mutations": false
|
||||
"supports_explaining_mutations": false,
|
||||
"supports_nested_object_aggregations": false
|
||||
}
|
||||
},
|
||||
"collection": "author",
|
||||
|
@ -15,7 +15,7 @@ open-dds = { path = "../open-dds" }
|
||||
derive_more = "0.99.17"
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
lazy_static = "1.4.0"
|
||||
ndc-models = { git = "https://github.com/hasura/ndc-spec.git", rev = "622c643b4f0b6bbe4601c0f065d6d93a4bd3e9db" }
|
||||
ndc-models = { git = "https://github.com/hasura/ndc-spec.git", rev = "aa8ad48e42aefd9e585a4c923bebfd56eee40204" }
|
||||
nonempty = "0.10"
|
||||
ref-cast = "1.0"
|
||||
reqwest = { version = "0.11", features = ["json", "multipart"] }
|
||||
|
@ -118,6 +118,13 @@ pub fn unwrap_custom_type_name(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_qualified_type_name(type_reference: &QualifiedTypeReference) -> &QualifiedTypeName {
|
||||
match &type_reference.underlying_type {
|
||||
QualifiedBaseType::List(inner_type) => unwrap_qualified_type_name(inner_type),
|
||||
QualifiedBaseType::Named(type_name) => type_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to create GraphQL compliant name
|
||||
pub fn mk_name(name: &str) -> Result<ast::Name, Error> {
|
||||
ast::Name::from_str(name).map_err(|_| Error::InvalidGraphQlName {
|
||||
|
@ -17,6 +17,10 @@ pub use helpers::types::{
|
||||
get_type_representation, mk_name, object_type_exists, unwrap_custom_type_name,
|
||||
NdcColumnForComparison, TypeRepresentation,
|
||||
};
|
||||
pub use stages::aggregates::{
|
||||
AggregatableFieldInfo, AggregateExpression, AggregateExpressionGraphqlConfig, AggregateOperand,
|
||||
AggregationFunctionInfo, DataConnectorAggregationFunctionInfo,
|
||||
};
|
||||
pub use stages::boolean_expressions::{
|
||||
BooleanExpressionGraphqlConfig, ComparisonExpressionInfo, ResolvedObjectBooleanExpressionType,
|
||||
};
|
||||
@ -28,7 +32,7 @@ pub use stages::model_permissions::{
|
||||
};
|
||||
pub use stages::models::{
|
||||
ConnectorArgumentName, Model, ModelExpressionType, ModelOrderByExpression, ModelSource,
|
||||
SelectManyGraphQlDefinition, SelectUniqueGraphQlDefinition,
|
||||
SelectAggregateGraphQlDefinition, SelectManyGraphQlDefinition, SelectUniqueGraphQlDefinition,
|
||||
};
|
||||
pub use stages::object_boolean_expressions::{
|
||||
ObjectBooleanExpressionDataConnector, ObjectBooleanExpressionType,
|
||||
@ -41,6 +45,7 @@ pub use stages::relationships::{
|
||||
RelationshipCapabilities, RelationshipCommandMapping, RelationshipExecutionCategory,
|
||||
RelationshipModelMapping, RelationshipTarget,
|
||||
};
|
||||
pub use stages::scalar_types::ScalarTypeRepresentation;
|
||||
pub use stages::type_permissions::TypeInputPermission;
|
||||
pub use stages::{resolve, Metadata};
|
||||
pub use types::error::{BooleanExpressionError, Error};
|
||||
|
751
v3/crates/metadata-resolve/src/stages/aggregates/mod.rs
Normal file
751
v3/crates/metadata-resolve/src/stages/aggregates/mod.rs
Normal file
@ -0,0 +1,751 @@
|
||||
use core::hash::Hash;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||
|
||||
use lang_graphql::ast::common as ast;
|
||||
use open_dds::aggregates::{AggregateExpressionName, AggregationFunctionName};
|
||||
use open_dds::data_connector::{
|
||||
DataConnectorName, DataConnectorObjectType, DataConnectorScalarType,
|
||||
};
|
||||
use open_dds::types::{CustomTypeName, TypeName};
|
||||
|
||||
use crate::helpers::types::{store_new_graphql_type, unwrap_qualified_type_name};
|
||||
use crate::stages::{
|
||||
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap, graphql_config, scalar_types,
|
||||
type_permissions,
|
||||
};
|
||||
use crate::types::subgraph::{mk_qualified_type_name, mk_qualified_type_reference};
|
||||
use crate::{
|
||||
mk_name, Error, Qualified, QualifiedBaseType, QualifiedTypeName, QualifiedTypeReference,
|
||||
};
|
||||
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
use super::data_connectors;
|
||||
|
||||
pub fn resolve(
|
||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||
data_connectors: &data_connectors::DataConnectors,
|
||||
data_connector_scalars: &BTreeMap<
|
||||
Qualified<DataConnectorName>,
|
||||
ScalarTypeWithRepresentationInfoMap,
|
||||
>,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
mut existing_graphql_types: BTreeSet<ast::TypeName>,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
) -> Result<AggregateExpressionsOutput, Error> {
|
||||
let mut resolved_aggregate_expressions =
|
||||
BTreeMap::<Qualified<AggregateExpressionName>, AggregateExpression>::new();
|
||||
|
||||
for open_dds::accessor::QualifiedObject {
|
||||
subgraph,
|
||||
object: aggregate_expression,
|
||||
} in &metadata_accessor.aggregate_expressions
|
||||
{
|
||||
let aggregate_expression_name =
|
||||
Qualified::new(subgraph.clone(), aggregate_expression.name.clone());
|
||||
|
||||
// Have we seen this aggregate expression name before?
|
||||
// Check this before checking anything else, so we can fail fast
|
||||
if resolved_aggregate_expressions.contains_key(&aggregate_expression_name) {
|
||||
return Err(
|
||||
AggregateExpressionError::DuplicateAggregateExpressionDefinition {
|
||||
name: aggregate_expression_name,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let resolved_aggregate_expression = resolve_aggregate_expression(
|
||||
metadata_accessor,
|
||||
data_connectors,
|
||||
data_connector_scalars,
|
||||
object_types,
|
||||
scalar_types,
|
||||
&mut existing_graphql_types,
|
||||
graphql_config,
|
||||
&aggregate_expression_name,
|
||||
aggregate_expression,
|
||||
)?;
|
||||
|
||||
resolved_aggregate_expressions
|
||||
.insert(aggregate_expression_name, resolved_aggregate_expression);
|
||||
}
|
||||
|
||||
Ok(AggregateExpressionsOutput {
|
||||
aggregate_expressions: resolved_aggregate_expressions,
|
||||
graphql_types: existing_graphql_types,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_aggregate_expression(
|
||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||
data_connectors: &data_connectors::DataConnectors,
|
||||
data_connector_scalars: &BTreeMap<
|
||||
Qualified<DataConnectorName>,
|
||||
ScalarTypeWithRepresentationInfoMap,
|
||||
>,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
existing_graphql_types: &mut BTreeSet<ast::TypeName>,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
aggregate_expression_name: &Qualified<AggregateExpressionName>,
|
||||
aggregate_expression: &open_dds::aggregates::AggregateExpressionV1,
|
||||
) -> Result<AggregateExpression, Error> {
|
||||
let operand = match &aggregate_expression.operand {
|
||||
open_dds::aggregates::AggregateOperand::Object(object_operand) => resolve_object_operand(
|
||||
metadata_accessor,
|
||||
object_types,
|
||||
aggregate_expression_name,
|
||||
object_operand,
|
||||
),
|
||||
open_dds::aggregates::AggregateOperand::Scalar(scalar_operand) => resolve_scalar_operand(
|
||||
data_connectors,
|
||||
data_connector_scalars,
|
||||
object_types,
|
||||
scalar_types,
|
||||
aggregate_expression_name,
|
||||
scalar_operand,
|
||||
),
|
||||
}?;
|
||||
|
||||
let graphql = resolve_aggregate_expression_graphql_config(
|
||||
existing_graphql_types,
|
||||
graphql_config,
|
||||
aggregate_expression_name,
|
||||
&operand,
|
||||
&aggregate_expression.graphql,
|
||||
)?;
|
||||
|
||||
Ok(AggregateExpression {
|
||||
name: aggregate_expression_name.clone(),
|
||||
operand,
|
||||
graphql,
|
||||
count: resolve_aggregate_count(&aggregate_expression.count),
|
||||
count_distinct: resolve_aggregate_count(&aggregate_expression.count_distinct),
|
||||
description: aggregate_expression.description.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_object_operand(
|
||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
aggregate_expression_name: &Qualified<AggregateExpressionName>,
|
||||
object_operand: &open_dds::aggregates::ObjectAggregateOperand,
|
||||
) -> Result<AggregateOperand, Error> {
|
||||
let operand_object_type_name = qualify(
|
||||
&object_operand.aggregated_type,
|
||||
&aggregate_expression_name.subgraph,
|
||||
);
|
||||
|
||||
// Does the operand object type exist?
|
||||
let operand_object_type =
|
||||
object_types
|
||||
.get(&operand_object_type_name)
|
||||
.ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::AggregateOperandObjectTypeNotFound {
|
||||
name: aggregate_expression_name.clone(),
|
||||
type_name: operand_object_type_name.clone(),
|
||||
}
|
||||
.into()
|
||||
})?;
|
||||
|
||||
// Check that no fields have been duplicated
|
||||
check_for_duplicates(&object_operand.aggregatable_fields, |agg_field_def| {
|
||||
&agg_field_def.field_name
|
||||
})
|
||||
.map_err(|agg_field_def| -> Error {
|
||||
AggregateExpressionError::AggregateOperandObjectFieldDuplicated {
|
||||
name: aggregate_expression_name.clone(),
|
||||
field_name: agg_field_def.field_name.clone(),
|
||||
}
|
||||
.into()
|
||||
})?;
|
||||
|
||||
let aggregatable_fields = object_operand
|
||||
.aggregatable_fields
|
||||
.iter()
|
||||
.map(|agg_field_def| {
|
||||
resolve_aggregatable_field(
|
||||
agg_field_def,
|
||||
operand_object_type,
|
||||
aggregate_expression_name,
|
||||
&operand_object_type_name,
|
||||
metadata_accessor,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
Ok(AggregateOperand {
|
||||
aggregated_type: QualifiedTypeName::Custom(operand_object_type_name),
|
||||
aggregatable_fields,
|
||||
aggregation_functions: vec![], // Object types don't support being aggregated themselves at this time
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_aggregatable_field(
|
||||
aggregate_field_def: &open_dds::aggregates::AggregatableFieldDefinition,
|
||||
operand_object_type: &type_permissions::ObjectTypeWithPermissions,
|
||||
aggregate_expression_name: &Qualified<AggregateExpressionName>,
|
||||
operand_object_type_name: &Qualified<CustomTypeName>,
|
||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||
) -> Result<AggregatableFieldInfo, Error> {
|
||||
// Does the field exist in the operand object type?
|
||||
let field_def = operand_object_type
|
||||
.object_type
|
||||
.fields
|
||||
.get(&aggregate_field_def.field_name)
|
||||
.ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::AggregateOperandObjectFieldNotFound {
|
||||
name: aggregate_expression_name.clone(),
|
||||
operand_type: operand_object_type_name.clone(),
|
||||
field_name: aggregate_field_def.field_name.clone(),
|
||||
}
|
||||
.into()
|
||||
})?;
|
||||
|
||||
// Get the underlying type of the field (ie. ignore nullability and unwrap one level of array)
|
||||
let field_agg_type_name =
|
||||
get_underlying_aggregatable_type(&field_def.field_type).ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::MultipleNestedArrayAggregationNotSupported {
|
||||
name: aggregate_expression_name.clone(),
|
||||
field_name: aggregate_field_def.field_name.clone(),
|
||||
field_type: field_def.field_type.clone(),
|
||||
}
|
||||
.into()
|
||||
})?;
|
||||
|
||||
let field_aggregate_expression_name = Qualified::new(
|
||||
aggregate_expression_name.subgraph.clone(),
|
||||
aggregate_field_def.aggregate_expression.clone(),
|
||||
);
|
||||
|
||||
// Find the field's referenced aggregate expression
|
||||
let field_aggregate_expression = metadata_accessor
|
||||
.aggregate_expressions
|
||||
.iter()
|
||||
.find(|agg_exp| {
|
||||
agg_exp.subgraph == aggregate_expression_name.subgraph
|
||||
&& agg_exp.object.name == aggregate_field_def.aggregate_expression
|
||||
})
|
||||
.map(|agg_exp| &agg_exp.object)
|
||||
.ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::AggregateOperandObjectFieldAggregateExpressionNotFound {
|
||||
name: aggregate_expression_name.clone(),
|
||||
field_name: aggregate_field_def.field_name.clone(),
|
||||
field_aggregate_expression: field_aggregate_expression_name.clone(),
|
||||
}
|
||||
.into()
|
||||
})?;
|
||||
|
||||
// Get the operand type of the field's referenced aggregate expression
|
||||
let field_aggregate_expression_operand_type_name = match &field_aggregate_expression.operand {
|
||||
open_dds::aggregates::AggregateOperand::Object(object_operand) => {
|
||||
QualifiedTypeName::Custom(qualify(
|
||||
&object_operand.aggregated_type,
|
||||
&aggregate_expression_name.subgraph,
|
||||
))
|
||||
}
|
||||
open_dds::aggregates::AggregateOperand::Scalar(scalar_operand) => mk_qualified_type_name(
|
||||
&scalar_operand.aggregated_type,
|
||||
&aggregate_expression_name.subgraph,
|
||||
),
|
||||
};
|
||||
|
||||
// Check that the field's aggregation expression actually operates over the field's type
|
||||
if *field_agg_type_name != field_aggregate_expression_operand_type_name {
|
||||
return Err(
|
||||
AggregateExpressionError::AggregateOperandObjectFieldTypeMismatch {
|
||||
name: aggregate_expression_name.clone(),
|
||||
operand_type: operand_object_type_name.clone(),
|
||||
field_name: aggregate_field_def.field_name.clone(),
|
||||
field_type: field_def.field_type.clone(),
|
||||
field_aggregate_exp_name: field_aggregate_expression_name,
|
||||
field_aggregate_exp_operand_type: field_aggregate_expression_operand_type_name,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(AggregatableFieldInfo {
|
||||
field_name: aggregate_field_def.field_name.clone(),
|
||||
description: aggregate_field_def.description.clone(),
|
||||
aggregate_expression: field_aggregate_expression_name,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_scalar_operand(
|
||||
data_connectors: &data_connectors::DataConnectors,
|
||||
data_connector_scalars: &BTreeMap<
|
||||
Qualified<DataConnectorName>,
|
||||
ScalarTypeWithRepresentationInfoMap,
|
||||
>,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
aggregate_expression_name: &Qualified<AggregateExpressionName>,
|
||||
scalar_operand: &open_dds::aggregates::ScalarAggregateOperand,
|
||||
) -> Result<AggregateOperand, Error> {
|
||||
// If a custom scalar type has been specified, check that it exists
|
||||
match &scalar_operand.aggregated_type {
|
||||
TypeName::Inbuilt(_) => (),
|
||||
TypeName::Custom(operand_type_name) => {
|
||||
let qualified_custom_scalar_name =
|
||||
qualify(operand_type_name, &aggregate_expression_name.subgraph);
|
||||
if !scalar_types.contains_key(&qualified_custom_scalar_name) {
|
||||
return Err(
|
||||
AggregateExpressionError::AggregateOperandScalarTypeNotFound {
|
||||
name: aggregate_expression_name.clone(),
|
||||
type_name: qualified_custom_scalar_name.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let operand_scalar_type = mk_qualified_type_name(
|
||||
&scalar_operand.aggregated_type,
|
||||
&aggregate_expression_name.subgraph,
|
||||
);
|
||||
|
||||
// Check that no functions have been duplicated
|
||||
check_for_duplicates(&scalar_operand.aggregation_functions, |agg_fn_def| {
|
||||
&agg_fn_def.name
|
||||
})
|
||||
.map_err(|agg_fn_def| -> Error {
|
||||
AggregateExpressionError::AggregateOperandFunctionDuplicated {
|
||||
name: aggregate_expression_name.clone(),
|
||||
function_name: agg_fn_def.name.clone(),
|
||||
}
|
||||
.into()
|
||||
})?;
|
||||
|
||||
// Resolve the aggregation functions
|
||||
let aggregation_functions = scalar_operand
|
||||
.aggregation_functions
|
||||
.iter()
|
||||
.map(|agg_fn_def| {
|
||||
resolve_aggregation_function(
|
||||
agg_fn_def,
|
||||
aggregate_expression_name,
|
||||
scalar_types,
|
||||
object_types,
|
||||
scalar_operand,
|
||||
data_connectors,
|
||||
data_connector_scalars,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
// Check that none of the data connector mappings references an aggregation function that does not exist
|
||||
|
||||
Ok(AggregateOperand {
|
||||
aggregated_type: operand_scalar_type,
|
||||
aggregatable_fields: vec![], // Scalar types don't have fields to aggregate
|
||||
aggregation_functions,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_aggregation_function(
|
||||
aggregation_function_def: &open_dds::aggregates::AggregationFunctionDefinition,
|
||||
aggregate_expression_name: &Qualified<AggregateExpressionName>,
|
||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
scalar_operand: &open_dds::aggregates::ScalarAggregateOperand,
|
||||
data_connectors: &data_connectors::DataConnectors,
|
||||
data_connector_scalars: &BTreeMap<
|
||||
Qualified<DataConnectorName>,
|
||||
ScalarTypeWithRepresentationInfoMap,
|
||||
>,
|
||||
) -> Result<AggregationFunctionInfo, Error> {
|
||||
let return_type = mk_qualified_type_reference(
|
||||
&aggregation_function_def.return_type,
|
||||
&aggregate_expression_name.subgraph,
|
||||
);
|
||||
let return_type_name = unwrap_qualified_type_name(&return_type);
|
||||
|
||||
// Check that the return type actually exists (only if it is a custom type, built-in ones obviously exist)
|
||||
if let QualifiedTypeName::Custom(custom_type_name) = return_type_name {
|
||||
if !scalar_types.contains_key(custom_type_name)
|
||||
&& !object_types.contains_key(custom_type_name)
|
||||
{
|
||||
return Err(
|
||||
AggregateExpressionError::AggregateOperandFunctionUnknownReturnType {
|
||||
name: aggregate_expression_name.clone(),
|
||||
function_name: aggregation_function_def.name.clone(),
|
||||
type_name: custom_type_name.name.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve mappings to all specified data connector functions
|
||||
let data_connector_functions = scalar_operand
|
||||
.data_connector_aggregation_function_mapping
|
||||
.iter()
|
||||
.map(|data_connector_fn_mappings| {
|
||||
// Check if the data connector exists
|
||||
let data_connector_name = qualify(&data_connector_fn_mappings.data_connector_name, &aggregate_expression_name.subgraph);
|
||||
let scalars = data_connector_scalars.get(&data_connector_name)
|
||||
.ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::AggregateOperandDataConnectorMissing {
|
||||
name: aggregate_expression_name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
}.into()
|
||||
})?;
|
||||
|
||||
// Check if the data connector supports aggregations
|
||||
let data_connector = data_connectors.0.get(&data_connector_name)
|
||||
.ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::AggregateOperandDataConnectorMissing {
|
||||
name: aggregate_expression_name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
}.into()
|
||||
})?;
|
||||
if data_connector.inner.capabilities.capabilities.query.aggregates.is_none() {
|
||||
return Err(AggregateExpressionError::AggregateOperandDataConnectorNotSupported {
|
||||
name: aggregate_expression_name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
}.into())
|
||||
}
|
||||
|
||||
// Make sure there is a mapping for this aggregation function
|
||||
let fn_mapping = data_connector_fn_mappings.function_mapping.get(&aggregation_function_def.name)
|
||||
.ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::AggregateOperandDataConnectorFunctionMappingMissing {
|
||||
name: aggregate_expression_name.clone(),
|
||||
function_name: aggregation_function_def.name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
}.into()
|
||||
})?;
|
||||
|
||||
// Check that the data connector operand scalar type actually exists on the data connector
|
||||
let data_connector_scalar_type = scalars.0.get(&data_connector_fn_mappings.data_connector_scalar_type)
|
||||
.ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::AggregateOperandDataConnectorFunctionUnknownScalarType {
|
||||
name: aggregate_expression_name.clone(),
|
||||
function_name: aggregation_function_def.name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
scalar_type: data_connector_fn_mappings.data_connector_scalar_type.clone(),
|
||||
}.into()
|
||||
})?;
|
||||
|
||||
// Check that the mapped data connector aggregate function actually exists
|
||||
let data_connector_fn = data_connector_scalar_type.aggregate_functions.get(&fn_mapping.name.0)
|
||||
.ok_or_else(|| -> Error {
|
||||
AggregateExpressionError::AggregateOperandDataConnectorFunctionNotFound {
|
||||
name: aggregate_expression_name.clone(),
|
||||
function_name: aggregation_function_def.name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
data_connector_aggregate_function_name: fn_mapping.name.0.clone(),
|
||||
}.into()
|
||||
})?;
|
||||
|
||||
check_aggregation_function_return_type(
|
||||
&return_type,
|
||||
&data_connector_fn.result_type,
|
||||
aggregate_expression_name,
|
||||
&aggregation_function_def.name,
|
||||
&data_connector_name,
|
||||
scalars,
|
||||
scalar_types,
|
||||
object_types,
|
||||
)?;
|
||||
|
||||
let function_info = DataConnectorAggregationFunctionInfo {
|
||||
data_connector_name,
|
||||
function_name: fn_mapping.name.clone(),
|
||||
operand_scalar_type: data_connector_fn_mappings.data_connector_scalar_type.clone(),
|
||||
};
|
||||
Ok(function_info)
|
||||
})
|
||||
.collect::<Result<Vec<DataConnectorAggregationFunctionInfo>, Error>>()?;
|
||||
|
||||
Ok(AggregationFunctionInfo {
|
||||
name: aggregation_function_def.name.clone(),
|
||||
description: aggregation_function_def.description.clone(),
|
||||
return_type,
|
||||
data_connector_functions,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_aggregation_function_return_type(
|
||||
return_type: &QualifiedTypeReference,
|
||||
data_connector_return_type: &ndc_models::Type,
|
||||
aggregate_expression_name: &Qualified<AggregateExpressionName>,
|
||||
aggregation_function_name: &AggregationFunctionName,
|
||||
data_connector_name: &Qualified<DataConnectorName>,
|
||||
data_connector_scalars: &ScalarTypeWithRepresentationInfoMap,
|
||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
) -> Result<(), Error> {
|
||||
let mk_error = |reason: &str| -> Error {
|
||||
AggregateExpressionError::AggregateOperandDataConnectorFunctionReturnTypeIncompatible {
|
||||
name: aggregate_expression_name.clone(),
|
||||
function_name: aggregation_function_name.clone(),
|
||||
return_type: return_type.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
reason: reason.to_owned(),
|
||||
}
|
||||
.into()
|
||||
};
|
||||
|
||||
// Unwrap and check all nullablity and array types
|
||||
let (named_return_type, ndc_named_return_type) = unwrap_aggregation_function_return_type(
|
||||
return_type,
|
||||
data_connector_return_type,
|
||||
aggregate_expression_name,
|
||||
aggregation_function_name,
|
||||
data_connector_name,
|
||||
)?;
|
||||
|
||||
let validate_scalar_representation = || -> Result<(), Error> {
|
||||
let type_name = data_connector_scalars.0.get(&DataConnectorScalarType(ndc_named_return_type.clone()))
|
||||
.ok_or_else(||
|
||||
mk_error(format!("The data connector's return type ({0}) isn't a scalar type", ndc_named_return_type).as_str())
|
||||
)?
|
||||
.representation
|
||||
.as_ref()
|
||||
.ok_or_else(||
|
||||
mk_error(format!("The data connector's return scalar type ({0}) doesn't have a type representation", ndc_named_return_type).as_str())
|
||||
)?;
|
||||
let ndc_qualified_type_name =
|
||||
mk_qualified_type_name(type_name, &aggregate_expression_name.subgraph);
|
||||
if ndc_qualified_type_name != *named_return_type {
|
||||
return Err(mk_error(format!("The data connector's return scalar type representation ({0}) does not match the Open DD return type", ndc_qualified_type_name).as_str()));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
match named_return_type {
|
||||
// If the Open DD type is an inbuilt scalar, check that the NDC type maps to that scalar
|
||||
QualifiedTypeName::Inbuilt(..) => validate_scalar_representation(),
|
||||
QualifiedTypeName::Custom(custom_type_name) => {
|
||||
// If the Open DD type is a custom scalar, check that the NDC type maps to that custom scalar
|
||||
if scalar_types.contains_key(custom_type_name) {
|
||||
validate_scalar_representation()
|
||||
}
|
||||
// If the Open DD type is an object type, check that there is a mapping from that object type to the NDC object type
|
||||
else {
|
||||
let return_object_type = object_types.get(custom_type_name).ok_or_else(|| {
|
||||
mk_error("The Open DD return type is not a scalar type or an object type")
|
||||
})?;
|
||||
let ndc_object_type_name = DataConnectorObjectType(ndc_named_return_type.clone());
|
||||
return_object_type.type_mappings.get(data_connector_name, &ndc_object_type_name)
|
||||
.ok_or_else(||
|
||||
mk_error(format!("There is no type mapping defined from the Open DD return object type to the data connector's object type '{0}'", ndc_named_return_type).as_str())
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_aggregation_function_return_type<'a>(
|
||||
return_type: &'a QualifiedTypeReference,
|
||||
data_connector_return_type: &'a ndc_models::Type,
|
||||
aggregate_expression_name: &'a Qualified<AggregateExpressionName>,
|
||||
aggregation_function_name: &'a AggregationFunctionName,
|
||||
data_connector_name: &'a Qualified<DataConnectorName>,
|
||||
) -> Result<(&'a QualifiedTypeName, &'a String), Error> {
|
||||
let mk_error = |reason: &str| -> Error {
|
||||
AggregateExpressionError::AggregateOperandDataConnectorFunctionReturnTypeIncompatible {
|
||||
name: aggregate_expression_name.clone(),
|
||||
function_name: aggregation_function_name.clone(),
|
||||
return_type: return_type.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
reason: reason.to_owned(),
|
||||
}
|
||||
.into()
|
||||
};
|
||||
|
||||
// Check that the Open DD type is nullable if the NDC return type is nullable
|
||||
let ndc_nullable_unwrapped = match data_connector_return_type {
|
||||
ndc_models::Type::Nullable { underlying_type } => {
|
||||
if return_type.nullable {
|
||||
Ok(underlying_type.as_ref())
|
||||
} else {
|
||||
Err(mk_error("The data connector's return type is nullable, but the Open DD return type is not"))
|
||||
}
|
||||
}
|
||||
other => Ok(other),
|
||||
}?;
|
||||
|
||||
match &return_type.underlying_type {
|
||||
// Ensure if the Open DD type is a named type, the NDC type is also a named type
|
||||
QualifiedBaseType::Named(named_type) => {
|
||||
match ndc_nullable_unwrapped {
|
||||
ndc_models::Type::Named { name } => Ok((named_type, name)),
|
||||
ndc_models::Type::Nullable { .. } => Err(mk_error("The data connector's return type is doubly-nullable")), // This shouldn't happen, would be an invalid NDC type
|
||||
ndc_models::Type::Array { .. } => Err(mk_error("The data connector's return type is an array, but the Open DD return type is not")),
|
||||
ndc_models::Type::Predicate { .. } => Err(mk_error("The data connector's return type is a predicate type, which is unsupported in aggregation return types")),
|
||||
}
|
||||
}
|
||||
// Ensure if the Open DD type is a list, the NDC type is an array, and then recur to unwrap and check the element type
|
||||
QualifiedBaseType::List(type_reference) => {
|
||||
match ndc_nullable_unwrapped {
|
||||
ndc_models::Type::Named { name } => Err(mk_error(format!("The data connector's return type is the named type '{0}', but the Open DD return type is an array", name).as_str())),
|
||||
ndc_models::Type::Nullable { .. } => Err(mk_error("The data connector's return type was doubly-nullable")), // This shouldn't happen, would be an invalid NDC type
|
||||
ndc_models::Type::Array { element_type } => unwrap_aggregation_function_return_type(type_reference, element_type, aggregate_expression_name, aggregation_function_name, data_connector_name),
|
||||
ndc_models::Type::Predicate { .. } => Err(mk_error("The data connector's return type is a predicate type, which is unsupported in aggregation return types")),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_underlying_aggregatable_type(
|
||||
type_reference: &QualifiedTypeReference,
|
||||
) -> Option<&QualifiedTypeName> {
|
||||
match &type_reference.underlying_type {
|
||||
QualifiedBaseType::Named(type_name) => Some(type_name),
|
||||
QualifiedBaseType::List(type_reference) => match &type_reference.underlying_type {
|
||||
QualifiedBaseType::Named(type_name) => Some(type_name),
|
||||
QualifiedBaseType::List(_) => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes something that can be turned into an Iterator and then uses the `select_key` function
|
||||
/// to extract a key value for each item in the iterable. It then detects if any these keys are
|
||||
/// duplicated. If so, the first duplicate value is returned as the `Err` value.
|
||||
fn check_for_duplicates<'a, TItem, TIter, TKey, FSelectKey>(
|
||||
items: TIter,
|
||||
select_key: FSelectKey,
|
||||
) -> Result<(), &'a TItem>
|
||||
where
|
||||
TIter: IntoIterator<Item = &'a TItem>,
|
||||
TKey: Eq + Hash,
|
||||
FSelectKey: Fn(&TItem) -> &TKey,
|
||||
{
|
||||
let mut used_items = HashSet::<&TKey>::new();
|
||||
for item in items {
|
||||
if !used_items.insert(select_key(item)) {
|
||||
return Err(item);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_aggregate_expression_graphql_config(
|
||||
existing_graphql_types: &mut BTreeSet<ast::TypeName>,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
aggregate_expression_name: &Qualified<AggregateExpressionName>,
|
||||
aggregate_operand: &AggregateOperand,
|
||||
aggregate_expression_graphql_definition: &Option<
|
||||
open_dds::aggregates::AggregateExpressionGraphQlDefinition,
|
||||
>,
|
||||
) -> Result<Option<AggregateExpressionGraphqlConfig>, Error> {
|
||||
let select_type_name = aggregate_expression_graphql_definition
|
||||
.as_ref()
|
||||
.map(|def| mk_name(def.select_type_name.0.as_ref()).map(ast::TypeName))
|
||||
.transpose()?;
|
||||
|
||||
store_new_graphql_type(existing_graphql_types, select_type_name.as_ref())?;
|
||||
|
||||
let graphql_config = select_type_name
|
||||
.map(|select_type_name| -> Result<_, Error> {
|
||||
// Check that the aggregate config is configured in graphql config
|
||||
let aggregate_config = graphql_config
|
||||
.query
|
||||
.aggregate_config
|
||||
.as_ref()
|
||||
.ok_or_else::<Error, _>(|| {
|
||||
AggregateExpressionError::ConfigMissingFromGraphQlConfig {
|
||||
name: aggregate_expression_name.clone(),
|
||||
config_name: "query.aggregate".to_string(),
|
||||
}
|
||||
.into()
|
||||
})?;
|
||||
|
||||
// Check that no aggregatable field conflicts with the _count field
|
||||
if let Some(field) = aggregate_operand.aggregatable_fields.iter().find(|field| {
|
||||
field.field_name.0.as_str() == aggregate_config.count_field_name.as_str()
|
||||
}) {
|
||||
return Err(AggregateExpressionError::AggregatableFieldNameConflict {
|
||||
name: aggregate_expression_name.clone(),
|
||||
config_name: "query.aggregate.countFieldName".to_string(),
|
||||
aggregatable_field_name: field.field_name.clone(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// Check that no aggregation function conflicts with the _count field
|
||||
if let Some(function) =
|
||||
aggregate_operand
|
||||
.aggregation_functions
|
||||
.iter()
|
||||
.find(|function| {
|
||||
function.name.0.as_str() == aggregate_config.count_field_name.as_str()
|
||||
})
|
||||
{
|
||||
return Err(AggregateExpressionError::AggregationFunctionNameConflict {
|
||||
name: aggregate_expression_name.clone(),
|
||||
config_name: "query.aggregate.countFieldName".to_string(),
|
||||
function_name: function.name.clone(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// Check that no aggregatable field conflicts with the _count_distinct field
|
||||
if let Some(field) = aggregate_operand.aggregatable_fields.iter().find(|field| {
|
||||
field.field_name.0.as_str() == aggregate_config.count_distinct_field_name.as_str()
|
||||
}) {
|
||||
return Err(AggregateExpressionError::AggregatableFieldNameConflict {
|
||||
name: aggregate_expression_name.clone(),
|
||||
config_name: "query.aggregate.countDistinctFieldName".to_string(),
|
||||
aggregatable_field_name: field.field_name.clone(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// Check that no aggregation function conflicts with the _count_distinct field
|
||||
if let Some(function) =
|
||||
aggregate_operand
|
||||
.aggregation_functions
|
||||
.iter()
|
||||
.find(|function| {
|
||||
function.name.0.as_str()
|
||||
== aggregate_config.count_distinct_field_name.as_str()
|
||||
})
|
||||
{
|
||||
return Err(AggregateExpressionError::AggregationFunctionNameConflict {
|
||||
name: aggregate_expression_name.clone(),
|
||||
config_name: "query.aggregate.countDistinctFieldName".to_string(),
|
||||
function_name: function.name.clone(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(AggregateExpressionGraphqlConfig {
|
||||
select_output_type_name: select_type_name,
|
||||
count_field_name: aggregate_config.count_field_name.clone(),
|
||||
count_distinct_field_name: aggregate_config.count_distinct_field_name.clone(),
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(graphql_config)
|
||||
}
|
||||
|
||||
fn resolve_aggregate_count(
|
||||
aggregate_count_definition: &Option<open_dds::aggregates::AggregateCountDefinition>,
|
||||
) -> AggregateCountDefinition {
|
||||
if let Some(aggregate_count_definition) = aggregate_count_definition {
|
||||
AggregateCountDefinition {
|
||||
enable: aggregate_count_definition.enable,
|
||||
description: aggregate_count_definition.description.clone(),
|
||||
}
|
||||
} else {
|
||||
AggregateCountDefinition {
|
||||
enable: false,
|
||||
description: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn qualify<T: std::fmt::Display + std::clone::Clone>(item: &T, subgraph: &str) -> Qualified<T> {
|
||||
Qualified::new(subgraph.to_owned(), item.clone())
|
||||
}
|
215
v3/crates/metadata-resolve/src/stages/aggregates/types.rs
Normal file
215
v3/crates/metadata-resolve/src/stages/aggregates/types.rs
Normal file
@ -0,0 +1,215 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use thiserror::Error;
|
||||
|
||||
use open_dds::{
|
||||
aggregates::{
|
||||
AggregateExpressionName, AggregationFunctionName, DataConnectorAggregationFunctionName,
|
||||
},
|
||||
data_connector::{DataConnectorName, DataConnectorScalarType},
|
||||
types::{CustomTypeName, FieldName},
|
||||
};
|
||||
|
||||
use lang_graphql::ast::common::{self as ast};
|
||||
|
||||
use crate::{Qualified, QualifiedTypeName, QualifiedTypeReference};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AggregateExpressionsOutput {
|
||||
pub aggregate_expressions: BTreeMap<Qualified<AggregateExpressionName>, AggregateExpression>,
|
||||
pub graphql_types: BTreeSet<ast::TypeName>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AggregateExpression {
|
||||
pub name: Qualified<AggregateExpressionName>,
|
||||
pub operand: AggregateOperand,
|
||||
pub count: AggregateCountDefinition,
|
||||
pub count_distinct: AggregateCountDefinition,
|
||||
pub graphql: Option<AggregateExpressionGraphqlConfig>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AggregateCountDefinition {
|
||||
pub enable: bool,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AggregateOperand {
|
||||
pub aggregated_type: QualifiedTypeName,
|
||||
pub aggregatable_fields: Vec<AggregatableFieldInfo>,
|
||||
pub aggregation_functions: Vec<AggregationFunctionInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AggregatableFieldInfo {
|
||||
pub field_name: FieldName,
|
||||
pub description: Option<String>,
|
||||
pub aggregate_expression: Qualified<AggregateExpressionName>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AggregationFunctionInfo {
|
||||
pub name: AggregationFunctionName,
|
||||
pub description: Option<String>,
|
||||
pub return_type: QualifiedTypeReference,
|
||||
pub data_connector_functions: Vec<DataConnectorAggregationFunctionInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct DataConnectorAggregationFunctionInfo {
|
||||
pub data_connector_name: Qualified<DataConnectorName>,
|
||||
pub function_name: DataConnectorAggregationFunctionName,
|
||||
pub operand_scalar_type: DataConnectorScalarType,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AggregateExpressionGraphqlConfig {
|
||||
pub count_field_name: ast::Name,
|
||||
pub count_distinct_field_name: ast::Name,
|
||||
|
||||
pub select_output_type_name: ast::TypeName,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AggregateExpressionError {
|
||||
#[error("the following aggregate expression is defined more than once: {name}")]
|
||||
DuplicateAggregateExpressionDefinition {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} defines a graphql section and so {config_name} must be set in the GraphqlConfig")]
|
||||
ConfigMissingFromGraphQlConfig {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
config_name: String,
|
||||
},
|
||||
|
||||
#[error("the name used by {config_name} from the GraphqlConfig conflicts with the aggregatable field name {aggregatable_field_name} in the aggregate expression {name}")]
|
||||
AggregatableFieldNameConflict {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
config_name: String,
|
||||
aggregatable_field_name: FieldName,
|
||||
},
|
||||
|
||||
#[error("the name used by {config_name} from the GraphqlConfig conflicts with the aggregation function name {function_name} in the aggregate expression {name}")]
|
||||
AggregationFunctionNameConflict {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
config_name: String,
|
||||
function_name: AggregationFunctionName,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an operand object type that cannot be found: {type_name}")]
|
||||
AggregateOperandObjectTypeNotFound {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} has duplicate definitions of the aggregatable field '{field_name}'")]
|
||||
AggregateOperandObjectFieldDuplicated {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
field_name: FieldName,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregatable field '{field_name}' that does not exist on its operand type {operand_type}")]
|
||||
AggregateOperandObjectFieldNotFound {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
operand_type: Qualified<CustomTypeName>,
|
||||
field_name: FieldName,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregatable field '{field_name}' that references an aggregate expression that cannot be found: {field_aggregate_expression}")]
|
||||
AggregateOperandObjectFieldAggregateExpressionNotFound {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
field_name: FieldName,
|
||||
field_aggregate_expression: Qualified<AggregateExpressionName>,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregatable field '{field_name}' of type {field_type}, however arrays of arrays are not supported for aggregation")]
|
||||
MultipleNestedArrayAggregationNotSupported {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
field_name: FieldName,
|
||||
field_type: QualifiedTypeReference,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregatable field '{field_name}' of type {field_type}, however the aggregation expression used to aggregate that field ({field_aggregate_exp_name}) is for aggregating a different type: {field_aggregate_exp_operand_type}")]
|
||||
AggregateOperandObjectFieldTypeMismatch {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
operand_type: Qualified<CustomTypeName>,
|
||||
field_name: FieldName,
|
||||
field_type: QualifiedTypeReference,
|
||||
field_aggregate_exp_name: Qualified<AggregateExpressionName>,
|
||||
field_aggregate_exp_operand_type: QualifiedTypeName,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an operand scalar type that cannot be found: {type_name}")]
|
||||
AggregateOperandScalarTypeNotFound {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} has duplicate definitions of the aggregation function '{function_name}'")]
|
||||
AggregateOperandFunctionDuplicated {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
function_name: AggregationFunctionName,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregation function '{function_name}' that uses an unknown type for its return type: {type_name}")]
|
||||
AggregateOperandFunctionUnknownReturnType {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
function_name: AggregationFunctionName,
|
||||
type_name: CustomTypeName,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} defines an aggregation function mapping to an unknown data connector: {data_connector_name}")]
|
||||
AggregateOperandDataConnectorMissing {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} defines an aggregation function mapping to a data connector that does not support aggregates: {data_connector_name}")]
|
||||
AggregateOperandDataConnectorNotSupported {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregation function '{function_name}' but there is no mapping defined to an aggregation function in the data connector '{data_connector_name}'")]
|
||||
AggregateOperandDataConnectorFunctionMappingMissing {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
function_name: AggregationFunctionName,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregation function '{function_name}' but the mapping to the data connector '{data_connector_name}' specifies a data connector scalar type that does not exist: {scalar_type}")]
|
||||
AggregateOperandDataConnectorFunctionUnknownScalarType {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
function_name: AggregationFunctionName,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
scalar_type: DataConnectorScalarType,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregation function '{function_name}' which is mapped to the data connector '{data_connector_name}', however the mapped data connector aggregate function cannot be found: {data_connector_aggregate_function_name}")]
|
||||
AggregateOperandDataConnectorFunctionNotFound {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
function_name: AggregationFunctionName,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
data_connector_aggregate_function_name: String,
|
||||
},
|
||||
|
||||
#[error("the aggregate expression {name} specifies an aggregation function '{function_name}' which is mapped to the data connector '{data_connector_name}' but the Open DD return type {return_type} is not compatible with the data connector's return type. Reason: {reason}")]
|
||||
AggregateOperandDataConnectorFunctionReturnTypeIncompatible {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
function_name: AggregationFunctionName,
|
||||
return_type: QualifiedTypeReference,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
reason: String,
|
||||
},
|
||||
|
||||
#[error("the data connector {data_connector_name} does not support aggregates over nested object fields, such as the field {field_name} used in aggregate expression {name}")]
|
||||
NestedObjectAggregatesNotSupportedByDataConnector {
|
||||
name: Qualified<AggregateExpressionName>,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
field_name: FieldName,
|
||||
},
|
||||
}
|
@ -127,6 +127,7 @@ fn convert_data_connectors_contexts<'a>(
|
||||
comparison_expression_name: None,
|
||||
comparison_operators: scalar.comparison_operators.clone(),
|
||||
representation: None,
|
||||
aggregate_functions: scalar.aggregate_functions,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ pub struct ScalarTypeWithRepresentationInfo<'a> {
|
||||
pub representation: Option<TypeName>,
|
||||
pub comparison_expression_name: Option<ast::TypeName>,
|
||||
pub comparison_operators: data_connectors::ComparisonOperators,
|
||||
pub aggregate_functions: &'a BTreeMap<String, ndc_models::AggregateFunctionDefinition>,
|
||||
}
|
||||
|
||||
pub struct ScalarTypeWithRepresentationInfoMap<'a>(
|
||||
|
@ -4,8 +4,8 @@ use crate::types::subgraph::Qualified;
|
||||
mod types;
|
||||
use std::collections::BTreeMap;
|
||||
pub use types::{
|
||||
ArgumentPreset, ComparisonOperators, DataConnectorContext, DataConnectorLink,
|
||||
DataConnectorSchema, DataConnectors,
|
||||
ArgumentPreset, ComparisonOperators, DataConnectorCapabilities, DataConnectorContext,
|
||||
DataConnectorLink, DataConnectorSchema, DataConnectors,
|
||||
};
|
||||
|
||||
/// Resolve data connectors.
|
||||
|
@ -168,6 +168,7 @@ impl DataConnectorSchema {
|
||||
pub struct ScalarTypeInfo<'a> {
|
||||
pub scalar_type: &'a ndc_models::ScalarType,
|
||||
pub comparison_operators: ComparisonOperators,
|
||||
pub aggregate_functions: &'a BTreeMap<String, ndc_models::AggregateFunctionDefinition>,
|
||||
}
|
||||
|
||||
impl<'a> ScalarTypeInfo<'a> {
|
||||
@ -191,6 +192,7 @@ impl<'a> ScalarTypeInfo<'a> {
|
||||
ScalarTypeInfo {
|
||||
scalar_type: source_scalar,
|
||||
comparison_operators,
|
||||
aggregate_functions: &source_scalar.aggregate_functions,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -277,6 +279,13 @@ impl DataConnectorLink {
|
||||
.mutation
|
||||
.explain
|
||||
.is_some(),
|
||||
supports_nested_object_aggregations: info
|
||||
.capabilities
|
||||
.capabilities
|
||||
.query
|
||||
.nested_fields
|
||||
.aggregates
|
||||
.is_some(),
|
||||
};
|
||||
Ok(Self {
|
||||
name,
|
||||
@ -430,6 +439,7 @@ impl ResponseHeaders {
|
||||
pub struct DataConnectorCapabilities {
|
||||
pub supports_explaining_queries: bool,
|
||||
pub supports_explaining_mutations: bool,
|
||||
pub supports_nested_object_aggregations: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -124,6 +124,7 @@ pub struct QueryGraphqlConfig {
|
||||
pub offset_field_name: Option<ast::Name>,
|
||||
pub filter_input_config: Option<FilterInputGraphqlConfig>,
|
||||
pub order_by_field_name: Option<ast::Name>,
|
||||
pub aggregate_config: Option<AggregateGraphqlConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
@ -141,6 +142,13 @@ pub struct FilterInputOperatorNames {
|
||||
pub is_null: ast::Name,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct AggregateGraphqlConfig {
|
||||
pub filter_input_field_name: ast::Name,
|
||||
pub count_field_name: ast::Name,
|
||||
pub count_distinct_field_name: ast::Name,
|
||||
}
|
||||
|
||||
/// Resolve and validate the GraphQL configuration.
|
||||
/// For example, make sure all names are valid GraphQL names.
|
||||
pub fn resolve_graphql_config(
|
||||
@ -256,6 +264,23 @@ pub fn resolve_graphql_config(
|
||||
}
|
||||
};
|
||||
|
||||
let aggregate_config = graphql_config_metadata
|
||||
.query
|
||||
.aggregate
|
||||
.as_ref()
|
||||
.map(|aggregate_config| -> Result<_, Error> {
|
||||
Ok(AggregateGraphqlConfig {
|
||||
filter_input_field_name: mk_name(
|
||||
aggregate_config.filter_input_field_name.0.as_str(),
|
||||
)?,
|
||||
count_field_name: mk_name(aggregate_config.count_field_name.0.as_str())?,
|
||||
count_distinct_field_name: mk_name(
|
||||
aggregate_config.count_distinct_field_name.0.as_str(),
|
||||
)?,
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let enable_apollo_federation_fields = graphql_config_metadata
|
||||
.apollo_federation
|
||||
.as_ref()
|
||||
@ -268,6 +293,7 @@ pub fn resolve_graphql_config(
|
||||
offset_field_name,
|
||||
filter_input_config,
|
||||
order_by_field_name,
|
||||
aggregate_config,
|
||||
},
|
||||
global: GlobalGraphqlConfig {
|
||||
query_root_type_name,
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod aggregates;
|
||||
mod apollo;
|
||||
pub mod boolean_expressions;
|
||||
pub mod command_permissions;
|
||||
@ -60,6 +61,19 @@ pub fn resolve(
|
||||
let object_types_with_permissions =
|
||||
type_permissions::resolve(&metadata_accessor, &object_types)?;
|
||||
|
||||
let aggregates::AggregateExpressionsOutput {
|
||||
aggregate_expressions,
|
||||
graphql_types,
|
||||
} = aggregates::resolve(
|
||||
&metadata_accessor,
|
||||
&data_connectors,
|
||||
&data_connector_scalars,
|
||||
&object_types_with_permissions,
|
||||
&scalar_types,
|
||||
graphql_types,
|
||||
&graphql_config,
|
||||
)?;
|
||||
|
||||
let object_boolean_expressions::ObjectBooleanExpressionsOutput {
|
||||
object_boolean_expression_types,
|
||||
graphql_types,
|
||||
@ -98,6 +112,7 @@ pub fn resolve(
|
||||
&apollo_federation_entity_enabled_types,
|
||||
&object_types_with_permissions,
|
||||
&scalar_types,
|
||||
&aggregate_expressions,
|
||||
&object_boolean_expression_types,
|
||||
&boolean_expression_types,
|
||||
&graphql_config,
|
||||
@ -159,6 +174,7 @@ pub fn resolve(
|
||||
commands: commands_with_permissions,
|
||||
object_boolean_expression_types,
|
||||
boolean_expression_types,
|
||||
aggregate_expressions,
|
||||
graphql_config: graphql_config.global,
|
||||
roles,
|
||||
})
|
||||
|
@ -1,9 +1,13 @@
|
||||
use open_dds::data_connector::{DataConnectorName, DataConnectorObjectType};
|
||||
use open_dds::aggregates::AggregateExpressionName;
|
||||
use open_dds::data_connector::{
|
||||
DataConnectorName, DataConnectorObjectType, DataConnectorScalarType,
|
||||
};
|
||||
pub use types::{
|
||||
ConnectorArgumentName, LimitFieldGraphqlConfig, Model, ModelExpressionType, ModelGraphQlApi,
|
||||
ModelGraphqlApiArgumentsConfig, ModelOrderByExpression, ModelSource, ModelsOutput,
|
||||
NDCFieldSourceMapping, OffsetFieldGraphqlConfig, OrderByExpressionInfo,
|
||||
SelectManyGraphQlDefinition, SelectUniqueGraphQlDefinition, UniqueIdentifierField,
|
||||
SelectAggregateGraphQlDefinition, SelectManyGraphQlDefinition, SelectUniqueGraphQlDefinition,
|
||||
UniqueIdentifierField,
|
||||
};
|
||||
mod types;
|
||||
|
||||
@ -15,14 +19,15 @@ use crate::helpers::type_mappings;
|
||||
use crate::helpers::types::NdcColumnForComparison;
|
||||
use crate::helpers::types::{mk_name, store_new_graphql_type};
|
||||
use crate::stages::{
|
||||
boolean_expressions, data_connector_scalar_types, data_connectors, graphql_config,
|
||||
aggregates, boolean_expressions, data_connector_scalar_types, data_connectors, graphql_config,
|
||||
object_boolean_expressions, object_types, scalar_types, type_permissions,
|
||||
};
|
||||
use crate::types::subgraph::{mk_qualified_type_reference, ArgumentInfo, Qualified};
|
||||
use crate::types::subgraph::{
|
||||
mk_qualified_type_reference, ArgumentInfo, Qualified, QualifiedTypeName,
|
||||
};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use lang_graphql::ast::common::{self as ast};
|
||||
use ref_cast::RefCast;
|
||||
|
||||
use open_dds::{
|
||||
models::{
|
||||
@ -51,6 +56,10 @@ pub fn resolve(
|
||||
>,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
aggregate_expressions: &BTreeMap<
|
||||
Qualified<AggregateExpressionName>,
|
||||
aggregates::AggregateExpression,
|
||||
>,
|
||||
object_boolean_expression_types: &BTreeMap<
|
||||
Qualified<CustomTypeName>,
|
||||
object_boolean_expressions::ObjectBooleanExpressionType,
|
||||
@ -71,6 +80,7 @@ pub fn resolve(
|
||||
object: model,
|
||||
} in &metadata_accessor.models
|
||||
{
|
||||
let qualified_model_name = Qualified::new(subgraph.to_string(), model.name.clone());
|
||||
let mut resolved_model = resolve_model(
|
||||
subgraph,
|
||||
model,
|
||||
@ -108,6 +118,20 @@ pub fn resolve(
|
||||
object_boolean_expression_types,
|
||||
)?;
|
||||
}
|
||||
let qualified_aggregate_expression_name = model
|
||||
.aggregate_expression
|
||||
.as_ref()
|
||||
.map(|aggregate_expression_name| {
|
||||
resolve_aggregate_expression(
|
||||
aggregate_expression_name,
|
||||
&qualified_model_name,
|
||||
&resolved_model.data_type,
|
||||
&resolved_model.source,
|
||||
aggregate_expressions,
|
||||
object_types,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
if let Some(model_graphql_definition) = &model.graphql {
|
||||
resolve_model_graphql_api(
|
||||
model_graphql_definition,
|
||||
@ -115,10 +139,11 @@ pub fn resolve(
|
||||
&mut graphql_types,
|
||||
data_connector_scalars,
|
||||
&model.description,
|
||||
&qualified_aggregate_expression_name,
|
||||
graphql_config,
|
||||
)?;
|
||||
}
|
||||
let qualified_model_name = Qualified::new(subgraph.to_string(), model.name.clone());
|
||||
|
||||
if models
|
||||
.insert(qualified_model_name.clone(), resolved_model)
|
||||
.is_some()
|
||||
@ -136,6 +161,212 @@ pub fn resolve(
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_aggregate_expression(
|
||||
aggregate_expression_name: &AggregateExpressionName,
|
||||
model_name: &Qualified<ModelName>,
|
||||
model_object_type_name: &Qualified<CustomTypeName>,
|
||||
model_source: &Option<ModelSource>,
|
||||
aggregate_expressions: &BTreeMap<
|
||||
Qualified<AggregateExpressionName>,
|
||||
aggregates::AggregateExpression,
|
||||
>,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
) -> Result<Qualified<AggregateExpressionName>, Error> {
|
||||
let qualified_aggregate_expression_name = Qualified::new(
|
||||
model_name.subgraph.clone(),
|
||||
aggregate_expression_name.clone(),
|
||||
);
|
||||
let model_object_type = QualifiedTypeName::Custom(model_object_type_name.clone());
|
||||
|
||||
// Check the model has a source
|
||||
let model_source =
|
||||
model_source
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::CannotUseAggregateExpressionsWithoutSource {
|
||||
model: model_name.clone(),
|
||||
})?;
|
||||
|
||||
// Check that the specified aggregate expression exists
|
||||
let aggregate_expression = aggregate_expressions
|
||||
.get(&qualified_aggregate_expression_name)
|
||||
.ok_or_else(|| Error::UnknownModelAggregateExpression {
|
||||
model_name: model_name.clone(),
|
||||
aggregate_expression: qualified_aggregate_expression_name.clone(),
|
||||
})?;
|
||||
|
||||
// Check that the specified aggregate expression actually aggregates the model's type
|
||||
if model_object_type != aggregate_expression.operand.aggregated_type {
|
||||
return Err(Error::ModelAggregateExpressionOperandTypeMismatch {
|
||||
model_name: model_name.clone(),
|
||||
aggregate_expression: qualified_aggregate_expression_name.clone(),
|
||||
model_type: model_object_type.clone(),
|
||||
aggregate_operand_type: aggregate_expression.operand.aggregated_type.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
// Check aggregate function mappings exist to the Model's source data connector
|
||||
resolve_aggregate_expression_data_connector_mapping(
|
||||
aggregate_expression,
|
||||
model_name,
|
||||
model_object_type_name,
|
||||
&model_source.data_connector.name,
|
||||
&model_source.collection_type,
|
||||
&model_source.data_connector.capabilities,
|
||||
aggregate_expressions,
|
||||
object_types,
|
||||
)?;
|
||||
|
||||
// Check that the aggregate expression does not define count_distinct, as this is
|
||||
// not valid on a model (every object is already "distinct", so it is meaningless)
|
||||
if aggregate_expression.count_distinct.enable {
|
||||
return Err(Error::ModelAggregateExpressionCountDistinctNotAllowed {
|
||||
model_name: model_name.clone(),
|
||||
aggregate_expression: qualified_aggregate_expression_name.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(qualified_aggregate_expression_name)
|
||||
}
|
||||
|
||||
fn resolve_aggregate_expression_data_connector_mapping(
|
||||
aggregate_expression: &aggregates::AggregateExpression,
|
||||
model_name: &Qualified<ModelName>,
|
||||
object_type_name: &Qualified<CustomTypeName>,
|
||||
data_connector_name: &Qualified<DataConnectorName>,
|
||||
data_connector_object_type: &DataConnectorObjectType,
|
||||
data_connector_capabilities: &data_connectors::DataConnectorCapabilities,
|
||||
aggregate_expressions: &BTreeMap<
|
||||
Qualified<AggregateExpressionName>,
|
||||
aggregates::AggregateExpression,
|
||||
>,
|
||||
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
) -> Result<(), Error> {
|
||||
// Find the object type being aggregated and its field mapping
|
||||
let object_type =
|
||||
object_types
|
||||
.get(object_type_name)
|
||||
.ok_or_else(|| Error::UnknownObjectType {
|
||||
data_type: object_type_name.clone(),
|
||||
})?;
|
||||
let object_type_mapping = object_type
|
||||
.type_mappings
|
||||
.get(data_connector_name, data_connector_object_type)
|
||||
.ok_or_else(|| Error::TypeMappingRequired {
|
||||
model_name: model_name.clone(),
|
||||
type_name: object_type_name.clone(),
|
||||
data_connector: data_connector_name.clone(),
|
||||
})?;
|
||||
let object_type_field_mapping = match object_type_mapping {
|
||||
object_types::TypeMapping::Object { field_mappings, .. } => field_mappings,
|
||||
};
|
||||
|
||||
// Resolve each aggregatable field
|
||||
for aggregatable_field in &aggregate_expression.operand.aggregatable_fields {
|
||||
// Ensure the aggregatable field actually exists in the object type
|
||||
let field_mapping = object_type_field_mapping
|
||||
.get(&aggregatable_field.field_name)
|
||||
.ok_or_else(|| {
|
||||
aggregates::AggregateExpressionError::AggregateOperandObjectFieldNotFound {
|
||||
name: aggregate_expression.name.clone(),
|
||||
operand_type: object_type_name.clone(),
|
||||
field_name: aggregatable_field.field_name.clone(),
|
||||
}
|
||||
})?;
|
||||
|
||||
// Get the underlying data connector type name for the aggregatable field
|
||||
// We only accept named or nullable named types. Array/predicate types are not allowed
|
||||
let data_connector_field_type = match &field_mapping.column_type {
|
||||
ndc_models::Type::Named { name } => Ok(name),
|
||||
ndc_models::Type::Nullable { underlying_type } => match &**underlying_type {
|
||||
ndc_models::Type::Named { name } => Ok(name),
|
||||
_ => Err(Error::ModelAggregateExpressionUnexpectedDataConnectorType {
|
||||
model_name: model_name.clone(),
|
||||
aggregate_expression: aggregate_expression.name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
field_name: aggregatable_field.field_name.clone(),
|
||||
}),
|
||||
},
|
||||
_ => Err(Error::ModelAggregateExpressionUnexpectedDataConnectorType {
|
||||
model_name: model_name.clone(),
|
||||
aggregate_expression: aggregate_expression.name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
field_name: aggregatable_field.field_name.clone(),
|
||||
}),
|
||||
}?;
|
||||
|
||||
// Get the aggregate expression used to aggregate the field's type
|
||||
let field_aggregate_expression = aggregate_expressions
|
||||
.get(&aggregatable_field.aggregate_expression)
|
||||
.ok_or_else(|| Error::UnknownModelAggregateExpression {
|
||||
model_name: model_name.clone(),
|
||||
aggregate_expression: aggregatable_field.aggregate_expression.clone(),
|
||||
})?;
|
||||
|
||||
// Get the field's aggregate expression operand type, if it an object type
|
||||
let field_object_type_name = match &field_aggregate_expression.operand.aggregated_type {
|
||||
QualifiedTypeName::Inbuilt(_) => None,
|
||||
QualifiedTypeName::Custom(custom_type_name) => {
|
||||
if object_types.contains_key(custom_type_name) {
|
||||
Some(custom_type_name)
|
||||
} else {
|
||||
None // Must be a scalar (operands are already validated to be either object or scalar in aggregates resolution)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If our field contains a nested object type
|
||||
if let Some(field_object_type_name) = field_object_type_name {
|
||||
// Check that the data connector supports aggregation over nested object fields
|
||||
if !data_connector_capabilities.supports_nested_object_aggregations {
|
||||
return Err(aggregates::AggregateExpressionError::NestedObjectAggregatesNotSupportedByDataConnector {
|
||||
name: aggregate_expression.name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
field_name: aggregatable_field.field_name.clone(),
|
||||
}.into());
|
||||
}
|
||||
|
||||
// Resolve the aggregate expression for the nested object field type
|
||||
resolve_aggregate_expression_data_connector_mapping(
|
||||
field_aggregate_expression,
|
||||
model_name,
|
||||
field_object_type_name,
|
||||
data_connector_name,
|
||||
&DataConnectorObjectType(data_connector_field_type.clone()),
|
||||
data_connector_capabilities,
|
||||
aggregate_expressions,
|
||||
object_types,
|
||||
)?;
|
||||
}
|
||||
// If our field contains a scalar type
|
||||
else {
|
||||
// Check that all aggregation functions over this scalar type
|
||||
// have a data connector mapping to the data connector used by the model
|
||||
let all_functions_have_a_data_connector_mapping = field_aggregate_expression
|
||||
.operand
|
||||
.aggregation_functions
|
||||
.iter()
|
||||
.all(|agg_fn| {
|
||||
agg_fn.data_connector_functions.iter().any(|dc_fn| {
|
||||
dc_fn.data_connector_name == *data_connector_name
|
||||
&& dc_fn.operand_scalar_type.0 == *data_connector_field_type
|
||||
})
|
||||
});
|
||||
if !all_functions_have_a_data_connector_mapping {
|
||||
return Err(Error::ModelAggregateExpressionDataConnectorMappingMissing {
|
||||
model_name: model_name.clone(),
|
||||
aggregate_expression: field_aggregate_expression.name.clone(),
|
||||
data_connector_name: data_connector_name.clone(),
|
||||
data_connector_operand_type: DataConnectorScalarType(
|
||||
data_connector_field_type.clone(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_filter_expression_type(
|
||||
model: &ModelV1,
|
||||
model_data_type: &Qualified<CustomTypeName>,
|
||||
@ -412,6 +643,7 @@ fn resolve_model_graphql_api(
|
||||
>,
|
||||
|
||||
model_description: &Option<String>,
|
||||
aggregate_expression_name: &Option<Qualified<AggregateExpressionName>>,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
) -> Result<(), Error> {
|
||||
let model_name = &model.name;
|
||||
@ -554,6 +786,58 @@ fn resolve_model_graphql_api(
|
||||
}),
|
||||
}?;
|
||||
|
||||
// record the filter input type name, if set
|
||||
let filter_input_type_name = model_graphql_definition
|
||||
.filter_input_type_name
|
||||
.as_ref()
|
||||
.map(|filter_input_type_name| mk_name(filter_input_type_name.0.as_str()).map(ast::TypeName))
|
||||
.transpose()?;
|
||||
store_new_graphql_type(existing_graphql_types, filter_input_type_name.as_ref())?;
|
||||
model.graphql_api.filter_input_type_name = filter_input_type_name;
|
||||
if aggregate_expression_name.is_none() && model.graphql_api.filter_input_type_name.is_some() {
|
||||
return Err(Error::UnnecessaryFilterInputTypeNameGraphqlConfiguration {
|
||||
model_name: model_name.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
// record select_aggregate root field
|
||||
model.graphql_api.select_aggregate = model_graphql_definition
|
||||
.aggregate
|
||||
.as_ref()
|
||||
.zip(aggregate_expression_name.as_ref()) // Only matters if we have an aggregate expression specified
|
||||
.map(
|
||||
|(graphql_aggregate, aggregate_expression_name)| -> Result<_, Error> {
|
||||
// Check that a filter input type name is configured
|
||||
if model.graphql_api.filter_input_type_name.is_none() {
|
||||
{
|
||||
return Err(Error::MissingFilterInputTypeNameGraphqlConfiguration {
|
||||
model_name: model_name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the filter input field name is configured in graphql config
|
||||
let filter_input_field_name = graphql_config
|
||||
.query
|
||||
.aggregate_config
|
||||
.as_ref()
|
||||
.map(|agg| agg.filter_input_field_name.clone())
|
||||
.ok_or_else::<Error, _>(|| Error::GraphqlConfigError {
|
||||
graphql_config_error:
|
||||
GraphqlConfigError::MissingAggregateFilterInputFieldNameInGraphqlConfig,
|
||||
})?;
|
||||
|
||||
Ok(SelectAggregateGraphQlDefinition {
|
||||
query_root_field: mk_name(&graphql_aggregate.query_root_field.0)?,
|
||||
description: graphql_aggregate.description.clone(),
|
||||
deprecated: graphql_aggregate.deprecated.clone(),
|
||||
aggregate_expression_name: aggregate_expression_name.clone(),
|
||||
filter_input_field_name,
|
||||
})
|
||||
},
|
||||
)
|
||||
.transpose()?;
|
||||
|
||||
// record limit and offset field names
|
||||
model.graphql_api.limit_field =
|
||||
graphql_config
|
||||
@ -649,6 +933,7 @@ fn resolve_model_source(
|
||||
data_connector: qualified_data_connector_name.clone(),
|
||||
collection: model_source.collection.clone(),
|
||||
})?;
|
||||
let source_collection_type = DataConnectorObjectType(source_collection.collection_type.clone());
|
||||
|
||||
let source_arguments = source_collection
|
||||
.clone()
|
||||
@ -677,7 +962,7 @@ fn resolve_model_source(
|
||||
let mut type_mappings = BTreeMap::new();
|
||||
let source_collection_type_mapping_to_collect = type_mappings::TypeMappingToCollect {
|
||||
type_name: &model.data_type,
|
||||
ndc_object_type_name: DataConnectorObjectType::ref_cast(&source_collection.collection_type),
|
||||
ndc_object_type_name: &source_collection_type,
|
||||
};
|
||||
for type_mapping_to_collect in iter::once(&source_collection_type_mapping_to_collect)
|
||||
.chain(argument_type_mappings_to_collect.iter())
|
||||
@ -712,13 +997,10 @@ fn resolve_model_source(
|
||||
});
|
||||
}
|
||||
|
||||
let collection_type =
|
||||
DataConnectorObjectType::ref_cast(&source_collection.collection_type);
|
||||
|
||||
if data_connector.object_type != *collection_type {
|
||||
if data_connector.object_type != source_collection_type {
|
||||
return Err(Error::DifferentDataConnectorObjectTypeInFilterExpression {
|
||||
model: model.name.clone(),
|
||||
model_data_connector_object_type: collection_type.clone(),
|
||||
model_data_connector_object_type: source_collection_type.clone(),
|
||||
filter_expression_type: filter_expression.name.clone(),
|
||||
filter_expression_data_connector_object_type: data_connector
|
||||
.object_type
|
||||
@ -734,6 +1016,7 @@ fn resolve_model_source(
|
||||
&data_connector_context.inner,
|
||||
)?,
|
||||
collection: model_source.collection.clone(),
|
||||
collection_type: source_collection_type,
|
||||
type_mappings,
|
||||
argument_mappings,
|
||||
source_arguments,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user