mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 17:31:56 +03:00
untangle input boolean expressions from models (#460)
<!-- Thank you for submitting this PR! :) --> ## Description Previously boolean expressions were only used on where clauses for models. We'd also like to use them for arguments for commands to make permissions work. This PR splits a boolean expression from it's model. This has the nice side effect of allowing the same boolean expression type to used across multiple models, which is a sensible thing to want to be able to do. Before this change, using the same boolean expression type on two models would give you this error: <img width="783" alt="Screenshot 2024-04-19 at 11 53 23" src="https://github.com/hasura/v3-engine/assets/4729125/bcc7a4b9-8b6f-4d82-9860-190621c0f5fd"> <!-- Questions to consider answering: 1. What user-facing changes are being made? 2. What are issues related to this PR? (Consider adding `(close #<issue-no>)` to the PR title) 3. What is the conceptual design behind this PR? 4. How can this PR be tested/verified? 5. Does the PR have limitations? 6. Does the PR introduce breaking changes? --> ## Changelog - Add a changelog entry (in the "Changelog entry" section below) if the changes in this PR have any user-facing impact. See [changelog guide](https://github.com/hasura/graphql-engine-mono/wiki/Changelog-Guide). - If no changelog is required ignore/remove this section and add a `no-changelog-required` label to the PR. ### Product _(Select all products this will be available in)_ - [X] community-edition - [X] cloud <!-- product : end : DO NOT REMOVE --> ### Type <!-- See changelog structure: https://github.com/hasura/graphql-engine-mono/wiki/Changelog-Guide#structure-of-our-changelog --> _(Select only one. In case of multiple, choose the most appropriate)_ - [ ] highlight - [X] enhancement - [ ] bugfix - [ ] behaviour-change - [ ] performance-enhancement - [ ] security-fix <!-- type : end : DO NOT REMOVE --> ### Changelog entry <!-- - Add a user understandable changelog entry - Include all details needed to understand the change. Try including links to docs or issues if relevant - For Highlights start with a H4 heading (#### <entry title>) - Get the changelog entry reviewed by your team --> Allow the same `ObjectBooleanExpressionType` to be shared between multiple models. <!-- changelog-entry : end : DO NOT REMOVE --> <!-- changelog : end : DO NOT REMOVE --> V3_GIT_ORIGIN_REV_ID: 6c9979ddaad50d476c0996d1ece48f0cf1c8e99d
This commit is contained in:
parent
bee983c902
commit
ee4e4eaabe
@ -10,7 +10,7 @@ use crate::execute::error;
|
|||||||
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
||||||
use crate::schema::types::output_type::relationship::FilterRelationshipAnnotation;
|
use crate::schema::types::output_type::relationship::FilterRelationshipAnnotation;
|
||||||
use crate::schema::types::{self};
|
use crate::schema::types::{self};
|
||||||
use crate::schema::types::{InputAnnotation, ModelInputAnnotation};
|
use crate::schema::types::{BooleanExpressionAnnotation, InputAnnotation, ModelInputAnnotation};
|
||||||
use crate::schema::GDS;
|
use crate::schema::GDS;
|
||||||
|
|
||||||
use super::relationship::LocalModelRelationshipInfo;
|
use super::relationship::LocalModelRelationshipInfo;
|
||||||
@ -58,8 +58,8 @@ pub(crate) fn build_filter_expression<'s>(
|
|||||||
) -> Result<Vec<ndc_models::Expression>, error::Error> {
|
) -> Result<Vec<ndc_models::Expression>, error::Error> {
|
||||||
match field.info.generic {
|
match field.info.generic {
|
||||||
// "_and"
|
// "_and"
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
field: types::ModelFilterArgument::AndOp,
|
field: types::ModelFilterArgument::AndOp,
|
||||||
},
|
},
|
||||||
)) => {
|
)) => {
|
||||||
@ -78,8 +78,8 @@ pub(crate) fn build_filter_expression<'s>(
|
|||||||
Ok(vec![expression])
|
Ok(vec![expression])
|
||||||
}
|
}
|
||||||
// "_or"
|
// "_or"
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
field: types::ModelFilterArgument::OrOp,
|
field: types::ModelFilterArgument::OrOp,
|
||||||
},
|
},
|
||||||
)) => {
|
)) => {
|
||||||
@ -98,8 +98,8 @@ pub(crate) fn build_filter_expression<'s>(
|
|||||||
Ok(vec![expression])
|
Ok(vec![expression])
|
||||||
}
|
}
|
||||||
// "_not"
|
// "_not"
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
field: types::ModelFilterArgument::NotOp,
|
field: types::ModelFilterArgument::NotOp,
|
||||||
},
|
},
|
||||||
)) => {
|
)) => {
|
||||||
@ -120,8 +120,8 @@ pub(crate) fn build_filter_expression<'s>(
|
|||||||
// to be a relationship column, we'll have to join all the paths to
|
// to be a relationship column, we'll have to join all the paths to
|
||||||
// specify NDC, what relationships needs to be traversed to access this
|
// specify NDC, what relationships needs to be traversed to access this
|
||||||
// column. The order decides how to access the column.
|
// column. The order decides how to access the column.
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
field: types::ModelFilterArgument::Field { ndc_column: column },
|
field: types::ModelFilterArgument::Field { ndc_column: column },
|
||||||
},
|
},
|
||||||
)) => {
|
)) => {
|
||||||
@ -158,8 +158,8 @@ pub(crate) fn build_filter_expression<'s>(
|
|||||||
}
|
}
|
||||||
// Relationship field used for filtering.
|
// Relationship field used for filtering.
|
||||||
// This relationship can either point to another relationship or a column.
|
// This relationship can either point to another relationship or a column.
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
field:
|
field:
|
||||||
types::ModelFilterArgument::RelationshipField(FilterRelationshipAnnotation {
|
types::ModelFilterArgument::RelationshipField(FilterRelationshipAnnotation {
|
||||||
relationship_name,
|
relationship_name,
|
||||||
|
@ -22,8 +22,7 @@ use crate::execute::ir::permissions;
|
|||||||
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
||||||
use crate::metadata::resolved;
|
use crate::metadata::resolved;
|
||||||
use crate::metadata::resolved::subgraph::Qualified;
|
use crate::metadata::resolved::subgraph::Qualified;
|
||||||
|
use crate::schema::types::{self, Annotation, BooleanExpressionAnnotation, ModelInputAnnotation};
|
||||||
use crate::schema::types::{self, Annotation, ModelInputAnnotation};
|
|
||||||
use crate::schema::GDS;
|
use crate::schema::GDS;
|
||||||
|
|
||||||
/// IR for the 'select_many' operation on a model
|
/// IR for the 'select_many' operation on a model
|
||||||
@ -85,12 +84,6 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
|
|||||||
.map_err(error::Error::map_unexpected_value_to_external_error)?,
|
.map_err(error::Error::map_unexpected_value_to_external_error)?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ModelInputAnnotation::ModelFilterExpression => {
|
|
||||||
filter_clause = filter::resolve_filter_expression(
|
|
||||||
argument.value.as_object()?,
|
|
||||||
&mut usage_counts,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ModelInputAnnotation::ModelArgumentsExpression => match &argument.value {
|
ModelInputAnnotation::ModelArgumentsExpression => match &argument.value {
|
||||||
normalized_ast::Value::Object(arguments) => {
|
normalized_ast::Value::Object(arguments) => {
|
||||||
model_arguments.extend(
|
model_arguments.extend(
|
||||||
@ -116,6 +109,15 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Annotation::Input(types::InputAnnotation::BooleanExpression(
|
||||||
|
BooleanExpressionAnnotation::BooleanExpression,
|
||||||
|
)) => {
|
||||||
|
filter_clause = filter::resolve_filter_expression(
|
||||||
|
argument.value.as_object()?,
|
||||||
|
&mut usage_counts,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
annotation => {
|
annotation => {
|
||||||
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||||
annotation: annotation.clone(),
|
annotation: annotation.clone(),
|
||||||
|
@ -39,7 +39,7 @@ use crate::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
metadata::resolved::{self, subgraph::Qualified},
|
metadata::resolved::{self, subgraph::Qualified},
|
||||||
schema::{
|
schema::{
|
||||||
types::{Annotation, InputAnnotation, ModelInputAnnotation},
|
types::{Annotation, BooleanExpressionAnnotation, InputAnnotation, ModelInputAnnotation},
|
||||||
GDS,
|
GDS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -248,12 +248,6 @@ pub(crate) fn generate_model_relationship_ir<'s>(
|
|||||||
error::Error::map_unexpected_value_to_external_error,
|
error::Error::map_unexpected_value_to_external_error,
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
ModelInputAnnotation::ModelFilterExpression => {
|
|
||||||
filter_clause = resolve_filter_expression(
|
|
||||||
argument.value.as_object()?,
|
|
||||||
usage_counts,
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
ModelInputAnnotation::ModelOrderByExpression => {
|
ModelInputAnnotation::ModelOrderByExpression => {
|
||||||
order_by = Some(build_ndc_order_by(argument, usage_counts)?)
|
order_by = Some(build_ndc_order_by(argument, usage_counts)?)
|
||||||
}
|
}
|
||||||
@ -264,6 +258,13 @@ pub(crate) fn generate_model_relationship_ir<'s>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
InputAnnotation::BooleanExpression(
|
||||||
|
BooleanExpressionAnnotation::BooleanExpression,
|
||||||
|
) => {
|
||||||
|
filter_clause =
|
||||||
|
resolve_filter_expression(argument.value.as_object()?, usage_counts)?
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||||
annotation: annotation.clone(),
|
annotation: annotation.clone(),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
mod argument;
|
mod argument;
|
||||||
|
pub mod boolean_expression;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod data_connector;
|
pub mod data_connector;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
155
v3/crates/engine/src/metadata/resolved/boolean_expression.rs
Normal file
155
v3/crates/engine/src/metadata/resolved/boolean_expression.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
use super::stages::data_connector_scalar_types;
|
||||||
|
|
||||||
|
use crate::metadata::resolved::data_connector;
|
||||||
|
|
||||||
|
use crate::metadata::resolved::error::{BooleanExpressionError, Error, GraphqlConfigError};
|
||||||
|
use crate::metadata::resolved::model;
|
||||||
|
|
||||||
|
use crate::metadata::resolved::stages::graphql_config::GraphqlConfig;
|
||||||
|
use crate::metadata::resolved::subgraph::{Qualified, QualifiedTypeReference};
|
||||||
|
|
||||||
|
use crate::metadata::resolved::types::TypeMapping;
|
||||||
|
|
||||||
|
use lang_graphql::ast::common::{self as ast};
|
||||||
|
use ndc_models;
|
||||||
|
|
||||||
|
use open_dds::{
|
||||||
|
data_connector::DataConnectorName,
|
||||||
|
types::{CustomTypeName, FieldName},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ComparisonExpressionInfo {
|
||||||
|
pub data_connector_name: Qualified<DataConnectorName>,
|
||||||
|
pub scalar_type_name: String,
|
||||||
|
pub type_name: ast::TypeName,
|
||||||
|
pub ndc_column: String,
|
||||||
|
pub operators: BTreeMap<String, QualifiedTypeReference>,
|
||||||
|
pub is_null_operator_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct BooleanExpressionGraphqlConfig {
|
||||||
|
pub where_field_name: ast::Name,
|
||||||
|
pub and_operator_name: ast::Name,
|
||||||
|
pub or_operator_name: ast::Name,
|
||||||
|
pub not_operator_name: ast::Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct BooleanExpression {
|
||||||
|
pub type_name: ast::TypeName,
|
||||||
|
pub scalar_fields: HashMap<FieldName, ComparisonExpressionInfo>,
|
||||||
|
pub graphql_config: BooleanExpressionGraphqlConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
// record filter expression info
|
||||||
|
pub fn resolve_boolean_expression(
|
||||||
|
name: &Qualified<CustomTypeName>,
|
||||||
|
data_connector_name: &Qualified<DataConnectorName>,
|
||||||
|
where_type_name: ast::TypeName,
|
||||||
|
subgraph: &str,
|
||||||
|
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||||
|
type_mappings: &TypeMapping,
|
||||||
|
graphql_config: &GraphqlConfig,
|
||||||
|
) -> Result<BooleanExpression, Error> {
|
||||||
|
let mut scalar_fields = HashMap::new();
|
||||||
|
|
||||||
|
let scalar_types = &data_connectors
|
||||||
|
.data_connectors_with_scalars
|
||||||
|
.get(data_connector_name)
|
||||||
|
.ok_or(Error::BooleanExpressionError {
|
||||||
|
boolean_expression_error:
|
||||||
|
BooleanExpressionError::UnknownDataConnectorInObjectBooleanExpressionType {
|
||||||
|
boolean_expression_type: name.clone(),
|
||||||
|
data_connector: data_connector_name.clone(),
|
||||||
|
},
|
||||||
|
})?
|
||||||
|
.scalars;
|
||||||
|
|
||||||
|
let TypeMapping::Object { field_mappings, .. } = type_mappings;
|
||||||
|
|
||||||
|
let filter_graphql_config = graphql_config
|
||||||
|
.query
|
||||||
|
.filter_input_config
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| Error::GraphqlConfigError {
|
||||||
|
graphql_config_error: GraphqlConfigError::MissingFilterInputFieldInGraphqlConfig,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for (field_name, field_mapping) in field_mappings.iter() {
|
||||||
|
// Generate comparison expression for fields mapped to simple scalar type
|
||||||
|
if let Some((scalar_type_name, scalar_type_info)) =
|
||||||
|
data_connector::get_simple_scalar(field_mapping.column_type.clone(), scalar_types)
|
||||||
|
{
|
||||||
|
if let Some(graphql_type_name) = &scalar_type_info.comparison_expression_name.clone() {
|
||||||
|
let mut operators = BTreeMap::new();
|
||||||
|
for (op_name, op_definition) in
|
||||||
|
scalar_type_info.scalar_type.comparison_operators.iter()
|
||||||
|
{
|
||||||
|
operators.insert(
|
||||||
|
op_name.clone(),
|
||||||
|
model::resolve_ndc_type(
|
||||||
|
data_connector_name,
|
||||||
|
&get_argument_type(op_definition, &field_mapping.column_type),
|
||||||
|
scalar_types,
|
||||||
|
subgraph,
|
||||||
|
)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register scalar comparison field only if it contains non-zero operators.
|
||||||
|
if !operators.is_empty() {
|
||||||
|
scalar_fields.insert(
|
||||||
|
field_name.clone(),
|
||||||
|
ComparisonExpressionInfo {
|
||||||
|
data_connector_name: data_connector_name.clone(),
|
||||||
|
scalar_type_name: scalar_type_name.clone(),
|
||||||
|
type_name: graphql_type_name.clone(),
|
||||||
|
ndc_column: field_mapping.column.clone(),
|
||||||
|
operators,
|
||||||
|
is_null_operator_name: filter_graphql_config
|
||||||
|
.operator_names
|
||||||
|
.is_null
|
||||||
|
.to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(BooleanExpression {
|
||||||
|
type_name: where_type_name,
|
||||||
|
scalar_fields,
|
||||||
|
graphql_config: (BooleanExpressionGraphqlConfig {
|
||||||
|
where_field_name: filter_graphql_config.where_field_name.clone(),
|
||||||
|
and_operator_name: filter_graphql_config.operator_names.and.clone(),
|
||||||
|
or_operator_name: filter_graphql_config.operator_names.or.clone(),
|
||||||
|
not_operator_name: filter_graphql_config.operator_names.not.clone(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_nullable(field_type: &ndc_models::Type) -> &ndc_models::Type {
|
||||||
|
if let ndc_models::Type::Nullable { underlying_type } = field_type {
|
||||||
|
unwrap_nullable(underlying_type)
|
||||||
|
} else {
|
||||||
|
field_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_argument_type(
|
||||||
|
op_definition: &ndc_models::ComparisonOperatorDefinition,
|
||||||
|
field_type: &ndc_models::Type,
|
||||||
|
) -> ndc_models::Type {
|
||||||
|
match op_definition {
|
||||||
|
ndc_models::ComparisonOperatorDefinition::Equal => unwrap_nullable(field_type).clone(),
|
||||||
|
ndc_models::ComparisonOperatorDefinition::In => ndc_models::Type::Array {
|
||||||
|
element_type: Box::new(unwrap_nullable(field_type).clone()),
|
||||||
|
},
|
||||||
|
ndc_models::ComparisonOperatorDefinition::Custom { argument_type } => argument_type.clone(),
|
||||||
|
}
|
||||||
|
}
|
@ -605,6 +605,11 @@ pub enum BooleanExpressionError {
|
|||||||
data_connector_object_type: String,
|
data_connector_object_type: String,
|
||||||
data_connector: Qualified<DataConnectorName>,
|
data_connector: Qualified<DataConnectorName>,
|
||||||
},
|
},
|
||||||
|
#[error("{error:} in boolean expression type {boolean_expression_type:}")]
|
||||||
|
BooleanExpressionTypeMappingCollectionError {
|
||||||
|
boolean_expression_type: Qualified<CustomTypeName>,
|
||||||
|
error: TypeMappingCollectionError,
|
||||||
|
},
|
||||||
#[error("the following object boolean expression type is defined more than once: {name:}")]
|
#[error("the following object boolean expression type is defined more than once: {name:}")]
|
||||||
DuplicateObjectBooleanExpressionTypeDefinition { name: Qualified<CustomTypeName> },
|
DuplicateObjectBooleanExpressionTypeDefinition { name: Qualified<CustomTypeName> },
|
||||||
#[error("unknown object boolean expression type {name:} is used in model {model:}")]
|
#[error("unknown object boolean expression type {name:} is used in model {model:}")]
|
||||||
|
@ -63,7 +63,9 @@ pub fn resolve_metadata(
|
|||||||
data_connectors,
|
data_connectors,
|
||||||
data_connector_type_mappings,
|
data_connector_type_mappings,
|
||||||
&object_types,
|
&object_types,
|
||||||
|
scalar_types,
|
||||||
&mut existing_graphql_types,
|
&mut existing_graphql_types,
|
||||||
|
graphql_config,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// resolve models
|
// resolve models
|
||||||
@ -252,7 +254,9 @@ fn resolve_boolean_expression_types(
|
|||||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||||
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
||||||
object_types: &HashMap<Qualified<CustomTypeName>, ObjectTypeRepresentation>,
|
object_types: &HashMap<Qualified<CustomTypeName>, ObjectTypeRepresentation>,
|
||||||
|
scalar_types: &HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||||
existing_graphql_types: &mut HashSet<ast::TypeName>,
|
existing_graphql_types: &mut HashSet<ast::TypeName>,
|
||||||
|
graphql_config: &graphql_config::GraphqlConfig,
|
||||||
) -> Result<HashMap<Qualified<CustomTypeName>, ObjectBooleanExpressionType>, Error> {
|
) -> Result<HashMap<Qualified<CustomTypeName>, ObjectBooleanExpressionType>, Error> {
|
||||||
let mut boolean_expression_types = HashMap::new();
|
let mut boolean_expression_types = HashMap::new();
|
||||||
for open_dds::accessor::QualifiedObject {
|
for open_dds::accessor::QualifiedObject {
|
||||||
@ -264,9 +268,11 @@ fn resolve_boolean_expression_types(
|
|||||||
boolean_expression_type,
|
boolean_expression_type,
|
||||||
subgraph,
|
subgraph,
|
||||||
data_connectors,
|
data_connectors,
|
||||||
object_types,
|
|
||||||
data_connector_type_mappings,
|
data_connector_type_mappings,
|
||||||
|
object_types,
|
||||||
|
scalar_types,
|
||||||
existing_graphql_types,
|
existing_graphql_types,
|
||||||
|
graphql_config,
|
||||||
)?;
|
)?;
|
||||||
if let Some(existing) = boolean_expression_types.insert(
|
if let Some(existing) = boolean_expression_types.insert(
|
||||||
resolved_boolean_expression.name.clone(),
|
resolved_boolean_expression.name.clone(),
|
||||||
@ -347,7 +353,6 @@ fn resolve_models(
|
|||||||
resolve_model_graphql_api(
|
resolve_model_graphql_api(
|
||||||
model_graphql_definition,
|
model_graphql_definition,
|
||||||
&mut resolved_model,
|
&mut resolved_model,
|
||||||
subgraph,
|
|
||||||
existing_graphql_types,
|
existing_graphql_types,
|
||||||
data_connectors,
|
data_connectors,
|
||||||
&model.description,
|
&model.description,
|
||||||
@ -398,6 +403,7 @@ fn resolve_relationships(
|
|||||||
data_connectors,
|
data_connectors,
|
||||||
object_representation,
|
object_representation,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if object_representation
|
if object_representation
|
||||||
.relationships
|
.relationships
|
||||||
.insert(
|
.insert(
|
||||||
|
@ -7,6 +7,7 @@ use super::types::{
|
|||||||
ObjectTypeRepresentation, TypeMappingToCollect,
|
ObjectTypeRepresentation, TypeMappingToCollect,
|
||||||
};
|
};
|
||||||
use crate::metadata::resolved::argument::get_argument_mappings;
|
use crate::metadata::resolved::argument::get_argument_mappings;
|
||||||
|
|
||||||
use crate::metadata::resolved::data_connector;
|
use crate::metadata::resolved::data_connector;
|
||||||
use crate::metadata::resolved::data_connector::DataConnectorLink;
|
use crate::metadata::resolved::data_connector::DataConnectorLink;
|
||||||
use crate::metadata::resolved::error::{
|
use crate::metadata::resolved::error::{
|
||||||
@ -63,31 +64,6 @@ pub struct SelectManyGraphQlDefinition {
|
|||||||
pub deprecated: Option<Deprecated>,
|
pub deprecated: Option<Deprecated>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ComparisonExpressionInfo {
|
|
||||||
pub data_connector_name: Qualified<DataConnectorName>,
|
|
||||||
pub scalar_type_name: String,
|
|
||||||
pub type_name: ast::TypeName,
|
|
||||||
pub ndc_column: String,
|
|
||||||
pub operators: BTreeMap<String, QualifiedTypeReference>,
|
|
||||||
pub is_null_operator_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ModelFilterExpressionGraphqlConfig {
|
|
||||||
pub where_field_name: ast::Name,
|
|
||||||
pub and_operator_name: ast::Name,
|
|
||||||
pub or_operator_name: ast::Name,
|
|
||||||
pub not_operator_name: ast::Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ModelFilterExpression {
|
|
||||||
pub where_type_name: ast::TypeName,
|
|
||||||
pub scalar_fields: HashMap<FieldName, ComparisonExpressionInfo>,
|
|
||||||
pub filter_graphql_config: ModelFilterExpressionGraphqlConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add support for aggregates
|
// TODO: add support for aggregates
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct OrderByExpressionInfo {
|
pub struct OrderByExpressionInfo {
|
||||||
@ -121,7 +97,6 @@ pub struct ModelGraphQlApi {
|
|||||||
pub arguments_input_config: Option<ModelGraphqlApiArgumentsConfig>,
|
pub arguments_input_config: Option<ModelGraphqlApiArgumentsConfig>,
|
||||||
pub select_uniques: Vec<SelectUniqueGraphQlDefinition>,
|
pub select_uniques: Vec<SelectUniqueGraphQlDefinition>,
|
||||||
pub select_many: Option<SelectManyGraphQlDefinition>,
|
pub select_many: Option<SelectManyGraphQlDefinition>,
|
||||||
pub filter_expression: Option<ModelFilterExpression>,
|
|
||||||
pub order_by_expression: Option<ModelOrderByExpression>,
|
pub order_by_expression: Option<ModelOrderByExpression>,
|
||||||
pub limit_field: Option<LimitFieldGraphqlConfig>,
|
pub limit_field: Option<LimitFieldGraphqlConfig>,
|
||||||
pub offset_field: Option<OffsetFieldGraphqlConfig>,
|
pub offset_field: Option<OffsetFieldGraphqlConfig>,
|
||||||
@ -416,7 +391,7 @@ pub fn resolve_model(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper function to resolve ndc types to dds type based on scalar type representations
|
// helper function to resolve ndc types to dds type based on scalar type representations
|
||||||
fn resolve_ndc_type(
|
pub(crate) fn resolve_ndc_type(
|
||||||
data_connector: &Qualified<DataConnectorName>,
|
data_connector: &Qualified<DataConnectorName>,
|
||||||
source_type: &ndc_models::Type,
|
source_type: &ndc_models::Type,
|
||||||
scalars: &HashMap<&str, data_connector_scalar_types::ScalarTypeWithRepresentationInfo>,
|
scalars: &HashMap<&str, data_connector_scalar_types::ScalarTypeWithRepresentationInfo>,
|
||||||
@ -939,7 +914,6 @@ pub(crate) fn get_ndc_column_for_comparison<F: Fn() -> String>(
|
|||||||
pub fn resolve_model_graphql_api(
|
pub fn resolve_model_graphql_api(
|
||||||
model_graphql_definition: &ModelGraphQlDefinition,
|
model_graphql_definition: &ModelGraphQlDefinition,
|
||||||
model: &mut Model,
|
model: &mut Model,
|
||||||
subgraph: &str,
|
|
||||||
existing_graphql_types: &mut HashSet<ast::TypeName>,
|
existing_graphql_types: &mut HashSet<ast::TypeName>,
|
||||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||||
model_description: &Option<String>,
|
model_description: &Option<String>,
|
||||||
@ -1063,120 +1037,6 @@ pub fn resolve_model_graphql_api(
|
|||||||
.transpose()?
|
.transpose()?
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
// record filter expression info
|
|
||||||
model.graphql_api.filter_expression = model
|
|
||||||
.filter_expression_type
|
|
||||||
.as_ref()
|
|
||||||
.map(
|
|
||||||
|model_filter_expression_type| -> Result<Option<ModelFilterExpression>, Error> {
|
|
||||||
let model_source = model.source.as_ref().ok_or_else(|| {
|
|
||||||
Error::CannotUseFilterExpressionsWithoutSource {
|
|
||||||
model: model.name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
let where_type_name = if let Some(model_filter_expression_graphql) =
|
|
||||||
&model_filter_expression_type.graphql
|
|
||||||
{
|
|
||||||
model_filter_expression_graphql.type_name.clone()
|
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
let mut scalar_fields = HashMap::new();
|
|
||||||
|
|
||||||
let scalar_types = &data_connectors
|
|
||||||
.data_connectors_with_scalars
|
|
||||||
.get(&model_source.data_connector.name)
|
|
||||||
.ok_or(Error::UnknownModelDataConnector {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
data_connector: model_source.data_connector.name.clone(),
|
|
||||||
})?
|
|
||||||
.scalars;
|
|
||||||
|
|
||||||
let TypeMapping::Object { field_mappings, .. } = model_source
|
|
||||||
.type_mappings
|
|
||||||
.get(&model.data_type)
|
|
||||||
.ok_or(Error::TypeMappingRequired {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
type_name: model.data_type.clone(),
|
|
||||||
data_connector: model_source.data_connector.name.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let filter_graphql_config = graphql_config
|
|
||||||
.query
|
|
||||||
.filter_input_config
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| Error::GraphqlConfigError {
|
|
||||||
graphql_config_error:
|
|
||||||
GraphqlConfigError::MissingFilterInputFieldInGraphqlConfig,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for (field_name, field_mapping) in field_mappings.iter() {
|
|
||||||
// Generate comparison expression for fields mapped to simple scalar type
|
|
||||||
if let Some((scalar_type_name, scalar_type_info)) =
|
|
||||||
data_connector::get_simple_scalar(
|
|
||||||
field_mapping.column_type.clone(),
|
|
||||||
scalar_types,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if let Some(graphql_type_name) =
|
|
||||||
&scalar_type_info.comparison_expression_name.clone()
|
|
||||||
{
|
|
||||||
let mut operators = BTreeMap::new();
|
|
||||||
for (op_name, op_definition) in
|
|
||||||
scalar_type_info.scalar_type.comparison_operators.iter()
|
|
||||||
{
|
|
||||||
operators.insert(
|
|
||||||
op_name.clone(),
|
|
||||||
resolve_ndc_type(
|
|
||||||
&model_source.data_connector.name,
|
|
||||||
&get_argument_type(
|
|
||||||
op_definition,
|
|
||||||
&field_mapping.column_type,
|
|
||||||
),
|
|
||||||
scalar_types,
|
|
||||||
subgraph,
|
|
||||||
)?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Register scalar comparison field only if it contains non-zero operators.
|
|
||||||
if !operators.is_empty() {
|
|
||||||
scalar_fields.insert(
|
|
||||||
field_name.clone(),
|
|
||||||
ComparisonExpressionInfo {
|
|
||||||
data_connector_name: model_source
|
|
||||||
.data_connector
|
|
||||||
.name
|
|
||||||
.clone(),
|
|
||||||
scalar_type_name: scalar_type_name.clone(),
|
|
||||||
type_name: graphql_type_name.clone(),
|
|
||||||
ndc_column: field_mapping.column.clone(),
|
|
||||||
operators,
|
|
||||||
is_null_operator_name: filter_graphql_config
|
|
||||||
.operator_names
|
|
||||||
.is_null
|
|
||||||
.to_string(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(ModelFilterExpression {
|
|
||||||
where_type_name,
|
|
||||||
scalar_fields,
|
|
||||||
filter_graphql_config: (ModelFilterExpressionGraphqlConfig {
|
|
||||||
where_field_name: filter_graphql_config.where_field_name.clone(),
|
|
||||||
and_operator_name: filter_graphql_config.operator_names.and.clone(),
|
|
||||||
or_operator_name: filter_graphql_config.operator_names.or.clone(),
|
|
||||||
not_operator_name: filter_graphql_config.operator_names.not.clone(),
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.transpose()?
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
// record select_many root field
|
// record select_many root field
|
||||||
model.graphql_api.select_many = match &model_graphql_definition.select_many {
|
model.graphql_api.select_many = match &model_graphql_definition.select_many {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@ -1250,27 +1110,6 @@ pub fn resolve_model_graphql_api(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwrap_nullable(field_type: &ndc_models::Type) -> &ndc_models::Type {
|
|
||||||
if let ndc_models::Type::Nullable { underlying_type } = field_type {
|
|
||||||
unwrap_nullable(underlying_type)
|
|
||||||
} else {
|
|
||||||
field_type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_argument_type(
|
|
||||||
op_definition: &ndc_models::ComparisonOperatorDefinition,
|
|
||||||
field_type: &ndc_models::Type,
|
|
||||||
) -> ndc_models::Type {
|
|
||||||
match op_definition {
|
|
||||||
ndc_models::ComparisonOperatorDefinition::Equal => unwrap_nullable(field_type).clone(),
|
|
||||||
ndc_models::ComparisonOperatorDefinition::In => ndc_models::Type::Array {
|
|
||||||
element_type: Box::new(unwrap_nullable(field_type).clone()),
|
|
||||||
},
|
|
||||||
ndc_models::ComparisonOperatorDefinition::Custom { argument_type } => argument_type.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_model_source(
|
pub fn resolve_model_source(
|
||||||
model_source: &models::ModelSource,
|
model_source: &models::ModelSource,
|
||||||
model: &mut Model,
|
model: &mut Model,
|
||||||
|
@ -51,6 +51,7 @@ pub struct ComparisonOperators {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// basic scalar type info
|
// basic scalar type info
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ScalarTypeInfo<'a> {
|
pub struct ScalarTypeInfo<'a> {
|
||||||
pub scalar_type: &'a ndc_models::ScalarType,
|
pub scalar_type: &'a ndc_models::ScalarType,
|
||||||
pub comparison_operators: ComparisonOperators,
|
pub comparison_operators: ComparisonOperators,
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
|
use super::stages::{
|
||||||
|
data_connector_scalar_types, data_connector_type_mappings, graphql_config, scalar_types,
|
||||||
|
};
|
||||||
|
use crate::metadata::resolved::boolean_expression;
|
||||||
|
use crate::metadata::resolved::data_connector;
|
||||||
use crate::metadata::resolved::error::{BooleanExpressionError, Error};
|
use crate::metadata::resolved::error::{BooleanExpressionError, Error};
|
||||||
|
|
||||||
use crate::metadata::resolved::relationship::Relationship;
|
use crate::metadata::resolved::relationship::Relationship;
|
||||||
use crate::metadata::resolved::subgraph::{
|
use crate::metadata::resolved::subgraph::{
|
||||||
mk_qualified_type_reference, Qualified, QualifiedBaseType, QualifiedTypeName,
|
mk_qualified_type_reference, Qualified, QualifiedBaseType, QualifiedTypeName,
|
||||||
QualifiedTypeReference,
|
QualifiedTypeReference,
|
||||||
};
|
};
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use lang_graphql::ast::common as ast;
|
use lang_graphql::ast::common as ast;
|
||||||
use ndc_models;
|
use ndc_models;
|
||||||
@ -22,8 +27,6 @@ use std::collections::{BTreeMap, HashMap, HashSet};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::ndc_validation::{get_underlying_named_type, NDCValidationError};
|
use super::ndc_validation::{get_underlying_named_type, NDCValidationError};
|
||||||
use super::stages::data_connector_scalar_types;
|
|
||||||
use super::stages::{data_connector_type_mappings, scalar_types};
|
|
||||||
use super::typecheck;
|
use super::typecheck;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, derive_more::Display)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, derive_more::Display)]
|
||||||
@ -83,13 +86,10 @@ pub struct ObjectBooleanExpressionType {
|
|||||||
pub name: Qualified<CustomTypeName>,
|
pub name: Qualified<CustomTypeName>,
|
||||||
pub object_type: Qualified<CustomTypeName>,
|
pub object_type: Qualified<CustomTypeName>,
|
||||||
pub data_connector_name: Qualified<DataConnectorName>,
|
pub data_connector_name: Qualified<DataConnectorName>,
|
||||||
|
pub data_connector_link: data_connector::DataConnectorLink,
|
||||||
pub data_connector_object_type: String,
|
pub data_connector_object_type: String,
|
||||||
pub graphql: Option<ObjectBooleanExpressionTypeGraphQlConfiguration>,
|
pub type_mappings: BTreeMap<Qualified<types::CustomTypeName>, TypeMapping>,
|
||||||
}
|
pub graphql: Option<boolean_expression::BooleanExpression>,
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ObjectBooleanExpressionTypeGraphQlConfiguration {
|
|
||||||
pub type_name: ast::TypeName,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, derive_more::Display)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, derive_more::Display)]
|
||||||
@ -433,9 +433,11 @@ pub(crate) fn resolve_object_boolean_expression_type(
|
|||||||
object_boolean_expression: &ObjectBooleanExpressionTypeV1,
|
object_boolean_expression: &ObjectBooleanExpressionTypeV1,
|
||||||
subgraph: &str,
|
subgraph: &str,
|
||||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||||
object_types: &HashMap<Qualified<CustomTypeName>, ObjectTypeRepresentation>,
|
|
||||||
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
||||||
|
object_types: &HashMap<Qualified<CustomTypeName>, ObjectTypeRepresentation>,
|
||||||
|
scalar_types: &HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||||
existing_graphql_types: &mut HashSet<ast::TypeName>,
|
existing_graphql_types: &mut HashSet<ast::TypeName>,
|
||||||
|
graphql_config: &graphql_config::GraphqlConfig,
|
||||||
) -> Result<ObjectBooleanExpressionType, Error> {
|
) -> Result<ObjectBooleanExpressionType, Error> {
|
||||||
// name of the boolean expression
|
// name of the boolean expression
|
||||||
let qualified_name = Qualified::new(
|
let qualified_name = Qualified::new(
|
||||||
@ -544,26 +546,88 @@ pub(crate) fn resolve_object_boolean_expression_type(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let boolean_expression_type =
|
||||||
|
Qualified::new(subgraph.to_string(), object_boolean_expression.name.clone());
|
||||||
|
|
||||||
|
let object_type = Qualified::new(
|
||||||
|
subgraph.to_string(),
|
||||||
|
object_boolean_expression.object_type.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let data_connector_name = Qualified::new(
|
||||||
|
subgraph.to_string(),
|
||||||
|
object_boolean_expression.data_connector_name.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Collect type mappings.
|
||||||
|
let mut type_mappings = BTreeMap::new();
|
||||||
|
|
||||||
|
let type_mapping_to_collect = TypeMappingToCollect {
|
||||||
|
type_name: &object_type,
|
||||||
|
ndc_object_type_name: object_boolean_expression
|
||||||
|
.data_connector_object_type
|
||||||
|
.as_str(),
|
||||||
|
};
|
||||||
|
collect_type_mapping_for_source(
|
||||||
|
&type_mapping_to_collect,
|
||||||
|
data_connector_type_mappings,
|
||||||
|
&qualified_data_connector_name,
|
||||||
|
object_types,
|
||||||
|
scalar_types,
|
||||||
|
&mut type_mappings,
|
||||||
|
)
|
||||||
|
.map_err(|error| {
|
||||||
|
Error::from(
|
||||||
|
BooleanExpressionError::BooleanExpressionTypeMappingCollectionError {
|
||||||
|
boolean_expression_type: boolean_expression_type.clone(),
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
// validate graphql config
|
// validate graphql config
|
||||||
let graphql_config = object_boolean_expression
|
let boolean_expression_graphql_config = object_boolean_expression
|
||||||
.graphql
|
.graphql
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|graphql_config| {
|
.map(|object_boolean_graphql_config| {
|
||||||
let graphql_type_name =
|
let graphql_type_name =
|
||||||
mk_name(graphql_config.type_name.0.as_ref()).map(ast::TypeName)?;
|
mk_name(object_boolean_graphql_config.type_name.0.as_ref()).map(ast::TypeName)?;
|
||||||
|
|
||||||
store_new_graphql_type(existing_graphql_types, Some(&graphql_type_name))?;
|
store_new_graphql_type(existing_graphql_types, Some(&graphql_type_name))?;
|
||||||
Ok::<_, Error>(ObjectBooleanExpressionTypeGraphQlConfiguration {
|
|
||||||
type_name: graphql_type_name,
|
let type_mapping = type_mappings
|
||||||
})
|
.get(&Qualified::new(
|
||||||
|
subgraph.to_string(),
|
||||||
|
object_boolean_expression.object_type.clone(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
boolean_expression::resolve_boolean_expression(
|
||||||
|
&boolean_expression_type,
|
||||||
|
&data_connector_name,
|
||||||
|
graphql_type_name.clone(),
|
||||||
|
subgraph,
|
||||||
|
data_connectors,
|
||||||
|
type_mapping,
|
||||||
|
graphql_config,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
|
let data_connector_link = data_connector::DataConnectorLink::new(
|
||||||
|
data_connector_name,
|
||||||
|
data_connector_context.inner.url.clone(),
|
||||||
|
data_connector_context.inner.headers,
|
||||||
|
)?;
|
||||||
|
|
||||||
let resolved_boolean_expression = ObjectBooleanExpressionType {
|
let resolved_boolean_expression = ObjectBooleanExpressionType {
|
||||||
name: qualified_name.clone(),
|
name: qualified_name.clone(),
|
||||||
|
type_mappings,
|
||||||
object_type: qualified_object_type_name.clone(),
|
object_type: qualified_object_type_name.clone(),
|
||||||
data_connector_name: qualified_data_connector_name,
|
data_connector_name: qualified_data_connector_name,
|
||||||
|
data_connector_link,
|
||||||
data_connector_object_type: object_boolean_expression.data_connector_object_type.clone(),
|
data_connector_object_type: object_boolean_expression.data_connector_object_type.clone(),
|
||||||
graphql: graphql_config,
|
graphql: boolean_expression_graphql_config,
|
||||||
};
|
};
|
||||||
Ok(resolved_boolean_expression)
|
Ok(resolved_boolean_expression)
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ use crate::metadata::{
|
|||||||
use self::types::{PossibleApolloFederationTypes, RootFieldAnnotation};
|
use self::types::{PossibleApolloFederationTypes, RootFieldAnnotation};
|
||||||
|
|
||||||
pub mod apollo_federation;
|
pub mod apollo_federation;
|
||||||
|
pub mod boolean_expression;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod model_arguments;
|
pub mod model_arguments;
|
||||||
pub mod model_filter;
|
pub mod model_filter;
|
||||||
@ -106,6 +107,15 @@ impl gql_schema::SchemaContext for GDS {
|
|||||||
gds_type_name,
|
gds_type_name,
|
||||||
graphql_type_name,
|
graphql_type_name,
|
||||||
),
|
),
|
||||||
|
types::TypeId::InputObjectBooleanExpressionType {
|
||||||
|
gds_type_name,
|
||||||
|
graphql_type_name,
|
||||||
|
} => boolean_expression::build_boolean_expression_input_schema(
|
||||||
|
self,
|
||||||
|
builder,
|
||||||
|
graphql_type_name,
|
||||||
|
gds_type_name,
|
||||||
|
),
|
||||||
types::TypeId::NodeRoot => Ok(gql_schema::TypeInfo::Interface(
|
types::TypeId::NodeRoot => Ok(gql_schema::TypeInfo::Interface(
|
||||||
relay::node_interface_schema(builder, self)?,
|
relay::node_interface_schema(builder, self)?,
|
||||||
)),
|
)),
|
||||||
@ -115,15 +125,6 @@ impl gql_schema::SchemaContext for GDS {
|
|||||||
} => model_arguments::build_model_arguments_input_schema(
|
} => model_arguments::build_model_arguments_input_schema(
|
||||||
self, builder, type_name, model_name,
|
self, builder, type_name, model_name,
|
||||||
),
|
),
|
||||||
types::TypeId::ModelBooleanExpression {
|
|
||||||
model_name,
|
|
||||||
graphql_type_name,
|
|
||||||
} => model_filter::build_model_filter_expression_input_schema(
|
|
||||||
self,
|
|
||||||
builder,
|
|
||||||
graphql_type_name,
|
|
||||||
model_name,
|
|
||||||
),
|
|
||||||
types::TypeId::ScalarTypeComparisonExpression {
|
types::TypeId::ScalarTypeComparisonExpression {
|
||||||
scalar_type_name: _,
|
scalar_type_name: _,
|
||||||
graphql_type_name,
|
graphql_type_name,
|
||||||
@ -224,6 +225,10 @@ pub enum Error {
|
|||||||
"internal error while building schema, filter_expression for model not found: {model_name}"
|
"internal error while building schema, filter_expression for model not found: {model_name}"
|
||||||
)]
|
)]
|
||||||
InternalModelFilterExpressionNotFound { model_name: Qualified<ModelName> },
|
InternalModelFilterExpressionNotFound { model_name: Qualified<ModelName> },
|
||||||
|
#[error("internal error while building schema, boolean expression not found: {type_name}")]
|
||||||
|
InternalBooleanExpressionNotFound {
|
||||||
|
type_name: Qualified<CustomTypeName>,
|
||||||
|
},
|
||||||
#[error(
|
#[error(
|
||||||
"Conflicting argument names {argument_name} for field {field_name} of type {type_name}"
|
"Conflicting argument names {argument_name} for field {field_name} of type {type_name}"
|
||||||
)]
|
)]
|
||||||
|
275
v3/crates/engine/src/schema/boolean_expression.rs
Normal file
275
v3/crates/engine/src/schema/boolean_expression.rs
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
use hasura_authn_core::Role;
|
||||||
|
use lang_graphql::ast::common as ast;
|
||||||
|
use lang_graphql::schema::{self as gql_schema};
|
||||||
|
use open_dds::types::CustomTypeName;
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
use super::types::output_type::get_object_type_representation;
|
||||||
|
use super::types::output_type::relationship::{FilterRelationshipAnnotation, ModelTargetSource};
|
||||||
|
use super::types::{BooleanExpressionAnnotation, InputAnnotation, TypeId};
|
||||||
|
use crate::metadata::resolved;
|
||||||
|
use crate::metadata::resolved::relationship::{
|
||||||
|
relationship_execution_category, RelationshipExecutionCategory, RelationshipTarget,
|
||||||
|
};
|
||||||
|
use crate::metadata::resolved::subgraph::Qualified;
|
||||||
|
use crate::metadata::resolved::types::mk_name;
|
||||||
|
|
||||||
|
use crate::schema::permissions;
|
||||||
|
use crate::schema::types;
|
||||||
|
use crate::schema::GDS;
|
||||||
|
|
||||||
|
type Error = crate::schema::Error;
|
||||||
|
|
||||||
|
pub fn build_boolean_expression_input_schema(
|
||||||
|
gds: &GDS,
|
||||||
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
|
type_name: &ast::TypeName,
|
||||||
|
gds_type_name: &Qualified<CustomTypeName>,
|
||||||
|
) -> Result<gql_schema::TypeInfo<GDS>, Error> {
|
||||||
|
let object_boolean_expression_type =
|
||||||
|
gds.metadata
|
||||||
|
.boolean_expression_types
|
||||||
|
.get(gds_type_name)
|
||||||
|
.ok_or_else(|| crate::schema::Error::InternalTypeNotFound {
|
||||||
|
type_name: gds_type_name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(boolean_expression_info) = &object_boolean_expression_type.graphql {
|
||||||
|
let mut input_fields = BTreeMap::new();
|
||||||
|
|
||||||
|
// `_and`, `_or` or `_not` fields are available for all roles
|
||||||
|
let not_field_name = &boolean_expression_info.graphql_config.not_operator_name;
|
||||||
|
|
||||||
|
input_fields.insert(
|
||||||
|
not_field_name.clone(),
|
||||||
|
builder.allow_all_namespaced(
|
||||||
|
gql_schema::InputField::<GDS>::new(
|
||||||
|
not_field_name.clone(),
|
||||||
|
None,
|
||||||
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
|
field: types::ModelFilterArgument::NotOp,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
ast::TypeContainer::named_null(gql_schema::RegisteredTypeName::new(
|
||||||
|
type_name.0.clone(),
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
gql_schema::DeprecationStatus::NotDeprecated,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let and_field_name = &boolean_expression_info.graphql_config.and_operator_name;
|
||||||
|
|
||||||
|
input_fields.insert(
|
||||||
|
and_field_name.clone(),
|
||||||
|
builder.allow_all_namespaced(
|
||||||
|
gql_schema::InputField::<GDS>::new(
|
||||||
|
and_field_name.clone(),
|
||||||
|
None,
|
||||||
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
|
field: types::ModelFilterArgument::AndOp,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
ast::TypeContainer::list_null(ast::TypeContainer::named_non_null(
|
||||||
|
gql_schema::RegisteredTypeName::new(type_name.0.clone()),
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
gql_schema::DeprecationStatus::NotDeprecated,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let or_field_name = &boolean_expression_info.graphql_config.or_operator_name;
|
||||||
|
input_fields.insert(
|
||||||
|
or_field_name.clone(),
|
||||||
|
builder.allow_all_namespaced(
|
||||||
|
gql_schema::InputField::<GDS>::new(
|
||||||
|
or_field_name.clone(),
|
||||||
|
None,
|
||||||
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
|
field: types::ModelFilterArgument::OrOp,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
ast::TypeContainer::list_null(ast::TypeContainer::named_non_null(
|
||||||
|
gql_schema::RegisteredTypeName::new(type_name.0.clone()),
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
gql_schema::DeprecationStatus::NotDeprecated,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let object_type_representation =
|
||||||
|
get_object_type_representation(gds, &object_boolean_expression_type.object_type)?;
|
||||||
|
|
||||||
|
// column fields
|
||||||
|
for (field_name, comparison_expression) in &boolean_expression_info.scalar_fields {
|
||||||
|
let field_graphql_name = mk_name(field_name.clone().0.as_str())?;
|
||||||
|
let registered_type_name =
|
||||||
|
get_scalar_comparison_input_type(builder, comparison_expression)?;
|
||||||
|
let field_type = ast::TypeContainer::named_null(registered_type_name);
|
||||||
|
let annotation = types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
|
BooleanExpressionAnnotation::BooleanExpressionArgument {
|
||||||
|
field: types::ModelFilterArgument::Field {
|
||||||
|
ndc_column: comparison_expression.ndc_column.clone(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
));
|
||||||
|
let field_permissions: HashMap<Role, Option<types::NamespaceAnnotation>> =
|
||||||
|
permissions::get_allowed_roles_for_field(object_type_representation, field_name)
|
||||||
|
.map(|role| (role.clone(), None))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let input_field = builder.conditional_namespaced(
|
||||||
|
gql_schema::InputField::<GDS>::new(
|
||||||
|
field_graphql_name.clone(),
|
||||||
|
None,
|
||||||
|
annotation,
|
||||||
|
field_type,
|
||||||
|
None,
|
||||||
|
gql_schema::DeprecationStatus::NotDeprecated,
|
||||||
|
),
|
||||||
|
field_permissions,
|
||||||
|
);
|
||||||
|
input_fields.insert(field_graphql_name, input_field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// relationship fields
|
||||||
|
// TODO(naveen): Add support for command relationships
|
||||||
|
for (rel_name, relationship) in object_type_representation.relationships.iter() {
|
||||||
|
if let RelationshipTarget::Model {
|
||||||
|
model_name,
|
||||||
|
relationship_type,
|
||||||
|
target_typename,
|
||||||
|
mappings,
|
||||||
|
} = &relationship.target
|
||||||
|
{
|
||||||
|
let target_model = gds.metadata.models.get(model_name).ok_or_else(|| {
|
||||||
|
crate::schema::Error::InternalModelNotFound {
|
||||||
|
model_name: model_name.clone(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let target_object_type_representation =
|
||||||
|
get_object_type_representation(gds, &target_model.data_type)?;
|
||||||
|
|
||||||
|
// Build relationship field in filter expression only when
|
||||||
|
// the target_model is backed by a source
|
||||||
|
if let Some(target_source) = &target_model.source {
|
||||||
|
let target_model_source =
|
||||||
|
ModelTargetSource::from_model_source(target_source, relationship)?;
|
||||||
|
|
||||||
|
// filter expression with relationships is currently only supported for local relationships
|
||||||
|
if let RelationshipExecutionCategory::Local = relationship_execution_category(
|
||||||
|
&object_boolean_expression_type.data_connector_link,
|
||||||
|
&target_source.data_connector,
|
||||||
|
&target_model_source.capabilities,
|
||||||
|
) {
|
||||||
|
if target_source.data_connector.name
|
||||||
|
== object_boolean_expression_type.data_connector_name
|
||||||
|
{
|
||||||
|
// If the relationship target model does not have filterExpressionType do not include
|
||||||
|
// it in the source model filter expression input type.
|
||||||
|
if let Some(ref target_model_filter_expression) = &target_model
|
||||||
|
.clone()
|
||||||
|
.filter_expression_type
|
||||||
|
.and_then(|ref object_boolean_expression_type| {
|
||||||
|
object_boolean_expression_type.clone().graphql
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let target_model_filter_expression_type_name =
|
||||||
|
&target_model_filter_expression.type_name;
|
||||||
|
|
||||||
|
let annotation = FilterRelationshipAnnotation {
|
||||||
|
source_type: relationship.source.clone(),
|
||||||
|
relationship_name: relationship.name.clone(),
|
||||||
|
target_source: target_model_source.clone(),
|
||||||
|
target_type: target_typename.clone(),
|
||||||
|
target_model_name: target_model.name.clone(),
|
||||||
|
relationship_type: relationship_type.clone(),
|
||||||
|
mappings: mappings.clone(),
|
||||||
|
source_data_connector: object_boolean_expression_type
|
||||||
|
.data_connector_link
|
||||||
|
.clone(),
|
||||||
|
source_type_mappings: object_boolean_expression_type
|
||||||
|
.type_mappings
|
||||||
|
.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let namespace_annotations =
|
||||||
|
permissions::get_model_relationship_namespace_annotations(
|
||||||
|
target_model,
|
||||||
|
object_type_representation,
|
||||||
|
target_object_type_representation,
|
||||||
|
mappings,
|
||||||
|
&gds.metadata.object_types,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
input_fields.insert(
|
||||||
|
rel_name.clone(),
|
||||||
|
builder.conditional_namespaced(
|
||||||
|
gql_schema::InputField::<GDS>::new(
|
||||||
|
rel_name.clone(),
|
||||||
|
None,
|
||||||
|
types::Annotation::Input(InputAnnotation::BooleanExpression(
|
||||||
|
BooleanExpressionAnnotation
|
||||||
|
::BooleanExpressionArgument {
|
||||||
|
field:
|
||||||
|
types::ModelFilterArgument::RelationshipField(
|
||||||
|
annotation,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
ast::TypeContainer::named_null(
|
||||||
|
gql_schema::RegisteredTypeName::new(
|
||||||
|
target_model_filter_expression_type_name.0.clone(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
gql_schema::DeprecationStatus::NotDeprecated,
|
||||||
|
),
|
||||||
|
namespace_annotations
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(gql_schema::TypeInfo::InputObject(
|
||||||
|
gql_schema::InputObject::new(type_name.clone(), None, input_fields, Vec::new()),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(crate::schema::Error::InternalBooleanExpressionNotFound {
|
||||||
|
type_name: gds_type_name.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_scalar_comparison_input_type(
|
||||||
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
|
comparison_expression: &resolved::boolean_expression::ComparisonExpressionInfo,
|
||||||
|
) -> Result<gql_schema::RegisteredTypeName, Error> {
|
||||||
|
let graphql_type_name = comparison_expression.type_name.clone();
|
||||||
|
let mut operators = Vec::new();
|
||||||
|
for (op_name, input_type) in &comparison_expression.operators {
|
||||||
|
let op_name = mk_name(op_name.as_str())?;
|
||||||
|
operators.push((op_name, input_type.clone()))
|
||||||
|
}
|
||||||
|
Ok(
|
||||||
|
builder.register_type(TypeId::ScalarTypeComparisonExpression {
|
||||||
|
scalar_type_name: comparison_expression.scalar_type_name.clone(),
|
||||||
|
graphql_type_name,
|
||||||
|
operators,
|
||||||
|
is_null_operator_name: mk_name(&comparison_expression.is_null_operator_name)?,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
@ -1,20 +1,13 @@
|
|||||||
use hasura_authn_core::Role;
|
|
||||||
use lang_graphql::ast::common as ast;
|
use lang_graphql::ast::common as ast;
|
||||||
use lang_graphql::schema::{self as gql_schema, InputField, Namespaced};
|
use lang_graphql::schema::{self as gql_schema, InputField, Namespaced};
|
||||||
use open_dds::models::ModelName;
|
use open_dds::types::CustomTypeName;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use super::types::output_type::get_object_type_representation;
|
use super::types::input_type;
|
||||||
use super::types::output_type::relationship::{FilterRelationshipAnnotation, ModelTargetSource};
|
|
||||||
use super::types::{input_type, InputAnnotation, ModelInputAnnotation, TypeId};
|
|
||||||
use crate::metadata::resolved;
|
use crate::metadata::resolved;
|
||||||
use crate::metadata::resolved::model::ComparisonExpressionInfo;
|
|
||||||
use crate::metadata::resolved::relationship::{
|
|
||||||
relationship_execution_category, RelationshipExecutionCategory, RelationshipTarget,
|
|
||||||
};
|
|
||||||
use crate::metadata::resolved::subgraph::{Qualified, QualifiedTypeReference};
|
use crate::metadata::resolved::subgraph::{Qualified, QualifiedTypeReference};
|
||||||
use crate::metadata::resolved::types::mk_name;
|
|
||||||
use crate::schema::permissions;
|
|
||||||
use crate::schema::types;
|
use crate::schema::types;
|
||||||
use crate::schema::GDS;
|
use crate::schema::GDS;
|
||||||
|
|
||||||
@ -22,22 +15,22 @@ type Error = crate::schema::Error;
|
|||||||
|
|
||||||
pub fn get_where_expression_input_field(
|
pub fn get_where_expression_input_field(
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
model_name: Qualified<ModelName>,
|
gds_type_name: Qualified<CustomTypeName>,
|
||||||
boolean_expression_info: &resolved::model::ModelFilterExpression,
|
boolean_expression_info: &resolved::boolean_expression::BooleanExpression,
|
||||||
) -> gql_schema::InputField<GDS> {
|
) -> gql_schema::InputField<GDS> {
|
||||||
gql_schema::InputField::new(
|
gql_schema::InputField::new(
|
||||||
boolean_expression_info
|
boolean_expression_info
|
||||||
.filter_graphql_config
|
.graphql_config
|
||||||
.where_field_name
|
.where_field_name
|
||||||
.clone(),
|
.clone(),
|
||||||
None,
|
None,
|
||||||
types::Annotation::Input(types::InputAnnotation::Model(
|
types::Annotation::Input(types::InputAnnotation::BooleanExpression(
|
||||||
types::ModelInputAnnotation::ModelFilterExpression,
|
types::BooleanExpressionAnnotation::BooleanExpression,
|
||||||
)),
|
)),
|
||||||
ast::TypeContainer::named_null(builder.register_type(
|
ast::TypeContainer::named_null(builder.register_type(
|
||||||
types::TypeId::ModelBooleanExpression {
|
types::TypeId::InputObjectBooleanExpressionType {
|
||||||
model_name,
|
gds_type_name,
|
||||||
graphql_type_name: boolean_expression_info.where_type_name.clone(),
|
graphql_type_name: boolean_expression_info.type_name.clone(),
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
None,
|
None,
|
||||||
@ -45,257 +38,6 @@ pub fn get_where_expression_input_field(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_model_filter_expression_input_schema(
|
|
||||||
gds: &GDS,
|
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
|
||||||
type_name: &ast::TypeName,
|
|
||||||
model_name: &Qualified<ModelName>,
|
|
||||||
) -> Result<gql_schema::TypeInfo<GDS>, Error> {
|
|
||||||
let model = gds.metadata.models.get(model_name).ok_or_else(|| {
|
|
||||||
crate::schema::Error::InternalModelNotFound {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
if let Some(boolean_expression_info) = &model.graphql_api.filter_expression {
|
|
||||||
let mut input_fields = BTreeMap::new();
|
|
||||||
|
|
||||||
// `_and`, `_or` or `_not` fields are available for all roles
|
|
||||||
let not_field_name = &boolean_expression_info
|
|
||||||
.filter_graphql_config
|
|
||||||
.not_operator_name;
|
|
||||||
|
|
||||||
input_fields.insert(
|
|
||||||
not_field_name.clone(),
|
|
||||||
builder.allow_all_namespaced(
|
|
||||||
gql_schema::InputField::<GDS>::new(
|
|
||||||
not_field_name.clone(),
|
|
||||||
None,
|
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
|
||||||
field: types::ModelFilterArgument::NotOp,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
ast::TypeContainer::named_null(gql_schema::RegisteredTypeName::new(
|
|
||||||
type_name.0.clone(),
|
|
||||||
)),
|
|
||||||
None,
|
|
||||||
gql_schema::DeprecationStatus::NotDeprecated,
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let and_field_name = &boolean_expression_info
|
|
||||||
.filter_graphql_config
|
|
||||||
.and_operator_name;
|
|
||||||
|
|
||||||
input_fields.insert(
|
|
||||||
and_field_name.clone(),
|
|
||||||
builder.allow_all_namespaced(
|
|
||||||
gql_schema::InputField::<GDS>::new(
|
|
||||||
and_field_name.clone(),
|
|
||||||
None,
|
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
|
||||||
field: types::ModelFilterArgument::AndOp,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
ast::TypeContainer::list_null(ast::TypeContainer::named_non_null(
|
|
||||||
gql_schema::RegisteredTypeName::new(type_name.0.clone()),
|
|
||||||
)),
|
|
||||||
None,
|
|
||||||
gql_schema::DeprecationStatus::NotDeprecated,
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let or_field_name = &boolean_expression_info
|
|
||||||
.filter_graphql_config
|
|
||||||
.or_operator_name;
|
|
||||||
input_fields.insert(
|
|
||||||
or_field_name.clone(),
|
|
||||||
builder.allow_all_namespaced(
|
|
||||||
gql_schema::InputField::<GDS>::new(
|
|
||||||
or_field_name.clone(),
|
|
||||||
None,
|
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
|
||||||
field: types::ModelFilterArgument::OrOp,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
ast::TypeContainer::list_null(ast::TypeContainer::named_non_null(
|
|
||||||
gql_schema::RegisteredTypeName::new(type_name.0.clone()),
|
|
||||||
)),
|
|
||||||
None,
|
|
||||||
gql_schema::DeprecationStatus::NotDeprecated,
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let object_type_representation = get_object_type_representation(gds, &model.data_type)?;
|
|
||||||
|
|
||||||
// column fields
|
|
||||||
if let Some(model_filter_expression) = model.graphql_api.filter_expression.as_ref() {
|
|
||||||
for (field_name, comparison_expression) in &model_filter_expression.scalar_fields {
|
|
||||||
let field_graphql_name = mk_name(field_name.clone().0.as_str())?;
|
|
||||||
let registered_type_name =
|
|
||||||
get_scalar_comparison_input_type(builder, comparison_expression)?;
|
|
||||||
let field_type = ast::TypeContainer::named_null(registered_type_name);
|
|
||||||
let annotation = types::Annotation::Input(InputAnnotation::Model(
|
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
|
||||||
field: types::ModelFilterArgument::Field {
|
|
||||||
ndc_column: comparison_expression.ndc_column.clone(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
));
|
|
||||||
let field_permissions: HashMap<Role, Option<types::NamespaceAnnotation>> =
|
|
||||||
permissions::get_allowed_roles_for_field(
|
|
||||||
object_type_representation,
|
|
||||||
field_name,
|
|
||||||
)
|
|
||||||
.map(|role| (role.clone(), None))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let input_field = builder.conditional_namespaced(
|
|
||||||
gql_schema::InputField::<GDS>::new(
|
|
||||||
field_graphql_name.clone(),
|
|
||||||
None,
|
|
||||||
annotation,
|
|
||||||
field_type,
|
|
||||||
None,
|
|
||||||
gql_schema::DeprecationStatus::NotDeprecated,
|
|
||||||
),
|
|
||||||
field_permissions,
|
|
||||||
);
|
|
||||||
input_fields.insert(field_graphql_name, input_field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// relationship fields
|
|
||||||
// TODO(naveen): Add support for command relationships
|
|
||||||
for (rel_name, relationship) in object_type_representation.relationships.iter() {
|
|
||||||
if let RelationshipTarget::Model {
|
|
||||||
model_name,
|
|
||||||
relationship_type,
|
|
||||||
target_typename,
|
|
||||||
mappings,
|
|
||||||
} = &relationship.target
|
|
||||||
{
|
|
||||||
let target_model = gds.metadata.models.get(model_name).ok_or_else(|| {
|
|
||||||
crate::schema::Error::InternalModelNotFound {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let target_object_type_representation =
|
|
||||||
get_object_type_representation(gds, &target_model.data_type)?;
|
|
||||||
|
|
||||||
// Build relationship field in filter expression only when both
|
|
||||||
// the target_model and source model are backed by a source
|
|
||||||
if let (Some(target_source), Some(model_source)) =
|
|
||||||
(&target_model.source, &model.source)
|
|
||||||
{
|
|
||||||
let target_model_source =
|
|
||||||
ModelTargetSource::from_model_source(target_source, relationship)?;
|
|
||||||
|
|
||||||
// filter expression with relationships is currently only supported for local relationships
|
|
||||||
if let RelationshipExecutionCategory::Local = relationship_execution_category(
|
|
||||||
&model_source.data_connector,
|
|
||||||
&target_source.data_connector,
|
|
||||||
&target_model_source.capabilities,
|
|
||||||
) {
|
|
||||||
if target_source.data_connector.name == model_source.data_connector.name {
|
|
||||||
// If the relationship target model does not have filterExpressionType do not include
|
|
||||||
// it in the source model filter expression input type.
|
|
||||||
if let Some(target_model_filter_expression) =
|
|
||||||
target_model.graphql_api.filter_expression.as_ref()
|
|
||||||
{
|
|
||||||
let target_model_filter_expression_type_name =
|
|
||||||
&target_model_filter_expression.where_type_name;
|
|
||||||
|
|
||||||
let annotation = FilterRelationshipAnnotation {
|
|
||||||
source_type: relationship.source.clone(),
|
|
||||||
relationship_name: relationship.name.clone(),
|
|
||||||
target_source: target_model_source.clone(),
|
|
||||||
target_type: target_typename.clone(),
|
|
||||||
target_model_name: target_model.name.clone(),
|
|
||||||
relationship_type: relationship_type.clone(),
|
|
||||||
mappings: mappings.clone(),
|
|
||||||
source_data_connector: model_source.data_connector.clone(),
|
|
||||||
source_type_mappings: model_source.type_mappings.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
input_fields.insert(
|
|
||||||
rel_name.clone(),
|
|
||||||
builder.conditional_namespaced(
|
|
||||||
gql_schema::InputField::<GDS>::new(
|
|
||||||
rel_name.clone(),
|
|
||||||
None,
|
|
||||||
types::Annotation::Input(InputAnnotation::Model(
|
|
||||||
ModelInputAnnotation::ModelFilterArgument {
|
|
||||||
field:
|
|
||||||
types::ModelFilterArgument::RelationshipField(
|
|
||||||
annotation,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
ast::TypeContainer::named_null(
|
|
||||||
gql_schema::RegisteredTypeName::new(
|
|
||||||
target_model_filter_expression_type_name.0.clone(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
gql_schema::DeprecationStatus::NotDeprecated,
|
|
||||||
),
|
|
||||||
permissions::get_model_relationship_namespace_annotations(
|
|
||||||
target_model,
|
|
||||||
object_type_representation,
|
|
||||||
target_object_type_representation,
|
|
||||||
mappings,
|
|
||||||
&gds.metadata.object_types,
|
|
||||||
)?,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(gql_schema::TypeInfo::InputObject(
|
|
||||||
gql_schema::InputObject::new(type_name.clone(), None, input_fields, Vec::new()),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Err(
|
|
||||||
crate::schema::Error::InternalModelFilterExpressionNotFound {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_scalar_comparison_input_type(
|
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
|
||||||
comparison_expression: &ComparisonExpressionInfo,
|
|
||||||
) -> Result<gql_schema::RegisteredTypeName, Error> {
|
|
||||||
let graphql_type_name = comparison_expression.type_name.clone();
|
|
||||||
let mut operators = Vec::new();
|
|
||||||
for (op_name, input_type) in &comparison_expression.operators {
|
|
||||||
let op_name = mk_name(op_name.as_str())?;
|
|
||||||
operators.push((op_name, input_type.clone()))
|
|
||||||
}
|
|
||||||
Ok(
|
|
||||||
builder.register_type(TypeId::ScalarTypeComparisonExpression {
|
|
||||||
scalar_type_name: comparison_expression.scalar_type_name.clone(),
|
|
||||||
graphql_type_name,
|
|
||||||
operators,
|
|
||||||
is_null_operator_name: mk_name(&comparison_expression.is_null_operator_name)?,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_scalar_comparison_input(
|
pub fn build_scalar_comparison_input(
|
||||||
gds: &GDS,
|
gds: &GDS,
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
|
@ -75,14 +75,20 @@ pub(crate) fn generate_select_many_arguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate and insert where argument
|
// generate and insert where argument
|
||||||
if let Some(filter_expression_info) = &model.graphql_api.filter_expression {
|
if let Some(object_boolean_expression_type) = &model.filter_expression_type {
|
||||||
let where_argument =
|
if let Some(boolean_expression) = &object_boolean_expression_type.graphql {
|
||||||
get_where_expression_input_field(builder, model.name.clone(), filter_expression_info);
|
let where_argument = get_where_expression_input_field(
|
||||||
|
builder,
|
||||||
|
object_boolean_expression_type.name.clone(),
|
||||||
|
boolean_expression,
|
||||||
|
);
|
||||||
|
|
||||||
arguments.insert(
|
arguments.insert(
|
||||||
where_argument.name.clone(),
|
where_argument.name.clone(),
|
||||||
builder.allow_all_namespaced(where_argument, None),
|
builder.allow_all_namespaced(where_argument, None),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(arguments)
|
Ok(arguments)
|
||||||
}
|
}
|
||||||
|
@ -200,10 +200,6 @@ pub enum ModelInputAnnotation {
|
|||||||
argument_type: QualifiedTypeReference,
|
argument_type: QualifiedTypeReference,
|
||||||
ndc_table_argument: Option<String>,
|
ndc_table_argument: Option<String>,
|
||||||
},
|
},
|
||||||
ModelFilterExpression,
|
|
||||||
ModelFilterArgument {
|
|
||||||
field: ModelFilterArgument,
|
|
||||||
},
|
|
||||||
ComparisonOperation {
|
ComparisonOperation {
|
||||||
operator: String,
|
operator: String,
|
||||||
},
|
},
|
||||||
@ -225,6 +221,13 @@ pub enum ModelInputAnnotation {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)]
|
||||||
|
/// Annotations of the input types/fields related to boolean expressions.
|
||||||
|
pub enum BooleanExpressionAnnotation {
|
||||||
|
BooleanExpression,
|
||||||
|
BooleanExpressionArgument { field: ModelFilterArgument },
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)]
|
||||||
/// Annotations for Relay input arguments/types.
|
/// Annotations for Relay input arguments/types.
|
||||||
pub enum RelayInputAnnotation {
|
pub enum RelayInputAnnotation {
|
||||||
@ -245,6 +248,7 @@ pub enum InputAnnotation {
|
|||||||
field_name: types::FieldName,
|
field_name: types::FieldName,
|
||||||
field_type: QualifiedTypeReference,
|
field_type: QualifiedTypeReference,
|
||||||
},
|
},
|
||||||
|
BooleanExpression(BooleanExpressionAnnotation),
|
||||||
CommandArgument {
|
CommandArgument {
|
||||||
argument_type: QualifiedTypeReference,
|
argument_type: QualifiedTypeReference,
|
||||||
ndc_func_proc_argument: Option<String>,
|
ndc_func_proc_argument: Option<String>,
|
||||||
@ -341,15 +345,15 @@ pub enum TypeId {
|
|||||||
gds_type_name: Qualified<types::CustomTypeName>,
|
gds_type_name: Qualified<types::CustomTypeName>,
|
||||||
graphql_type_name: ast::TypeName,
|
graphql_type_name: ast::TypeName,
|
||||||
},
|
},
|
||||||
|
InputObjectBooleanExpressionType {
|
||||||
|
gds_type_name: Qualified<types::CustomTypeName>,
|
||||||
|
graphql_type_name: ast::TypeName,
|
||||||
|
},
|
||||||
NodeRoot,
|
NodeRoot,
|
||||||
ModelArgumentsInput {
|
ModelArgumentsInput {
|
||||||
model_name: Qualified<models::ModelName>,
|
model_name: Qualified<models::ModelName>,
|
||||||
type_name: ast::TypeName,
|
type_name: ast::TypeName,
|
||||||
},
|
},
|
||||||
ModelBooleanExpression {
|
|
||||||
model_name: Qualified<models::ModelName>,
|
|
||||||
graphql_type_name: ast::TypeName,
|
|
||||||
},
|
|
||||||
ModelOrderByExpression {
|
ModelOrderByExpression {
|
||||||
model_name: Qualified<models::ModelName>,
|
model_name: Qualified<models::ModelName>,
|
||||||
graphql_type_name: ast::TypeName,
|
graphql_type_name: ast::TypeName,
|
||||||
@ -396,7 +400,7 @@ impl TypeId {
|
|||||||
} => graphql_type_name.clone(),
|
} => graphql_type_name.clone(),
|
||||||
TypeId::NodeRoot => ast::TypeName(mk_name!("Node")),
|
TypeId::NodeRoot => ast::TypeName(mk_name!("Node")),
|
||||||
TypeId::ModelArgumentsInput { type_name, .. } => type_name.clone(),
|
TypeId::ModelArgumentsInput { type_name, .. } => type_name.clone(),
|
||||||
TypeId::ModelBooleanExpression {
|
TypeId::InputObjectBooleanExpressionType {
|
||||||
graphql_type_name, ..
|
graphql_type_name, ..
|
||||||
} => graphql_type_name.clone(),
|
} => graphql_type_name.clone(),
|
||||||
TypeId::ScalarTypeComparisonExpression {
|
TypeId::ScalarTypeComparisonExpression {
|
||||||
|
@ -0,0 +1,138 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"AuthorManyNot": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author_id": 2,
|
||||||
|
"first_name": "John"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorManyOr": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorManyAnd": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorManyNestedBool": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author_id": 2,
|
||||||
|
"first_name": "John"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorTwoManyNot": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author_id": 2,
|
||||||
|
"first_name": "John"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorTwoManyOr": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorTwoManyAnd": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorTwoManyNestedBool": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author_id": 2,
|
||||||
|
"first_name": "John"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"AuthorManyNot": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author_id": 2,
|
||||||
|
"first_name": "John"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorManyOr": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorManyAnd": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorManyNestedBool": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author_id": 2,
|
||||||
|
"first_name": "John"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorTwoManyNot": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author_id": 2,
|
||||||
|
"first_name": "John"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorTwoManyOr": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorTwoManyAnd": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AuthorTwoManyNestedBool": [
|
||||||
|
{
|
||||||
|
"author_id": 1,
|
||||||
|
"first_name": "Peter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author_id": 2,
|
||||||
|
"first_name": "John"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,263 @@
|
|||||||
|
{
|
||||||
|
"version": "v2",
|
||||||
|
"subgraphs": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"kind": "DataConnectorScalarRepresentation",
|
||||||
|
"version": "v1",
|
||||||
|
"definition": {
|
||||||
|
"dataConnectorName": "db",
|
||||||
|
"dataConnectorScalarType": "String",
|
||||||
|
"representation": "String",
|
||||||
|
"graphql": {
|
||||||
|
"comparisonExpressionTypeName": "String_Comparison_Exp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"definition": {
|
||||||
|
"dataConnectorName": "db",
|
||||||
|
"dataConnectorScalarType": "int4",
|
||||||
|
"representation": "Int",
|
||||||
|
"graphql": {
|
||||||
|
"comparisonExpressionTypeName": "int4_comparison"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "DataConnectorScalarRepresentation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "ObjectType",
|
||||||
|
"version": "v1",
|
||||||
|
"definition": {
|
||||||
|
"name": "author",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "author_id",
|
||||||
|
"type": "Int!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "first_name",
|
||||||
|
"type": "String!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last_name",
|
||||||
|
"type": "String!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphql": {
|
||||||
|
"typeName": "Author"
|
||||||
|
},
|
||||||
|
"dataConnectorTypeMapping": [
|
||||||
|
{
|
||||||
|
"dataConnectorName": "db",
|
||||||
|
"dataConnectorObjectType": "author",
|
||||||
|
"fieldMapping": {
|
||||||
|
"author_id": {
|
||||||
|
"column": {
|
||||||
|
"name": "id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"column": {
|
||||||
|
"name": "first_name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"column": {
|
||||||
|
"name": "last_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "ObjectBooleanExpressionType",
|
||||||
|
"version": "v1",
|
||||||
|
"definition": {
|
||||||
|
"name": "author_bool_exp",
|
||||||
|
"objectType": "author",
|
||||||
|
"dataConnectorName": "db",
|
||||||
|
"dataConnectorObjectType": "author",
|
||||||
|
"comparableFields": [
|
||||||
|
{
|
||||||
|
"fieldName": "author_id",
|
||||||
|
"operators": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "first_name",
|
||||||
|
"operators": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "last_name",
|
||||||
|
"operators": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphql": {
|
||||||
|
"typeName": "AuthorFilter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Model",
|
||||||
|
"version": "v1",
|
||||||
|
"definition": {
|
||||||
|
"name": "AuthorsTwo",
|
||||||
|
"objectType": "author",
|
||||||
|
"source": {
|
||||||
|
"dataConnectorName": "db",
|
||||||
|
"collection": "author"
|
||||||
|
},
|
||||||
|
"graphql": {
|
||||||
|
"selectUniques": [],
|
||||||
|
"selectMany": {
|
||||||
|
"queryRootField": "AuthorTwoMany"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterExpressionType": "author_bool_exp",
|
||||||
|
"orderableFields": [
|
||||||
|
{
|
||||||
|
"fieldName": "author_id",
|
||||||
|
"orderByDirections": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "first_name",
|
||||||
|
"orderByDirections": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "last_name",
|
||||||
|
"orderByDirections": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Model",
|
||||||
|
"version": "v1",
|
||||||
|
"definition": {
|
||||||
|
"name": "Authors",
|
||||||
|
"objectType": "author",
|
||||||
|
"source": {
|
||||||
|
"dataConnectorName": "db",
|
||||||
|
"collection": "author"
|
||||||
|
},
|
||||||
|
"graphql": {
|
||||||
|
"selectUniques": [],
|
||||||
|
"selectMany": {
|
||||||
|
"queryRootField": "AuthorMany"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterExpressionType": "author_bool_exp",
|
||||||
|
"orderableFields": [
|
||||||
|
{
|
||||||
|
"fieldName": "author_id",
|
||||||
|
"orderByDirections": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "first_name",
|
||||||
|
"orderByDirections": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "last_name",
|
||||||
|
"orderByDirections": {
|
||||||
|
"enableAll": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "TypePermissions",
|
||||||
|
"version": "v1",
|
||||||
|
"definition": {
|
||||||
|
"typeName": "author",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"role": "admin",
|
||||||
|
"output": {
|
||||||
|
"allowedFields": [
|
||||||
|
"author_id",
|
||||||
|
"first_name",
|
||||||
|
"last_name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"output": {
|
||||||
|
"allowedFields": [
|
||||||
|
"author_id",
|
||||||
|
"first_name",
|
||||||
|
"last_name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "ModelPermissions",
|
||||||
|
"version": "v1",
|
||||||
|
"definition": {
|
||||||
|
"modelName": "Authors",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"role": "admin",
|
||||||
|
"select": {
|
||||||
|
"filter": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"select": {
|
||||||
|
"filter": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "ModelPermissions",
|
||||||
|
"version": "v1",
|
||||||
|
"definition": {
|
||||||
|
"modelName": "AuthorsTwo",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"role": "admin",
|
||||||
|
"select": {
|
||||||
|
"filter": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"select": {
|
||||||
|
"filter": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
query {
|
||||||
|
AuthorManyNot: AuthorMany(where: {_not: {first_name: {_is_null: true}}}) {
|
||||||
|
author_id first_name
|
||||||
|
}
|
||||||
|
AuthorManyOr: AuthorMany(where: {_or: [{first_name: {_like: "Pet%"}}]}) {
|
||||||
|
author_id first_name
|
||||||
|
}
|
||||||
|
AuthorManyAnd: AuthorMany(where: {_and: [{first_name: {_like: "Pet%"}}]}) {
|
||||||
|
author_id first_name
|
||||||
|
}
|
||||||
|
AuthorManyNestedBool: AuthorMany(where: {_or: [{_and: [{author_id: {_eq: 1}}, {first_name: {_eq: "Peter"}}]}, {_and: [{author_id: {_eq: 2}}, {first_name: {_eq: "John"}}]}]}){
|
||||||
|
author_id
|
||||||
|
first_name
|
||||||
|
}
|
||||||
|
AuthorTwoManyNot: AuthorTwoMany(where: {_not: {first_name: {_is_null: true}}}) {
|
||||||
|
author_id first_name
|
||||||
|
}
|
||||||
|
AuthorTwoManyOr: AuthorTwoMany(where: {_or: [{first_name: {_like: "Pet%"}}]}) {
|
||||||
|
author_id first_name
|
||||||
|
}
|
||||||
|
AuthorTwoManyAnd: AuthorTwoMany(where: {_and: [{first_name: {_like: "Pet%"}}]}) {
|
||||||
|
author_id first_name
|
||||||
|
}
|
||||||
|
AuthorTwoManyNestedBool: AuthorTwoMany(where: {_or: [{_and: [{author_id: {_eq: 1}}, {first_name: {_eq: "Peter"}}]}, {_and: [{author_id: {_eq: 2}}, {first_name: {_eq: "John"}}]}]}){
|
||||||
|
author_id
|
||||||
|
first_name
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"x-hasura-role": "admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x-hasura-role": "user",
|
||||||
|
"x-hasura-user-id": "2"
|
||||||
|
}
|
||||||
|
]
|
@ -206,6 +206,15 @@ fn test_model_select_many_where() -> anyhow::Result<()> {
|
|||||||
common::test_execution_expectation(test_path_string, &[common_metadata_path_string])
|
common::test_execution_expectation(test_path_string, &[common_metadata_path_string])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the test here is that two Models can both use the same ObjectBooleanExpressionType without
|
||||||
|
// errors
|
||||||
|
#[test]
|
||||||
|
fn test_model_select_many_shared_boolean_expression() -> anyhow::Result<()> {
|
||||||
|
let test_path_string = "execute/models/select_many/where/shared_boolean_expression";
|
||||||
|
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||||
|
common::test_execution_expectation(test_path_string, &[common_metadata_path_string])
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_model_select_many_where_is_null() -> anyhow::Result<()> {
|
fn test_model_select_many_where_is_null() -> anyhow::Result<()> {
|
||||||
let test_path_string = "execute/models/select_many/where/is_null";
|
let test_path_string = "execute/models/select_many/where/is_null";
|
||||||
|
@ -33,6 +33,7 @@ pub fn normalize<'q, 's, S: schema::SchemaContext, V: ValueSource<'q, 's, S>>(
|
|||||||
) -> Result<normalized::Value<'s, S>>
|
) -> Result<normalized::Value<'s, S>>
|
||||||
where
|
where
|
||||||
's: 'q,
|
's: 'q,
|
||||||
|
V: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
let normalized_value = match &location_type.type_().base {
|
let normalized_value = match &location_type.type_().base {
|
||||||
ast::BaseType::Named(_) => {
|
ast::BaseType::Named(_) => {
|
||||||
@ -191,6 +192,7 @@ fn normalize_input_object<'q, 's, S: schema::SchemaContext, V: ValueSource<'q, '
|
|||||||
) -> Result<normalized::Value<'s, S>>
|
) -> Result<normalized::Value<'s, S>>
|
||||||
where
|
where
|
||||||
's: 'q,
|
's: 'q,
|
||||||
|
V: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
// let (normalized_object, required_field_count) = value.fold_key_values(
|
// let (normalized_object, required_field_count) = value.fold_key_values(
|
||||||
let normalized_object = value.fold_key_values(
|
let normalized_object = value.fold_key_values(
|
||||||
|
Loading…
Reference in New Issue
Block a user