Skip to content

Commit cf17d0a

Browse files
danieljharveyhasura-bot
authored andcommitted
Tidy models metadata-resolve (#729)
<!-- Thank you for submitting this PR! :) --> ## Description Doing some work on the `models` section and it's a bit big and confusing, so doing a tidy-up to get my head around it all. Splits the large module into separate files (`ordering`, `source`, `graphql`, `filter` etc). Only actual changes are: - Move filter resolving to after model source is checked, so we can refer to it. This will be useful for the thing I actually want to do. - Make steps like `graphql` and `source` return the values they create rather than mutating `Model` directly. - Move some filter checks into `filter` rather than `source`, now we are able to. This logic was all over the place. Functional no-op. V3_GIT_ORIGIN_REV_ID: 540db16d34590ed9e481c36d251ab5e1886f2c81
1 parent e14908e commit cf17d0a

File tree

8 files changed

+958
-900
lines changed

8 files changed

+958
-900
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
use super::types::ModelSource;
2+
use open_dds::aggregates::AggregateExpressionName;
3+
use open_dds::data_connector::{
4+
DataConnectorName, DataConnectorObjectType, DataConnectorScalarType,
5+
};
6+
7+
use crate::types::error::Error;
8+
9+
use crate::stages::{aggregates, data_connectors, object_types, type_permissions};
10+
use crate::types::subgraph::{Qualified, QualifiedTypeName};
11+
12+
use open_dds::{models::ModelName, types::CustomTypeName};
13+
14+
use std::collections::BTreeMap;
15+
16+
pub(crate) fn resolve_aggregate_expression(
17+
aggregate_expression_name: &AggregateExpressionName,
18+
model_name: &Qualified<ModelName>,
19+
model_object_type_name: &Qualified<CustomTypeName>,
20+
model_source: &Option<ModelSource>,
21+
aggregate_expressions: &BTreeMap<
22+
Qualified<AggregateExpressionName>,
23+
aggregates::AggregateExpression,
24+
>,
25+
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
26+
) -> Result<Qualified<AggregateExpressionName>, Error> {
27+
let qualified_aggregate_expression_name = Qualified::new(
28+
model_name.subgraph.clone(),
29+
aggregate_expression_name.clone(),
30+
);
31+
let model_object_type = QualifiedTypeName::Custom(model_object_type_name.clone());
32+
33+
// Check the model has a source
34+
let model_source =
35+
model_source
36+
.as_ref()
37+
.ok_or_else(|| Error::CannotUseAggregateExpressionsWithoutSource {
38+
model: model_name.clone(),
39+
})?;
40+
41+
// Check that the specified aggregate expression exists
42+
let aggregate_expression = aggregate_expressions
43+
.get(&qualified_aggregate_expression_name)
44+
.ok_or_else(|| Error::UnknownModelAggregateExpression {
45+
model_name: model_name.clone(),
46+
aggregate_expression: qualified_aggregate_expression_name.clone(),
47+
})?;
48+
49+
// Check that the specified aggregate expression actually aggregates the model's type
50+
if model_object_type != aggregate_expression.operand.aggregated_type {
51+
return Err(Error::ModelAggregateExpressionOperandTypeMismatch {
52+
model_name: model_name.clone(),
53+
aggregate_expression: qualified_aggregate_expression_name.clone(),
54+
model_type: model_object_type.clone(),
55+
aggregate_operand_type: aggregate_expression.operand.aggregated_type.clone(),
56+
});
57+
}
58+
59+
// Check aggregate function mappings exist to the Model's source data connector
60+
resolve_aggregate_expression_data_connector_mapping(
61+
aggregate_expression,
62+
model_name,
63+
model_object_type_name,
64+
&model_source.data_connector.name,
65+
&model_source.collection_type,
66+
&model_source.data_connector.capabilities,
67+
aggregate_expressions,
68+
object_types,
69+
)?;
70+
71+
// Check that the aggregate expression does not define count_distinct, as this is
72+
// not valid on a model (every object is already "distinct", so it is meaningless)
73+
if aggregate_expression.count_distinct.enable {
74+
return Err(Error::ModelAggregateExpressionCountDistinctNotAllowed {
75+
model_name: model_name.clone(),
76+
aggregate_expression: qualified_aggregate_expression_name.clone(),
77+
});
78+
}
79+
80+
Ok(qualified_aggregate_expression_name)
81+
}
82+
83+
fn resolve_aggregate_expression_data_connector_mapping(
84+
aggregate_expression: &aggregates::AggregateExpression,
85+
model_name: &Qualified<ModelName>,
86+
object_type_name: &Qualified<CustomTypeName>,
87+
data_connector_name: &Qualified<DataConnectorName>,
88+
data_connector_object_type: &DataConnectorObjectType,
89+
data_connector_capabilities: &data_connectors::DataConnectorCapabilities,
90+
aggregate_expressions: &BTreeMap<
91+
Qualified<AggregateExpressionName>,
92+
aggregates::AggregateExpression,
93+
>,
94+
object_types: &BTreeMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
95+
) -> Result<(), Error> {
96+
// Find the object type being aggregated and its field mapping
97+
let object_type =
98+
object_types
99+
.get(object_type_name)
100+
.ok_or_else(|| Error::UnknownObjectType {
101+
data_type: object_type_name.clone(),
102+
})?;
103+
let object_type_mapping = object_type
104+
.type_mappings
105+
.get(data_connector_name, data_connector_object_type)
106+
.ok_or_else(|| Error::TypeMappingRequired {
107+
model_name: model_name.clone(),
108+
type_name: object_type_name.clone(),
109+
data_connector: data_connector_name.clone(),
110+
})?;
111+
let object_type_field_mapping = match object_type_mapping {
112+
object_types::TypeMapping::Object { field_mappings, .. } => field_mappings,
113+
};
114+
115+
// Resolve each aggregatable field
116+
for aggregatable_field in &aggregate_expression.operand.aggregatable_fields {
117+
// Ensure the aggregatable field actually exists in the object type
118+
let field_mapping = object_type_field_mapping
119+
.get(&aggregatable_field.field_name)
120+
.ok_or_else(|| {
121+
aggregates::AggregateExpressionError::AggregateOperandObjectFieldNotFound {
122+
name: aggregate_expression.name.clone(),
123+
operand_type: object_type_name.clone(),
124+
field_name: aggregatable_field.field_name.clone(),
125+
}
126+
})?;
127+
128+
// Get the underlying data connector type name for the aggregatable field
129+
// We only accept named or nullable named types. Array/predicate types are not allowed
130+
let data_connector_field_type = match &field_mapping.column_type {
131+
ndc_models::Type::Named { name } => Ok(name),
132+
ndc_models::Type::Nullable { underlying_type } => match &**underlying_type {
133+
ndc_models::Type::Named { name } => Ok(name),
134+
_ => Err(Error::ModelAggregateExpressionUnexpectedDataConnectorType {
135+
model_name: model_name.clone(),
136+
aggregate_expression: aggregate_expression.name.clone(),
137+
data_connector_name: data_connector_name.clone(),
138+
field_name: aggregatable_field.field_name.clone(),
139+
}),
140+
},
141+
_ => Err(Error::ModelAggregateExpressionUnexpectedDataConnectorType {
142+
model_name: model_name.clone(),
143+
aggregate_expression: aggregate_expression.name.clone(),
144+
data_connector_name: data_connector_name.clone(),
145+
field_name: aggregatable_field.field_name.clone(),
146+
}),
147+
}?;
148+
149+
// Get the aggregate expression used to aggregate the field's type
150+
let field_aggregate_expression = aggregate_expressions
151+
.get(&aggregatable_field.aggregate_expression)
152+
.ok_or_else(|| Error::UnknownModelAggregateExpression {
153+
model_name: model_name.clone(),
154+
aggregate_expression: aggregatable_field.aggregate_expression.clone(),
155+
})?;
156+
157+
// Get the field's aggregate expression operand type, if it an object type
158+
let field_object_type_name = match &field_aggregate_expression.operand.aggregated_type {
159+
QualifiedTypeName::Inbuilt(_) => None,
160+
QualifiedTypeName::Custom(custom_type_name) => {
161+
if object_types.contains_key(custom_type_name) {
162+
Some(custom_type_name)
163+
} else {
164+
None // Must be a scalar (operands are already validated to be either object or scalar in aggregates resolution)
165+
}
166+
}
167+
};
168+
169+
// If our field contains a nested object type
170+
if let Some(field_object_type_name) = field_object_type_name {
171+
// Check that the data connector supports aggregation over nested object fields
172+
if !data_connector_capabilities.supports_nested_object_aggregations {
173+
return Err(aggregates::AggregateExpressionError::NestedObjectAggregatesNotSupportedByDataConnector {
174+
name: aggregate_expression.name.clone(),
175+
data_connector_name: data_connector_name.clone(),
176+
field_name: aggregatable_field.field_name.clone(),
177+
}.into());
178+
}
179+
180+
// Resolve the aggregate expression for the nested object field type
181+
resolve_aggregate_expression_data_connector_mapping(
182+
field_aggregate_expression,
183+
model_name,
184+
field_object_type_name,
185+
data_connector_name,
186+
&DataConnectorObjectType(data_connector_field_type.clone()),
187+
data_connector_capabilities,
188+
aggregate_expressions,
189+
object_types,
190+
)?;
191+
}
192+
// If our field contains a scalar type
193+
else {
194+
// Check that all aggregation functions over this scalar type
195+
// have a data connector mapping to the data connector used by the model
196+
let all_functions_have_a_data_connector_mapping = field_aggregate_expression
197+
.operand
198+
.aggregation_functions
199+
.iter()
200+
.all(|agg_fn| {
201+
agg_fn.data_connector_functions.iter().any(|dc_fn| {
202+
dc_fn.data_connector_name == *data_connector_name
203+
&& dc_fn.operand_scalar_type.0 == *data_connector_field_type
204+
})
205+
});
206+
if !all_functions_have_a_data_connector_mapping {
207+
return Err(Error::ModelAggregateExpressionDataConnectorMappingMissing {
208+
model_name: model_name.clone(),
209+
aggregate_expression: field_aggregate_expression.name.clone(),
210+
data_connector_name: data_connector_name.clone(),
211+
data_connector_operand_type: DataConnectorScalarType(
212+
data_connector_field_type.clone(),
213+
),
214+
});
215+
}
216+
}
217+
}
218+
219+
Ok(())
220+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use super::types::{ModelExpressionType, ModelSource};
2+
3+
use crate::types::error::{BooleanExpressionError, Error};
4+
5+
use crate::stages::{boolean_expressions, object_boolean_expressions};
6+
use crate::types::subgraph::Qualified;
7+
8+
use open_dds::{models::ModelName, types::CustomTypeName};
9+
10+
use std::collections::BTreeMap;
11+
12+
// given a valid source and a filter expression type, try and resolve a predicate type for this
13+
// model
14+
pub(crate) fn resolve_filter_expression_type(
15+
model_name: &Qualified<ModelName>,
16+
model_source: &ModelSource,
17+
model_data_type: &Qualified<CustomTypeName>,
18+
boolean_expression_type_name: &Qualified<CustomTypeName>,
19+
object_boolean_expression_types: &BTreeMap<
20+
Qualified<CustomTypeName>,
21+
object_boolean_expressions::ObjectBooleanExpressionType,
22+
>,
23+
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
24+
) -> Result<ModelExpressionType, Error> {
25+
match object_boolean_expression_types.get(boolean_expression_type_name) {
26+
Some(object_boolean_expression_type) => {
27+
// we're using an old ObjectBooleanExpressionType kind
28+
29+
// check that the model object type and boolean expression object type agree
30+
if object_boolean_expression_type.object_type != *model_data_type {
31+
return Err(Error::from(
32+
BooleanExpressionError::BooleanExpressionTypeForInvalidObjectTypeInModel {
33+
name: boolean_expression_type_name.clone(),
34+
boolean_expression_object_type: object_boolean_expression_type
35+
.object_type
36+
.clone(),
37+
model: model_name.clone(),
38+
model_object_type: model_data_type.clone(),
39+
},
40+
));
41+
}
42+
43+
// The `ObjectBooleanExpressionType` allows specifying Data Connector related information
44+
// there we still check it againt the type specified in the models source.
45+
// The newer `BooleanExpressionType` defers to the model's choice of object by default, so we
46+
// do not need this check there
47+
let data_connector = &object_boolean_expression_type.data_connector;
48+
49+
if data_connector.name != model_source.data_connector.name {
50+
return Err(Error::DifferentDataConnectorInFilterExpression {
51+
model: model_name.clone(),
52+
model_data_connector: model_source.data_connector.name.clone(),
53+
filter_expression_type: object_boolean_expression_type.name.clone(),
54+
filter_expression_data_connector: data_connector.name.clone(),
55+
});
56+
}
57+
58+
if data_connector.object_type != model_source.collection_type {
59+
return Err(Error::DifferentDataConnectorObjectTypeInFilterExpression {
60+
model: model_name.clone(),
61+
model_data_connector_object_type: model_source.collection_type.clone(),
62+
filter_expression_type: boolean_expression_type_name.clone(),
63+
filter_expression_data_connector_object_type: data_connector
64+
.object_type
65+
.clone(),
66+
});
67+
}
68+
69+
Ok(ModelExpressionType::ObjectBooleanExpressionType(
70+
object_boolean_expression_type.clone(),
71+
))
72+
}
73+
None => {
74+
// now we should also check in `BooleanExpressionTypes`, the new kind
75+
match boolean_expression_types
76+
.objects
77+
.get(boolean_expression_type_name)
78+
{
79+
Some(boolean_expression_object_type) => {
80+
// we're using the new style of BooleanExpressionType
81+
82+
// TODO: what checks do we need here?
83+
84+
Ok(ModelExpressionType::BooleanExpressionType(
85+
boolean_expression_object_type.clone(),
86+
))
87+
}
88+
None => Err(Error::from(
89+
BooleanExpressionError::UnknownBooleanExpressionTypeInModel {
90+
name: boolean_expression_type_name.clone(),
91+
model: model_name.clone(),
92+
},
93+
)),
94+
}
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)