mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
Implement relationships in boolean exp, order_by and predicates (#254)
V3_GIT_ORIGIN_REV_ID: bc0fb85552f141f7e887d61c15c5e455f87ac02a
This commit is contained in:
parent
fed4371f84
commit
d177c6ffdb
@ -1,6 +1,7 @@
|
||||
use gql::{ast::common as ast, http::GraphQLError};
|
||||
use lang_graphql as gql;
|
||||
use open_dds::{
|
||||
models::ModelName,
|
||||
relationships::RelationshipName,
|
||||
session_variables::SessionVariable,
|
||||
types::{CustomTypeName, FieldName},
|
||||
@ -23,6 +24,32 @@ pub enum InternalDeveloperError {
|
||||
field_name: ast::Name,
|
||||
},
|
||||
|
||||
#[error("No source data connector specified for relationship argument field {argument_name} of type {type_name}")]
|
||||
NoTargetSourceDataConnectorForRelationshipArgument {
|
||||
argument_name: ast::Name,
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
},
|
||||
|
||||
#[error("No source model specified for relationship argument field {argument_name} of type {type_name}")]
|
||||
NoSourceModelForRelationshipArgument {
|
||||
argument_name: ast::Name,
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
},
|
||||
|
||||
#[error("No data connector specified for source model {source_model_name} which has a relationship argument field {argument_name} of type {type_name}")]
|
||||
NoModelSourceDataConnectorForRelationshipArgument {
|
||||
argument_name: ast::Name,
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
source_model_name: Qualified<ModelName>,
|
||||
},
|
||||
|
||||
#[error("No type mappings specified for source model {source_model_name} which has a relationship argument field {argument_name} of type {type_name}")]
|
||||
NoModelSourceTypeMappingsForRelationshipArgument {
|
||||
argument_name: ast::Name,
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
source_model_name: Qualified<ModelName>,
|
||||
},
|
||||
|
||||
#[error("No function/procedure specified for command field {field_name} of type {type_name}")]
|
||||
NoFunctionOrProcedure {
|
||||
type_name: ast::TypeName,
|
||||
|
@ -1,104 +1,219 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use lang_graphql::ast::common as ast;
|
||||
use lang_graphql::normalized_ast;
|
||||
use ndc_client as gdc;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::execute::error;
|
||||
use crate::schema::types;
|
||||
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
||||
use crate::schema::types::output_type::relationship::FilterRelationshipAnnotation;
|
||||
use crate::schema::types::{self};
|
||||
use crate::schema::types::{InputAnnotation, ModelInputAnnotation};
|
||||
use crate::schema::GDS;
|
||||
|
||||
use super::relationship::LocalModelRelationshipInfo;
|
||||
use crate::execute::ir::selection_set::NDCRelationshipName;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ResolvedFilterExpression<'s> {
|
||||
pub expressions: Vec<gdc::models::Expression>,
|
||||
// relationships that were used in the filter expression. This is helpful
|
||||
// for collecting relatinships and sending collection_relationships
|
||||
pub relationships: BTreeMap<NDCRelationshipName, LocalModelRelationshipInfo<'s>>,
|
||||
}
|
||||
|
||||
/// Generates the IR for GraphQL 'where' boolean expression
|
||||
pub(crate) fn resolve_filter_expression(
|
||||
fields: &IndexMap<ast::Name, normalized_ast::InputField<'_, GDS>>,
|
||||
) -> Result<Vec<gdc::models::Expression>, error::Error> {
|
||||
pub(crate) fn resolve_filter_expression<'s>(
|
||||
fields: &IndexMap<ast::Name, normalized_ast::InputField<'s, GDS>>,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ResolvedFilterExpression<'s>, error::Error> {
|
||||
let mut expressions = Vec::new();
|
||||
for (_field_name, field) in fields {
|
||||
match field.info.generic {
|
||||
// "_and"
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field: types::ModelFilterArgument::AndOp,
|
||||
},
|
||||
)) => {
|
||||
let values = field.value.as_list()?;
|
||||
let expression = gdc::models::Expression::And {
|
||||
expressions: values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
Ok(gdc::models::Expression::And {
|
||||
expressions: resolve_filter_expression(value.as_object()?)?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<gdc::models::Expression>, error::Error>>()?,
|
||||
};
|
||||
expressions.push(expression);
|
||||
let mut relationships = BTreeMap::new();
|
||||
for field in fields.values() {
|
||||
let relationship_paths = Vec::new();
|
||||
let expression =
|
||||
build_filter_expression(field, relationship_paths, &mut relationships, usage_counts)?;
|
||||
expressions.extend(expression);
|
||||
}
|
||||
let resolved_filter_expression = ResolvedFilterExpression {
|
||||
expressions,
|
||||
relationships,
|
||||
};
|
||||
Ok(resolved_filter_expression)
|
||||
}
|
||||
|
||||
// Build the NDC filter expression by traversing the relationships when present
|
||||
pub(crate) fn build_filter_expression<'s>(
|
||||
field: &normalized_ast::InputField<'s, GDS>,
|
||||
// The path to access the relationship column. If the column is a
|
||||
// non-relationship column, this will be empty. The paths contains the names
|
||||
// of relationships (in order) that needs to be traversed to access the
|
||||
// column.
|
||||
mut relationship_paths: Vec<NDCRelationshipName>,
|
||||
relationships: &mut BTreeMap<NDCRelationshipName, LocalModelRelationshipInfo<'s>>,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<Vec<gdc::models::Expression>, error::Error> {
|
||||
match field.info.generic {
|
||||
// "_and"
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field: types::ModelFilterArgument::AndOp,
|
||||
},
|
||||
)) => {
|
||||
let mut expressions = Vec::new();
|
||||
let values = field.value.as_list()?;
|
||||
|
||||
for value in values.iter() {
|
||||
let resolved_filter_expression =
|
||||
resolve_filter_expression(value.as_object()?, usage_counts)?;
|
||||
expressions.extend(resolved_filter_expression.expressions);
|
||||
relationships.extend(resolved_filter_expression.relationships);
|
||||
}
|
||||
// "_or"
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field: types::ModelFilterArgument::OrOp,
|
||||
},
|
||||
)) => {
|
||||
let values = field.value.as_list()?;
|
||||
let expression = gdc::models::Expression::Or {
|
||||
expressions: values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
Ok(gdc::models::Expression::And {
|
||||
expressions: resolve_filter_expression(value.as_object()?)?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<gdc::models::Expression>, error::Error>>()?,
|
||||
};
|
||||
expressions.push(expression);
|
||||
|
||||
let expression = gdc::models::Expression::And { expressions };
|
||||
|
||||
Ok(vec![expression])
|
||||
}
|
||||
// "_or"
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field: types::ModelFilterArgument::OrOp,
|
||||
},
|
||||
)) => {
|
||||
let mut expressions = Vec::new();
|
||||
let values = field.value.as_list()?;
|
||||
|
||||
for value in values.iter() {
|
||||
let resolved_filter_expression =
|
||||
resolve_filter_expression(value.as_object()?, usage_counts)?;
|
||||
expressions.extend(resolved_filter_expression.expressions);
|
||||
relationships.extend(resolved_filter_expression.relationships);
|
||||
}
|
||||
// "_not"
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field: types::ModelFilterArgument::NotOp,
|
||||
},
|
||||
)) => {
|
||||
let value = field.value.as_object()?;
|
||||
expressions.push(gdc::models::Expression::Not {
|
||||
expression: Box::new(gdc::models::Expression::And {
|
||||
expressions: resolve_filter_expression(value)?,
|
||||
}),
|
||||
})
|
||||
}
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field: types::ModelFilterArgument::Field { ndc_column: column },
|
||||
},
|
||||
)) => {
|
||||
for (op_name, op_value) in field.value.as_object()? {
|
||||
let expression = match op_name.as_str() {
|
||||
"_eq" => build_binary_comparison_expression(
|
||||
gdc::models::BinaryComparisonOperator::Equal,
|
||||
|
||||
let expression = gdc::models::Expression::Or { expressions };
|
||||
|
||||
Ok(vec![expression])
|
||||
}
|
||||
// "_not"
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field: types::ModelFilterArgument::NotOp,
|
||||
},
|
||||
)) => {
|
||||
let mut expressions = Vec::new();
|
||||
let value = field.value.as_object()?;
|
||||
|
||||
let resolved_filter_expression = resolve_filter_expression(value, usage_counts)?;
|
||||
relationships.extend(resolved_filter_expression.relationships);
|
||||
|
||||
expressions.push(gdc::models::Expression::Not {
|
||||
expression: Box::new(gdc::models::Expression::And {
|
||||
expressions: resolved_filter_expression.expressions,
|
||||
}),
|
||||
});
|
||||
Ok(expressions)
|
||||
}
|
||||
// The column that we want to use for filtering. If the column happens
|
||||
// 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
|
||||
// column. The order decides how to access the column.
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field: types::ModelFilterArgument::Field { ndc_column: column },
|
||||
},
|
||||
)) => {
|
||||
let mut expressions = Vec::new();
|
||||
for (op_name, op_value) in field.value.as_object()? {
|
||||
let expression = match op_name.as_str() {
|
||||
"_eq" => build_binary_comparison_expression(
|
||||
gdc::models::BinaryComparisonOperator::Equal,
|
||||
column.clone(),
|
||||
&op_value.value,
|
||||
&relationship_paths,
|
||||
),
|
||||
"_is_null" => build_is_null_expression(
|
||||
column.clone(),
|
||||
&op_value.value,
|
||||
&relationship_paths,
|
||||
)?,
|
||||
other => {
|
||||
let operator = gdc::models::BinaryComparisonOperator::Other {
|
||||
name: other.to_string(),
|
||||
};
|
||||
build_binary_comparison_expression(
|
||||
operator,
|
||||
column.clone(),
|
||||
&op_value.value,
|
||||
),
|
||||
"_is_null" => build_is_null_expression(column.clone(), &op_value.value)?,
|
||||
other => {
|
||||
let operator = gdc::models::BinaryComparisonOperator::Other {
|
||||
name: other.to_string(),
|
||||
};
|
||||
build_binary_comparison_expression(
|
||||
operator,
|
||||
column.clone(),
|
||||
&op_value.value,
|
||||
)
|
||||
}
|
||||
};
|
||||
expressions.push(expression)
|
||||
}
|
||||
&relationship_paths,
|
||||
)
|
||||
}
|
||||
};
|
||||
expressions.push(expression)
|
||||
}
|
||||
annotation => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
})?,
|
||||
Ok(expressions)
|
||||
}
|
||||
// Relationship field used for filtering.
|
||||
// This relationship can either point to another relationship or a column.
|
||||
types::Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelFilterArgument {
|
||||
field:
|
||||
types::ModelFilterArgument::RelationshipField(FilterRelationshipAnnotation {
|
||||
relationship_name,
|
||||
relationship_type,
|
||||
source_type,
|
||||
source_data_connector,
|
||||
source_type_mappings,
|
||||
target_source,
|
||||
target_type,
|
||||
target_model_name,
|
||||
mappings,
|
||||
}),
|
||||
},
|
||||
)) => {
|
||||
// Add the target model being used in the usage counts
|
||||
count_model(target_model_name.clone(), usage_counts);
|
||||
|
||||
let ndc_relationship_name = NDCRelationshipName::new(source_type, relationship_name)?;
|
||||
relationships.insert(
|
||||
ndc_relationship_name.clone(),
|
||||
LocalModelRelationshipInfo {
|
||||
relationship_name,
|
||||
relationship_type,
|
||||
source_type,
|
||||
source_data_connector,
|
||||
source_type_mappings,
|
||||
target_source,
|
||||
target_type,
|
||||
mappings,
|
||||
},
|
||||
);
|
||||
|
||||
let mut expressions = Vec::new();
|
||||
|
||||
// This map contains the relationships or the columns of the
|
||||
// relationship that needs to be used for ordering.
|
||||
let argument_value_map = field.value.as_object()?;
|
||||
// Add the current relationship to the relationship paths.
|
||||
relationship_paths.push(ndc_relationship_name);
|
||||
// Keep track of relationship paths as we keep traversing down the
|
||||
// relationships.
|
||||
for argument in argument_value_map.values() {
|
||||
let expression = build_filter_expression(
|
||||
argument,
|
||||
relationship_paths.clone(),
|
||||
relationships,
|
||||
usage_counts,
|
||||
)?;
|
||||
expressions.extend(expression);
|
||||
}
|
||||
Ok(expressions)
|
||||
}
|
||||
annotation => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
})?,
|
||||
}
|
||||
Ok(expressions)
|
||||
}
|
||||
|
||||
/// Generate a binary comparison operator
|
||||
@ -106,11 +221,14 @@ fn build_binary_comparison_expression(
|
||||
operator: gdc::models::BinaryComparisonOperator,
|
||||
column: String,
|
||||
value: &normalized_ast::Value<'_, GDS>,
|
||||
relationship_paths: &Vec<NDCRelationshipName>,
|
||||
) -> gdc::models::Expression {
|
||||
let path_elements = build_path_elements(relationship_paths);
|
||||
|
||||
gdc::models::Expression::BinaryComparisonOperator {
|
||||
column: gdc::models::ComparisonTarget::Column {
|
||||
name: column,
|
||||
path: vec![],
|
||||
path: path_elements,
|
||||
},
|
||||
operator,
|
||||
value: gdc::models::ComparisonValue::Scalar {
|
||||
@ -123,12 +241,15 @@ fn build_binary_comparison_expression(
|
||||
fn build_is_null_expression(
|
||||
column: String,
|
||||
value: &normalized_ast::Value<'_, GDS>,
|
||||
relationship_paths: &Vec<NDCRelationshipName>,
|
||||
) -> Result<gdc::models::Expression, error::Error> {
|
||||
let path_elements = build_path_elements(relationship_paths);
|
||||
|
||||
// Build an 'IsNull' unary comparison expression
|
||||
let unary_comparison_expression = gdc::models::Expression::UnaryComparisonOperator {
|
||||
column: gdc::models::ComparisonTarget::Column {
|
||||
name: column,
|
||||
path: vec![],
|
||||
path: path_elements,
|
||||
},
|
||||
operator: gdc::models::UnaryComparisonOperator::IsNull,
|
||||
};
|
||||
@ -144,3 +265,21 @@ fn build_is_null_expression(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_path_elements(
|
||||
relationship_paths: &Vec<NDCRelationshipName>,
|
||||
) -> Vec<gdc::models::PathElement> {
|
||||
let mut path_elements = Vec::new();
|
||||
for path in relationship_paths {
|
||||
path_elements.push(gdc::models::PathElement {
|
||||
relationship: path.0.clone(),
|
||||
arguments: BTreeMap::new(),
|
||||
// 'AND' predicate indicates that the column can be accessed
|
||||
// by joining all the relationships paths provided
|
||||
predicate: Box::new(gdc::models::Expression::And {
|
||||
expressions: Vec::new(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
path_elements
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ use open_dds::types::CustomTypeName;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::filter::ResolvedFilterExpression;
|
||||
use super::order_by::ResolvedOrderBy;
|
||||
use super::permissions;
|
||||
use super::selection_set;
|
||||
use crate::execute::error;
|
||||
@ -28,7 +30,7 @@ pub struct ModelSelection<'s> {
|
||||
pub(crate) arguments: BTreeMap<String, ndc::models::Argument>,
|
||||
|
||||
// The boolean expression that would fetch a single row from this model
|
||||
pub(crate) filter_clause: Vec<ndc::models::Expression>,
|
||||
pub(crate) filter_clause: ResolvedFilterExpression<'s>,
|
||||
|
||||
// Limit
|
||||
pub(crate) limit: Option<u32>,
|
||||
@ -37,7 +39,7 @@ pub struct ModelSelection<'s> {
|
||||
pub(crate) offset: Option<u32>,
|
||||
|
||||
// Order by
|
||||
pub(crate) order_by: Option<ndc::models::OrderBy>,
|
||||
pub(crate) order_by: Option<ResolvedOrderBy<'s>>,
|
||||
|
||||
// Fields requested from the model
|
||||
pub(crate) selection: selection_set::ResultSelectionSet<'s>,
|
||||
@ -50,21 +52,30 @@ pub(crate) fn model_selection_ir<'s>(
|
||||
data_type: &Qualified<CustomTypeName>,
|
||||
model_source: &'s resolved::model::ModelSource,
|
||||
arguments: BTreeMap<String, ndc::models::Argument>,
|
||||
mut filter_clauses: Vec<ndc::models::Expression>,
|
||||
permissions_predicate: &resolved::model::FilterPermission,
|
||||
mut filter_clauses: ResolvedFilterExpression<'s>,
|
||||
permissions_predicate: &'s resolved::model::FilterPermission,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
order_by: Option<ndc::models::OrderBy>,
|
||||
order_by: Option<ResolvedOrderBy<'s>>,
|
||||
session_variables: &SessionVariables,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ModelSelection<'s>, error::Error> {
|
||||
match permissions_predicate {
|
||||
resolved::model::FilterPermission::AllowAll => {}
|
||||
resolved::model::FilterPermission::Filter(predicate) => {
|
||||
filter_clauses.push(permissions::process_model_predicate(
|
||||
let permissions_predicate_relationship_paths = Vec::new();
|
||||
let mut permissions_predicate_relationships = BTreeMap::new();
|
||||
let processed_model_perdicate = permissions::process_model_predicate(
|
||||
predicate,
|
||||
session_variables,
|
||||
)?);
|
||||
permissions_predicate_relationship_paths,
|
||||
&mut permissions_predicate_relationships,
|
||||
usage_counts,
|
||||
)?;
|
||||
filter_clauses.expressions.push(processed_model_perdicate);
|
||||
for (rel_name, rel_info) in permissions_predicate_relationships {
|
||||
filter_clauses.relationships.insert(rel_name, rel_info);
|
||||
}
|
||||
}
|
||||
};
|
||||
let field_mappings = model_source
|
||||
|
@ -1,61 +1,53 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use gdc::models::PathElement;
|
||||
use lang_graphql::normalized_ast::{self as normalized_ast, InputField};
|
||||
use ndc_client as gdc;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
||||
use crate::schema::types::output_type::relationship::OrderByRelationshipAnnotation;
|
||||
use crate::schema::types::{Annotation, InputAnnotation, ModelInputAnnotation};
|
||||
|
||||
use super::relationship::LocalModelRelationshipInfo;
|
||||
use super::selection_set::NDCRelationshipName;
|
||||
|
||||
use crate::execute::error;
|
||||
use crate::schema::types;
|
||||
use crate::schema::GDS;
|
||||
|
||||
pub fn build_ndc_order_by(
|
||||
args_field: &InputField<GDS>,
|
||||
) -> Result<gdc::models::OrderBy, error::Error> {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ResolvedOrderBy<'s> {
|
||||
pub(crate) order_by: gdc::models::OrderBy,
|
||||
// relationships that were used in the order_by expression. This is helpful
|
||||
// for collecting relatinships and sending collection_relationships
|
||||
pub(crate) relationships: BTreeMap<NDCRelationshipName, LocalModelRelationshipInfo<'s>>,
|
||||
}
|
||||
|
||||
pub(crate) fn build_ndc_order_by<'s>(
|
||||
args_field: &InputField<'s, GDS>,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ResolvedOrderBy<'s>, error::Error> {
|
||||
match &args_field.value {
|
||||
normalized_ast::Value::Object(arguments) => {
|
||||
let mut ndc_order_elements = Vec::new();
|
||||
for (_name, argument) in arguments {
|
||||
match argument.info.generic {
|
||||
Annotation::Input(InputAnnotation::Model(
|
||||
types::ModelInputAnnotation::ModelOrderByArgument { ndc_column },
|
||||
)) => {
|
||||
let order_by_value = argument.value.as_enum()?;
|
||||
let order_direction = match &order_by_value.info.generic {
|
||||
Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelOrderByDirection { direction },
|
||||
)) => match &direction {
|
||||
types::ModelOrderByDirection::Asc => {
|
||||
gdc::models::OrderDirection::Asc
|
||||
}
|
||||
types::ModelOrderByDirection::Desc => {
|
||||
gdc::models::OrderDirection::Desc
|
||||
}
|
||||
},
|
||||
&annotation => {
|
||||
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
})?
|
||||
}
|
||||
};
|
||||
|
||||
let order_element = gdc::models::OrderByElement {
|
||||
order_direction,
|
||||
target: gdc::models::OrderByTarget::Column {
|
||||
name: ndc_column.clone(),
|
||||
path: Vec::new(),
|
||||
},
|
||||
};
|
||||
|
||||
ndc_order_elements.push(order_element);
|
||||
}
|
||||
annotation => {
|
||||
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
let mut relationships = BTreeMap::new();
|
||||
// TODO: use argument.values?
|
||||
for argument in arguments.values() {
|
||||
let relationship_paths = Vec::new();
|
||||
let order_by_element = build_ndc_order_by_element(
|
||||
argument,
|
||||
relationship_paths,
|
||||
&mut relationships,
|
||||
usage_counts,
|
||||
)?;
|
||||
ndc_order_elements.extend(order_by_element);
|
||||
}
|
||||
Ok(gdc::models::OrderBy {
|
||||
elements: ndc_order_elements,
|
||||
Ok(ResolvedOrderBy {
|
||||
order_by: gdc::models::OrderBy {
|
||||
elements: ndc_order_elements,
|
||||
},
|
||||
relationships,
|
||||
})
|
||||
}
|
||||
_ => Err(error::InternalEngineError::InternalGeneric {
|
||||
@ -63,3 +55,163 @@ pub fn build_ndc_order_by(
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
// Build the NDC OrderByElement by traversing the relationships when present
|
||||
// For eg: If we have the following order_by query:
|
||||
// Track(order_by: {Album: {Artist: {ArtistId: Asc}, AlbumId: Asc}}, limit: 15)
|
||||
// where, '{Album: {Artist: {ArtistId: Asc}, AlbumId: Asc}}' will be annotated as 'ModelOrderByRelationshipArgument'
|
||||
// the `OrderByElement` will be:
|
||||
// [
|
||||
// gdc::models::OrderByElement {
|
||||
// order_direction: Asc,
|
||||
// target: gdc::models::OrderByTarget::Column {
|
||||
// name: "ArtistId",
|
||||
// path: ["TrackAlbum", "AlbumArtist"]
|
||||
// }
|
||||
// },
|
||||
// gdc::models::OrderByElement {
|
||||
// order_direction: Asc,
|
||||
// target: gdc::models::OrderByTarget::Column {
|
||||
// name: "AlbumId",
|
||||
// path: ["TrackAlbum"]
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
|
||||
pub(crate) fn build_ndc_order_by_element<'s>(
|
||||
argument: &InputField<'s, GDS>,
|
||||
// The path to access the relationship column. If the column is a
|
||||
// non-relationship column, this will be empty. The paths contains
|
||||
// the names of relationships (in order) that needs to be traversed
|
||||
// to access the column.
|
||||
mut relationship_paths: Vec<NDCRelationshipName>,
|
||||
relationships: &mut BTreeMap<NDCRelationshipName, LocalModelRelationshipInfo<'s>>,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<Vec<gdc::models::OrderByElement>, error::Error> {
|
||||
match argument.info.generic {
|
||||
// The column that we want to use for ordering. If the column happens 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 column
|
||||
Annotation::Input(InputAnnotation::Model(
|
||||
types::ModelInputAnnotation::ModelOrderByArgument { ndc_column },
|
||||
)) => {
|
||||
let order_by_value = argument.value.as_enum()?;
|
||||
let order_direction = match &order_by_value.info.generic {
|
||||
Annotation::Input(InputAnnotation::Model(
|
||||
ModelInputAnnotation::ModelOrderByDirection { direction },
|
||||
)) => match &direction {
|
||||
types::ModelOrderByDirection::Asc => gdc::models::OrderDirection::Asc,
|
||||
types::ModelOrderByDirection::Desc => gdc::models::OrderDirection::Desc,
|
||||
},
|
||||
&annotation => {
|
||||
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
})?
|
||||
}
|
||||
};
|
||||
|
||||
let mut order_by_element_path = Vec::new();
|
||||
// When using a nested relationship column, you'll have to provide all the relationships(paths)
|
||||
// NDC has to traverse to access the column. The ordering of that paths is important.
|
||||
// The order decides how to access the column.
|
||||
//
|
||||
// For example, if you have a model called `User` with a relationship column called `Posts`
|
||||
// which has a relationship column called `Comments` which has a non-relationship column
|
||||
// called `text`, you'll have to provide the following paths to access the `text` column:
|
||||
// ["UserPosts", "PostsComments"]
|
||||
for path in relationship_paths.iter() {
|
||||
order_by_element_path.push(PathElement {
|
||||
relationship: path.0.clone(),
|
||||
arguments: BTreeMap::new(),
|
||||
// 'AND' predicate indicates that the column can be accessed
|
||||
// by joining all the relationships paths provided
|
||||
predicate: Box::new(gdc::models::Expression::And {
|
||||
// TODO(naveen): Add expressions here, when we support sorting with predicates.
|
||||
//
|
||||
// There are two types of sorting:
|
||||
// 1. Sorting without predicates
|
||||
// 2. Sorting with predicates
|
||||
//
|
||||
// In the 1st sort, we sort all the elements of the results either in ascending
|
||||
// or descing order based on the order_by argument.
|
||||
//
|
||||
// In the 2nd sort, we want fetch the entire result but only sort a subset
|
||||
// of result and put those sorted set either at the beginning or at the end of the
|
||||
// result.
|
||||
//
|
||||
// Currently we only support the 1st type of sort. Hence we don't have any expressions/predicate.
|
||||
expressions: Vec::new(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
let order_element = gdc::models::OrderByElement {
|
||||
order_direction,
|
||||
// TODO(naveen): When aggregates are supported, extend this to support other gdc::models::OrderByTarget
|
||||
target: gdc::models::OrderByTarget::Column {
|
||||
name: ndc_column.clone(),
|
||||
path: order_by_element_path,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(vec![order_element])
|
||||
}
|
||||
// A relationship is being used to order the results. This relationship can
|
||||
// either point to another relationship or a column.
|
||||
Annotation::Input(InputAnnotation::Model(
|
||||
types::ModelInputAnnotation::ModelOrderByRelationshipArgument(
|
||||
OrderByRelationshipAnnotation {
|
||||
relationship_name,
|
||||
relationship_type,
|
||||
source_type,
|
||||
source_data_connector,
|
||||
source_type_mappings,
|
||||
target_source,
|
||||
target_type,
|
||||
target_model_name,
|
||||
mappings,
|
||||
},
|
||||
),
|
||||
)) => {
|
||||
let ndc_relationship_name = NDCRelationshipName::new(source_type, relationship_name)?;
|
||||
|
||||
relationships.insert(
|
||||
ndc_relationship_name.clone(),
|
||||
LocalModelRelationshipInfo {
|
||||
relationship_name,
|
||||
relationship_type,
|
||||
source_type,
|
||||
source_data_connector,
|
||||
source_type_mappings,
|
||||
target_source,
|
||||
target_type,
|
||||
mappings,
|
||||
},
|
||||
);
|
||||
|
||||
// Add the target model being used in the usage counts
|
||||
count_model(target_model_name.clone(), usage_counts);
|
||||
|
||||
// This map contains the relationships or the columns of the relationship that needs to be used for ordering.
|
||||
let argument_value_map = argument.value.as_object()?;
|
||||
let mut order_by_elements = Vec::new();
|
||||
|
||||
// Add the current relationship to the relationship paths.
|
||||
relationship_paths.push(ndc_relationship_name);
|
||||
|
||||
for argument in argument_value_map.values() {
|
||||
let order_by_element = build_ndc_order_by_element(
|
||||
argument,
|
||||
relationship_paths.clone(),
|
||||
relationships,
|
||||
usage_counts,
|
||||
)?;
|
||||
order_by_elements.extend(order_by_element);
|
||||
}
|
||||
Ok(order_by_elements)
|
||||
}
|
||||
annotation => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use hasura_authn_core::{SessionVariableValue, SessionVariables};
|
||||
use lang_graphql::normalized_ast;
|
||||
use ndc_client as gdc;
|
||||
@ -5,6 +7,7 @@ use ndc_client as gdc;
|
||||
use open_dds::{permissions::ValueExpression, types::InbuiltType};
|
||||
|
||||
use crate::execute::error::{Error, InternalDeveloperError, InternalEngineError, InternalError};
|
||||
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
||||
use crate::metadata::resolved;
|
||||
|
||||
use crate::metadata::resolved::subgraph::{
|
||||
@ -13,6 +16,9 @@ use crate::metadata::resolved::subgraph::{
|
||||
use crate::schema::types;
|
||||
use crate::schema::GDS;
|
||||
|
||||
use super::relationship::LocalModelRelationshipInfo;
|
||||
use super::selection_set::NDCRelationshipName;
|
||||
|
||||
/// Fetch filter expression from the namespace annotation
|
||||
/// of the field call. If the filter predicate namespace annotation
|
||||
/// is not found, then an error will be thrown.
|
||||
@ -37,9 +43,12 @@ pub(crate) fn get_select_filter_predicate<'s>(
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn process_model_predicate(
|
||||
model_predicate: &resolved::model::ModelPredicate,
|
||||
pub(crate) fn process_model_predicate<'s>(
|
||||
model_predicate: &'s resolved::model::ModelPredicate,
|
||||
session_variables: &SessionVariables,
|
||||
mut relationship_paths: Vec<NDCRelationshipName>,
|
||||
relationships: &mut BTreeMap<NDCRelationshipName, LocalModelRelationshipInfo<'s>>,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<gdc::models::Expression, Error> {
|
||||
match model_predicate {
|
||||
resolved::model::ModelPredicate::UnaryFieldComparison {
|
||||
@ -49,6 +58,7 @@ pub(crate) fn process_model_predicate(
|
||||
} => Ok(make_permission_unary_boolean_expression(
|
||||
ndc_column.clone(),
|
||||
operator,
|
||||
&relationship_paths,
|
||||
)?),
|
||||
resolved::model::ModelPredicate::BinaryFieldComparison {
|
||||
field: _,
|
||||
@ -62,9 +72,16 @@ pub(crate) fn process_model_predicate(
|
||||
operator,
|
||||
value,
|
||||
session_variables,
|
||||
&relationship_paths,
|
||||
)?),
|
||||
resolved::model::ModelPredicate::Not(predicate) => {
|
||||
let expr = process_model_predicate(predicate, session_variables)?;
|
||||
let expr = process_model_predicate(
|
||||
predicate,
|
||||
session_variables,
|
||||
relationship_paths,
|
||||
relationships,
|
||||
usage_counts,
|
||||
)?;
|
||||
Ok(gdc::models::Expression::Not {
|
||||
expression: Box::new(expr),
|
||||
})
|
||||
@ -72,27 +89,68 @@ pub(crate) fn process_model_predicate(
|
||||
resolved::model::ModelPredicate::And(predicates) => {
|
||||
let exprs = predicates
|
||||
.iter()
|
||||
.map(|p| process_model_predicate(p, session_variables))
|
||||
.map(|p| {
|
||||
process_model_predicate(
|
||||
p,
|
||||
session_variables,
|
||||
relationship_paths.clone(),
|
||||
relationships,
|
||||
usage_counts,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
Ok(gdc::models::Expression::And { expressions: exprs })
|
||||
}
|
||||
resolved::model::ModelPredicate::Or(predicates) => {
|
||||
let exprs = predicates
|
||||
.iter()
|
||||
.map(|p| process_model_predicate(p, session_variables))
|
||||
.map(|p| {
|
||||
process_model_predicate(
|
||||
p,
|
||||
session_variables,
|
||||
relationship_paths.clone(),
|
||||
relationships,
|
||||
usage_counts,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
Ok(gdc::models::Expression::Or { expressions: exprs })
|
||||
}
|
||||
// TODO: implement this
|
||||
// TODO: naveen: When we can use models in predicates, make sure to
|
||||
// include those models in the 'models_used' field of the IR's. This is
|
||||
// for tracking the models used in query.
|
||||
resolved::model::ModelPredicate::Relationship {
|
||||
name: _,
|
||||
predicate: _,
|
||||
} => Err(InternalEngineError::InternalGeneric {
|
||||
description: "'relationship' model predicate is not supported yet.".to_string(),
|
||||
})?,
|
||||
relationship_info,
|
||||
predicate,
|
||||
} => {
|
||||
let relationship_name = (NDCRelationshipName::new(
|
||||
&relationship_info.source_type,
|
||||
&relationship_info.relationship_name,
|
||||
))?;
|
||||
|
||||
relationship_paths.push(relationship_name.clone());
|
||||
relationships.insert(
|
||||
relationship_name.clone(),
|
||||
LocalModelRelationshipInfo {
|
||||
relationship_name: &relationship_info.relationship_name,
|
||||
relationship_type: &relationship_info.relationship_type,
|
||||
source_type: &relationship_info.source_type,
|
||||
source_data_connector: &relationship_info.source_data_connector,
|
||||
source_type_mappings: &relationship_info.source_type_mappings,
|
||||
target_source: &relationship_info.target_source,
|
||||
target_type: &relationship_info.target_type,
|
||||
mappings: &relationship_info.mappings,
|
||||
},
|
||||
);
|
||||
|
||||
// Add the target model being used in the usage counts
|
||||
count_model(relationship_info.target_model_name.clone(), usage_counts);
|
||||
|
||||
process_model_predicate(
|
||||
predicate,
|
||||
session_variables,
|
||||
relationship_paths,
|
||||
relationships,
|
||||
usage_counts,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,13 +160,15 @@ fn make_permission_binary_boolean_expression(
|
||||
operator: &ndc_client::models::BinaryComparisonOperator,
|
||||
value_expression: &ValueExpression,
|
||||
session_variables: &SessionVariables,
|
||||
relationship_paths: &Vec<NDCRelationshipName>,
|
||||
) -> Result<gdc::models::Expression, Error> {
|
||||
let path_elements = super::filter::build_path_elements(relationship_paths);
|
||||
let ndc_expression_value =
|
||||
make_value_from_value_expression(value_expression, argument_type, session_variables)?;
|
||||
Ok(gdc::models::Expression::BinaryComparisonOperator {
|
||||
column: gdc::models::ComparisonTarget::Column {
|
||||
name: ndc_column,
|
||||
path: vec![],
|
||||
path: path_elements,
|
||||
},
|
||||
operator: operator.clone(),
|
||||
value: ndc_expression_value,
|
||||
@ -118,11 +178,13 @@ fn make_permission_binary_boolean_expression(
|
||||
fn make_permission_unary_boolean_expression(
|
||||
ndc_column: String,
|
||||
operator: &ndc_client::models::UnaryComparisonOperator,
|
||||
relationship_paths: &Vec<NDCRelationshipName>,
|
||||
) -> Result<gdc::models::Expression, Error> {
|
||||
let path_elements = super::filter::build_path_elements(relationship_paths);
|
||||
Ok(gdc::models::Expression::UnaryComparisonOperator {
|
||||
column: gdc::models::ComparisonTarget::Column {
|
||||
name: ndc_column,
|
||||
path: vec![],
|
||||
path: path_elements,
|
||||
},
|
||||
operator: *operator,
|
||||
})
|
||||
|
@ -10,6 +10,7 @@ use serde::Serialize;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use crate::execute::error;
|
||||
use crate::execute::ir::filter::ResolvedFilterExpression;
|
||||
use crate::execute::ir::model_selection;
|
||||
use crate::execute::model_tracking::UsagesCounts;
|
||||
use crate::metadata::resolved;
|
||||
@ -122,7 +123,7 @@ pub(crate) fn relay_node_ir<'n, 's>(
|
||||
typename_mapping.type_name
|
||||
),
|
||||
})?;
|
||||
let filter_clauses = global_id
|
||||
let filter_clause_expressions = global_id
|
||||
.id
|
||||
.iter()
|
||||
.map(|(field_name, val)| {
|
||||
@ -148,6 +149,11 @@ pub(crate) fn relay_node_ir<'n, 's>(
|
||||
|
||||
let mut usage_counts = UsagesCounts::new();
|
||||
|
||||
let filter_clauses = ResolvedFilterExpression {
|
||||
expressions: filter_clause_expressions,
|
||||
relationships: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let model_selection = model_selection::model_selection_ir(
|
||||
&new_selection_set,
|
||||
&typename_mapping.type_name,
|
||||
|
@ -13,6 +13,7 @@ use std::collections::BTreeMap;
|
||||
use super::error;
|
||||
use crate::execute::ir::arguments;
|
||||
use crate::execute::ir::filter;
|
||||
use crate::execute::ir::filter::ResolvedFilterExpression;
|
||||
use crate::execute::ir::model_selection;
|
||||
use crate::execute::ir::order_by::build_ndc_order_by;
|
||||
use crate::execute::ir::permissions;
|
||||
@ -49,10 +50,17 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
|
||||
) -> Result<ModelSelectMany<'n, 's>, error::Error> {
|
||||
let mut limit = None;
|
||||
let mut offset = None;
|
||||
let mut filter_clause = Vec::new();
|
||||
let mut filter_clause = ResolvedFilterExpression {
|
||||
expressions: Vec::new(),
|
||||
relationships: BTreeMap::new(),
|
||||
};
|
||||
let mut order_by = None;
|
||||
let mut model_arguments = BTreeMap::new();
|
||||
|
||||
// Add the name of the root model
|
||||
let mut usage_counts = UsagesCounts::new();
|
||||
count_model(model_name.clone(), &mut usage_counts);
|
||||
|
||||
for argument in field_call.arguments.values() {
|
||||
match argument.info.generic {
|
||||
annotation @ Annotation::Input(types::InputAnnotation::Model(
|
||||
@ -65,7 +73,10 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
|
||||
offset = Some(argument.value.as_int_u32()?)
|
||||
}
|
||||
ModelInputAnnotation::ModelFilterExpression => {
|
||||
filter_clause = filter::resolve_filter_expression(argument.value.as_object()?)?
|
||||
filter_clause = filter::resolve_filter_expression(
|
||||
argument.value.as_object()?,
|
||||
&mut usage_counts,
|
||||
)?;
|
||||
}
|
||||
ModelInputAnnotation::ModelArgumentsExpression => match &argument.value {
|
||||
normalized_ast::Value::Object(arguments) => {
|
||||
@ -83,7 +94,7 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
|
||||
})?,
|
||||
},
|
||||
ModelInputAnnotation::ModelOrderByExpression => {
|
||||
order_by = Some(build_ndc_order_by(argument)?)
|
||||
order_by = Some(build_ndc_order_by(argument, &mut usage_counts)?)
|
||||
}
|
||||
_ => {
|
||||
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
@ -100,10 +111,6 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
|
||||
}
|
||||
}
|
||||
|
||||
// Add the name of the root model
|
||||
let mut usage_counts = UsagesCounts::new();
|
||||
count_model(model_name.clone(), &mut usage_counts);
|
||||
|
||||
let model_selection = model_selection::model_selection_ir(
|
||||
&field.selection_set,
|
||||
data_type,
|
||||
|
@ -2,6 +2,8 @@
|
||||
//!
|
||||
//! A 'select_one' operation fetches zero or one row from a model
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Generates the IR for a 'select_one' operation
|
||||
// TODO: Remove once TypeMapping has more than one variant
|
||||
use hasura_authn_core::SessionVariables;
|
||||
@ -12,6 +14,7 @@ use serde::Serialize;
|
||||
|
||||
use super::error;
|
||||
use crate::execute::ir::arguments;
|
||||
use crate::execute::ir::filter::ResolvedFilterExpression;
|
||||
use crate::execute::ir::model_selection;
|
||||
use crate::execute::ir::permissions;
|
||||
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
||||
@ -59,7 +62,7 @@ pub(crate) fn select_one_generate_ir<'n, 's>(
|
||||
description: format!("type '{:}' not found in source type_mappings", data_type),
|
||||
})?;
|
||||
|
||||
let mut filter_clause = vec![];
|
||||
let mut filter_clause_expressions = vec![];
|
||||
let mut model_argument_fields = Vec::new();
|
||||
for argument in field_call.arguments.values() {
|
||||
match argument.info.generic {
|
||||
@ -87,7 +90,7 @@ pub(crate) fn select_one_generate_ir<'n, 's>(
|
||||
value: argument.value.as_json(),
|
||||
},
|
||||
};
|
||||
filter_clause.push(ndc_expression);
|
||||
filter_clause_expressions.push(ndc_expression);
|
||||
}
|
||||
_ => Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
annotation: annotation.clone(),
|
||||
@ -108,6 +111,11 @@ pub(crate) fn select_one_generate_ir<'n, 's>(
|
||||
let mut usage_counts = UsagesCounts::new();
|
||||
count_model(model_name.clone(), &mut usage_counts);
|
||||
|
||||
let filter_clause = ResolvedFilterExpression {
|
||||
expressions: filter_clause_expressions,
|
||||
relationships: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let model_selection = model_selection::model_selection_ir(
|
||||
&field.selection_set,
|
||||
data_type,
|
||||
|
@ -11,16 +11,22 @@ use open_dds::{
|
||||
use ndc_client as ndc;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::order_by::build_ndc_order_by;
|
||||
use super::permissions;
|
||||
use super::selection_set::FieldSelection;
|
||||
use super::{
|
||||
commands::generate_function_based_command, filter::resolve_filter_expression,
|
||||
model_selection::model_selection_ir,
|
||||
filter::ResolvedFilterExpression, model_selection::model_selection_ir,
|
||||
};
|
||||
use super::{
|
||||
order_by::{build_ndc_order_by, ResolvedOrderBy},
|
||||
selection_set::NDCRelationshipName,
|
||||
};
|
||||
|
||||
use crate::execute::model_tracking::{count_model, UsagesCounts};
|
||||
use crate::metadata::resolved::subgraph::serialize_qualified_btreemap;
|
||||
use crate::metadata::resolved::{
|
||||
relationship::{relationship_execution_category, RelationshipExecutionCategory},
|
||||
subgraph::serialize_qualified_btreemap,
|
||||
};
|
||||
use crate::schema::types::output_type::relationship::{
|
||||
ModelRelationshipAnnotation, ModelTargetSource,
|
||||
};
|
||||
@ -40,11 +46,15 @@ use crate::{
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct LocalModelRelationshipInfo<'s> {
|
||||
pub annotation: &'s ModelRelationshipAnnotation,
|
||||
pub relationship_name: &'s RelationshipName,
|
||||
pub relationship_type: &'s RelationshipType,
|
||||
pub source_type: &'s Qualified<CustomTypeName>,
|
||||
pub source_data_connector: &'s resolved::data_connector::DataConnector,
|
||||
#[serde(serialize_with = "serialize_qualified_btreemap")]
|
||||
pub source_type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, resolved::types::TypeMapping>,
|
||||
pub target_source: &'s ModelTargetSource,
|
||||
pub target_type: &'s Qualified<CustomTypeName>,
|
||||
pub mappings: &'s Vec<resolved::relationship::RelationshipModelMapping>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@ -79,17 +89,22 @@ pub(crate) fn process_model_relationship_definition(
|
||||
relationship_info: &LocalModelRelationshipInfo,
|
||||
) -> Result<ndc::models::Relationship, error::Error> {
|
||||
let &LocalModelRelationshipInfo {
|
||||
annotation,
|
||||
relationship_name,
|
||||
relationship_type,
|
||||
source_type,
|
||||
source_data_connector,
|
||||
source_type_mappings,
|
||||
target_source,
|
||||
|
||||
target_type,
|
||||
mappings,
|
||||
} = relationship_info;
|
||||
|
||||
let mut column_mapping = BTreeMap::new();
|
||||
for resolved::relationship::RelationshipModelMapping {
|
||||
source_field: source_field_path,
|
||||
target_field: target_field_path,
|
||||
} in annotation.mappings.iter()
|
||||
} in mappings.iter()
|
||||
{
|
||||
if !matches!(
|
||||
relationship_execution_category(
|
||||
@ -103,14 +118,14 @@ pub(crate) fn process_model_relationship_definition(
|
||||
} else {
|
||||
let source_column = get_field_mapping_of_field_name(
|
||||
source_type_mappings,
|
||||
&annotation.source_type,
|
||||
&annotation.relationship_name,
|
||||
source_type,
|
||||
relationship_name,
|
||||
&source_field_path.field_name,
|
||||
)?;
|
||||
let target_column = get_field_mapping_of_field_name(
|
||||
&target_source.model.type_mappings,
|
||||
&annotation.target_type,
|
||||
&annotation.relationship_name,
|
||||
target_type,
|
||||
relationship_name,
|
||||
&target_field_path.field_name,
|
||||
)?;
|
||||
|
||||
@ -120,7 +135,7 @@ pub(crate) fn process_model_relationship_definition(
|
||||
{
|
||||
Err(error::InternalEngineError::MappingExistsInRelationship {
|
||||
source_column: source_field_path.field_name.clone(),
|
||||
relationship_name: annotation.relationship_name.clone(),
|
||||
relationship_name: relationship_name.clone(),
|
||||
})?
|
||||
}
|
||||
}
|
||||
@ -128,7 +143,7 @@ pub(crate) fn process_model_relationship_definition(
|
||||
let ndc_relationship = ndc_client::models::Relationship {
|
||||
column_mapping,
|
||||
relationship_type: {
|
||||
match annotation.relationship_type {
|
||||
match relationship_type {
|
||||
RelationshipType::Object => ndc_client::models::RelationshipType::Object,
|
||||
RelationshipType::Array => ndc_client::models::RelationshipType::Array,
|
||||
}
|
||||
@ -197,32 +212,6 @@ pub(crate) fn process_command_relationship_definition(
|
||||
Ok(ndc_relationship)
|
||||
}
|
||||
|
||||
enum RelationshipExecutionCategory {
|
||||
// Push down relationship definition to the data connector
|
||||
Local,
|
||||
// Use foreach in the data connector to fetch related rows for multiple objects in a single request
|
||||
RemoteForEach,
|
||||
}
|
||||
|
||||
#[allow(clippy::match_single_binding)]
|
||||
fn relationship_execution_category(
|
||||
source_connector: &resolved::data_connector::DataConnector,
|
||||
target_connector: &resolved::data_connector::DataConnector,
|
||||
relationship_capabilities: &resolved::relationship::RelationshipCapabilities,
|
||||
) -> RelationshipExecutionCategory {
|
||||
// It's a local relationship if the source and target connectors are the same and
|
||||
// the connector supports relationships.
|
||||
if target_connector.name == source_connector.name && relationship_capabilities.relationships {
|
||||
RelationshipExecutionCategory::Local
|
||||
} else {
|
||||
match relationship_capabilities.foreach {
|
||||
// TODO: When we support naive relationships for connectors not implementing foreach,
|
||||
// add another match arm / return enum variant
|
||||
() => RelationshipExecutionCategory::RemoteForEach,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_model_relationship_ir<'s>(
|
||||
field: &Field<'s, GDS>,
|
||||
annotation: &'s ModelRelationshipAnnotation,
|
||||
@ -237,7 +226,10 @@ pub(crate) fn generate_model_relationship_ir<'s>(
|
||||
|
||||
let mut limit = None;
|
||||
let mut offset = None;
|
||||
let mut filter_clause = Vec::new();
|
||||
let mut filter_clause = ResolvedFilterExpression {
|
||||
expressions: Vec::new(),
|
||||
relationships: BTreeMap::new(),
|
||||
};
|
||||
let mut order_by = None;
|
||||
|
||||
for argument in field_call.arguments.values() {
|
||||
@ -252,10 +244,13 @@ pub(crate) fn generate_model_relationship_ir<'s>(
|
||||
offset = Some(argument.value.as_int_u32()?)
|
||||
}
|
||||
ModelInputAnnotation::ModelFilterExpression => {
|
||||
filter_clause = resolve_filter_expression(argument.value.as_object()?)?
|
||||
filter_clause = resolve_filter_expression(
|
||||
argument.value.as_object()?,
|
||||
usage_counts,
|
||||
)?
|
||||
}
|
||||
ModelInputAnnotation::ModelOrderByExpression => {
|
||||
order_by = Some(build_ndc_order_by(argument)?)
|
||||
order_by = Some(build_ndc_order_by(argument, usage_counts)?)
|
||||
}
|
||||
_ => {
|
||||
return Err(error::InternalEngineError::UnexpectedAnnotation {
|
||||
@ -278,7 +273,6 @@ pub(crate) fn generate_model_relationship_ir<'s>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let target_source =
|
||||
annotation
|
||||
.target_source
|
||||
@ -385,10 +379,10 @@ pub(crate) fn build_local_model_relationship<'s>(
|
||||
data_connector: &'s resolved::data_connector::DataConnector,
|
||||
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, resolved::types::TypeMapping>,
|
||||
target_source: &'s ModelTargetSource,
|
||||
filter_clause: Vec<ndc::models::Expression>,
|
||||
filter_clause: ResolvedFilterExpression<'s>,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
order_by: Option<ndc::models::OrderBy>,
|
||||
order_by: Option<ResolvedOrderBy<'s>>,
|
||||
session_variables: &SessionVariables,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<FieldSelection<'s>, error::Error> {
|
||||
@ -406,24 +400,19 @@ pub(crate) fn build_local_model_relationship<'s>(
|
||||
usage_counts,
|
||||
)?;
|
||||
let rel_info = LocalModelRelationshipInfo {
|
||||
annotation,
|
||||
relationship_name: &annotation.relationship_name,
|
||||
relationship_type: &annotation.relationship_type,
|
||||
source_type: &annotation.source_type,
|
||||
source_data_connector: data_connector,
|
||||
source_type_mappings: type_mappings,
|
||||
target_source,
|
||||
target_type: &annotation.target_type,
|
||||
mappings: &annotation.mappings,
|
||||
};
|
||||
|
||||
// Relationship names needs to be unique across the IR. This is so that, the
|
||||
// NDC can use these names to figure out what joins to use.
|
||||
// A single "source type" can have only one relationship with a given name,
|
||||
// hence the relationship name in the IR is a tuple between the source type
|
||||
// and the relationship name.
|
||||
// Relationship name = (source_type, relationship_name)
|
||||
let relationship_name =
|
||||
serde_json::to_string(&(&annotation.source_type, &annotation.relationship_name))?;
|
||||
|
||||
Ok(FieldSelection::ModelRelationshipLocal {
|
||||
query: relationships_ir,
|
||||
name: relationship_name,
|
||||
name: NDCRelationshipName::new(&annotation.source_type, &annotation.relationship_name)?,
|
||||
relationship_info: rel_info,
|
||||
})
|
||||
}
|
||||
@ -461,12 +450,10 @@ pub(crate) fn build_local_command_relationship<'s>(
|
||||
// hence the relationship name in the IR is a tuple between the source type
|
||||
// and the relationship name.
|
||||
// Relationship name = (source_type, relationship_name)
|
||||
let relationship_name =
|
||||
serde_json::to_string(&(&annotation.source_type, &annotation.relationship_name))?;
|
||||
|
||||
Ok(FieldSelection::CommandRelationshipLocal {
|
||||
ir: relationships_ir,
|
||||
name: relationship_name,
|
||||
name: NDCRelationshipName::new(&annotation.source_type, &annotation.relationship_name)?,
|
||||
relationship_info: rel_info,
|
||||
})
|
||||
}
|
||||
@ -478,10 +465,10 @@ pub(crate) fn build_remote_relationship<'n, 's>(
|
||||
annotation: &'s ModelRelationshipAnnotation,
|
||||
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, resolved::types::TypeMapping>,
|
||||
target_source: &'s ModelTargetSource,
|
||||
filter_clause: Vec<ndc::models::Expression>,
|
||||
filter_clause: ResolvedFilterExpression<'s>,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
order_by: Option<ndc::models::OrderBy>,
|
||||
order_by: Option<ResolvedOrderBy<'s>>,
|
||||
session_variables: &SessionVariables,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<FieldSelection<'s>, error::Error> {
|
||||
@ -535,7 +522,10 @@ pub(crate) fn build_remote_relationship<'n, 's>(
|
||||
name: target_value_variable,
|
||||
},
|
||||
};
|
||||
remote_relationships_ir.filter_clause.push(comparison_exp);
|
||||
remote_relationships_ir
|
||||
.filter_clause
|
||||
.expressions
|
||||
.push(comparison_exp);
|
||||
}
|
||||
let rel_info = RemoteModelRelationshipInfo {
|
||||
annotation,
|
||||
|
@ -2,8 +2,10 @@ use hasura_authn_core::SessionVariables;
|
||||
use indexmap::IndexMap;
|
||||
use lang_graphql::ast::common::Alias;
|
||||
use lang_graphql::normalized_ast;
|
||||
use open_dds::relationships::RelationshipName;
|
||||
use open_dds::types::{CustomTypeName, FieldName};
|
||||
use serde::Serialize;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::commands::FunctionBasedCommand;
|
||||
@ -29,15 +31,15 @@ pub(crate) enum FieldSelection<'s> {
|
||||
},
|
||||
ModelRelationshipLocal {
|
||||
query: ModelSelection<'s>,
|
||||
/// Relationship names needs to be unique across the IR. This field contains
|
||||
/// the uniquely generated relationship name. `ModelRelationshipAnnotation`
|
||||
/// contains a relationship name but that is the name from the metadata.
|
||||
name: String,
|
||||
// Relationship names needs to be unique across the IR. This field contains
|
||||
// the uniquely generated relationship name. `ModelRelationshipAnnotation`
|
||||
// contains a relationship name but that is the name from the metadata.
|
||||
name: NDCRelationshipName,
|
||||
relationship_info: LocalModelRelationshipInfo<'s>,
|
||||
},
|
||||
CommandRelationshipLocal {
|
||||
ir: FunctionBasedCommand<'s>,
|
||||
name: String,
|
||||
name: NDCRelationshipName,
|
||||
relationship_info: LocalCommandRelationshipInfo<'s>,
|
||||
},
|
||||
ModelRelationshipRemote {
|
||||
@ -50,6 +52,38 @@ pub(crate) enum FieldSelection<'s> {
|
||||
},
|
||||
}
|
||||
|
||||
/// The unique relationship name that is passed to NDC
|
||||
// Relationship names needs to be unique across the IR. This is so that, the
|
||||
// NDC can use these names to figure out what joins to use.
|
||||
// A single "source type" can have only one relationship with a given name,
|
||||
// hence the relationship name in the IR is a tuple between the source type
|
||||
// and the relationship name.
|
||||
// Relationship name = (source_type, relationship_name)
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
derive_more::Display,
|
||||
JsonSchema,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
pub struct NDCRelationshipName(pub(crate) String);
|
||||
|
||||
impl NDCRelationshipName {
|
||||
pub fn new(
|
||||
source_type: &Qualified<CustomTypeName>,
|
||||
relationship_name: &RelationshipName,
|
||||
) -> Result<Self, error::Error> {
|
||||
let name = serde_json::to_string(&(source_type, relationship_name))?;
|
||||
Ok(NDCRelationshipName(name))
|
||||
}
|
||||
}
|
||||
|
||||
/// IR that represents the selected fields of an output type.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ResultSelectionSet<'s> {
|
||||
|
@ -19,8 +19,8 @@ pub(crate) fn ndc_query<'s, 'ir>(
|
||||
fields: Some(ndc_fields),
|
||||
limit: ir.limit,
|
||||
offset: ir.offset,
|
||||
order_by: ir.order_by.clone(),
|
||||
predicate: match ir.filter_clause.as_slice() {
|
||||
order_by: ir.order_by.as_ref().map(|x| x.order_by.clone()),
|
||||
predicate: match ir.filter_clause.expressions.as_slice() {
|
||||
[] => None,
|
||||
[expression] => Some(expression.clone()),
|
||||
expressions => Some(ndc::models::Expression::And {
|
||||
|
@ -34,7 +34,7 @@ pub enum Error {
|
||||
#[error("the data type {data_type:} for model {model_name:} has not been defined")]
|
||||
UnknownModelDataType {
|
||||
model_name: Qualified<ModelName>,
|
||||
data_type: CustomTypeName,
|
||||
data_type: Qualified<CustomTypeName>,
|
||||
},
|
||||
#[error(
|
||||
"the following argument in model {model_name:} is defined more than once: {argument_name:}"
|
||||
@ -198,6 +198,20 @@ pub enum Error {
|
||||
"model source is required for model '{model_name:}' to resolve select permission predicate"
|
||||
)]
|
||||
ModelSourceRequiredForPredicate { model_name: Qualified<ModelName> },
|
||||
#[error(
|
||||
"both model source for model '{source_model_name:}' and target source for model '{target_model_name}' are required to resolve select permission predicate with relationships"
|
||||
)]
|
||||
ModelAndTargetSourceRequiredForRelationshipPredicate {
|
||||
source_model_name: Qualified<ModelName>,
|
||||
target_model_name: Qualified<ModelName>,
|
||||
},
|
||||
#[error(
|
||||
"no relationship predicate is defined for relationship '{relationship_name:}' in model '{model_name:}'"
|
||||
)]
|
||||
NoPredicateDefinedForRelationshipPredicate {
|
||||
model_name: Qualified<ModelName>,
|
||||
relationship_name: RelationshipName,
|
||||
},
|
||||
#[error("unknown field '{field_name:}' used in select permissions of model '{model_name:}'")]
|
||||
UnknownFieldInSelectPermissionsDefinition {
|
||||
field_name: FieldName,
|
||||
@ -208,6 +222,18 @@ pub enum Error {
|
||||
field_name: FieldName,
|
||||
model_name: Qualified<ModelName>,
|
||||
},
|
||||
#[error("relationship '{relationship_name:}' used in select permissions of model '{model_name:}' does not exist on type {type_name:}")]
|
||||
UnknownRelationshipInSelectPermissionsPredicate {
|
||||
relationship_name: RelationshipName,
|
||||
model_name: Qualified<ModelName>,
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
},
|
||||
#[error("The model '{target_model_name:}' corresponding to the relationship '{relationship_name:}' used in select permissions of model '{model_name:}' is not defined")]
|
||||
UnknownModelUsedInRelationshipSelectPermissionsPredicate {
|
||||
model_name: Qualified<ModelName>,
|
||||
target_model_name: Qualified<ModelName>,
|
||||
relationship_name: RelationshipName,
|
||||
},
|
||||
#[error(
|
||||
"Invalid operator used in model '{model_name:}' select permission: '{operator_name:}'"
|
||||
)]
|
||||
@ -385,6 +411,12 @@ pub enum Error {
|
||||
relationship_name: RelationshipName,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
},
|
||||
#[error("The target data connector {data_connector_name} for relationship {relationship_name} on type {type_name} has not defined any capabilities")]
|
||||
NoRelationshipCapabilitiesDefined {
|
||||
type_name: Qualified<CustomTypeName>,
|
||||
relationship_name: RelationshipName,
|
||||
data_connector_name: Qualified<DataConnectorName>,
|
||||
},
|
||||
#[error("model {model_name:} with arguments is unsupported as a global ID source")]
|
||||
ModelWithArgumentsAsGlobalIdSource { model_name: Qualified<ModelName> },
|
||||
#[error("capabilities for the data connector {data_connector:} have not been defined")]
|
||||
|
@ -376,24 +376,37 @@ pub fn resolve_metadata(metadata: open_dds::Metadata) -> Result<Metadata, Error>
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Model permissions's predicate can include the relationship field,
|
||||
// hence Model permissions should be resolved after the relationships of a
|
||||
// model is resolved.
|
||||
for open_dds::accessor::QualifiedObject {
|
||||
subgraph,
|
||||
object: permissions,
|
||||
} in &metadata_accessor.model_permissions
|
||||
{
|
||||
let model_name = Qualified::new(subgraph.to_string(), permissions.model_name.clone());
|
||||
let model = models.get_mut(&model_name).ok_or_else(|| {
|
||||
Error::UnknownModelInModelSelectPermissions {
|
||||
model_name: model_name.clone(),
|
||||
}
|
||||
})?;
|
||||
let model =
|
||||
models
|
||||
.get(&model_name)
|
||||
.ok_or_else(|| Error::UnknownModelInModelSelectPermissions {
|
||||
model_name: model_name.clone(),
|
||||
})?;
|
||||
if model.select_permissions.is_none() {
|
||||
model.select_permissions = Some(resolve_model_select_permissions(
|
||||
let select_permissions = Some(resolve_model_select_permissions(
|
||||
model,
|
||||
subgraph,
|
||||
permissions,
|
||||
&data_connectors,
|
||||
&types,
|
||||
&models, // This is required to get the model for the relationship target
|
||||
)?);
|
||||
|
||||
let model = models.get_mut(&model_name).ok_or_else(|| {
|
||||
Error::UnknownModelInModelSelectPermissions {
|
||||
model_name: model_name.clone(),
|
||||
}
|
||||
})?;
|
||||
model.select_permissions = select_permissions;
|
||||
} else {
|
||||
return Err(Error::DuplicateModelSelectPermission {
|
||||
model_name: model_name.clone(),
|
||||
|
@ -13,10 +13,13 @@ use crate::metadata::resolved::types::{mk_name, FieldDefinition, TypeMapping};
|
||||
use crate::metadata::resolved::types::{
|
||||
resolve_type_mappings, ScalarTypeInfo, TypeMappingToResolve, TypeRepresentation,
|
||||
};
|
||||
use crate::schema::types::output_type::relationship::{
|
||||
ModelTargetSource, PredicateRelationshipAnnotation,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use lang_graphql::ast::common as ast;
|
||||
use lang_graphql::ast::common::{self as ast};
|
||||
use ndc_client as ndc;
|
||||
use open_dds::permissions::NullableModelPredicate;
|
||||
use open_dds::permissions::{NullableModelPredicate, RelationshipPredicate};
|
||||
use open_dds::{
|
||||
arguments::ArgumentName,
|
||||
data_connector::DataConnectorName,
|
||||
@ -25,13 +28,15 @@ use open_dds::{
|
||||
OperatorName, OrderableField,
|
||||
},
|
||||
permissions::{self, ModelPermissionsV1, Role, ValueExpression},
|
||||
relationships::RelationshipName,
|
||||
types::{CustomTypeName, FieldName, InbuiltType},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::iter;
|
||||
|
||||
use super::relationship::RelationshipTarget;
|
||||
use super::types::ObjectTypeRepresentation;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SelectUniqueGraphQlDefinition {
|
||||
pub query_root_field: ast::Name,
|
||||
@ -118,10 +123,9 @@ pub enum ModelPredicate {
|
||||
argument_type: QualifiedTypeReference,
|
||||
value: ValueExpression,
|
||||
},
|
||||
// TODO: Remote relationships are disallowed for now
|
||||
Relationship {
|
||||
name: RelationshipName,
|
||||
predicate: Option<Box<ModelPredicate>>,
|
||||
relationship_info: PredicateRelationshipAnnotation,
|
||||
predicate: Box<ModelPredicate>,
|
||||
},
|
||||
|
||||
And(Vec<ModelPredicate>),
|
||||
@ -215,19 +219,11 @@ pub fn resolve_model(
|
||||
let qualified_object_type_name =
|
||||
Qualified::new(subgraph.to_string(), model.object_type.to_owned());
|
||||
let qualified_model_name = Qualified::new(subgraph.to_string(), model.name.clone());
|
||||
let object_type_representation = match types.get(&qualified_object_type_name) {
|
||||
Some(TypeRepresentation::Object(object_type_representation)) => {
|
||||
Ok(object_type_representation)
|
||||
}
|
||||
Some(type_rep) => Err(Error::InvalidTypeRepresentation {
|
||||
model_name: qualified_model_name.clone(),
|
||||
type_representation: type_rep.clone(),
|
||||
}),
|
||||
None => Err(Error::UnknownModelDataType {
|
||||
model_name: qualified_model_name.clone(),
|
||||
data_type: model.object_type.clone(),
|
||||
}),
|
||||
}?;
|
||||
let object_type_representation = get_model_object_type_representation(
|
||||
types,
|
||||
&qualified_object_type_name,
|
||||
&qualified_model_name,
|
||||
)?;
|
||||
if model.global_id_source {
|
||||
// Check if there are any global fields present in the related
|
||||
// object type, if the model is marked as a global source.
|
||||
@ -406,6 +402,8 @@ fn resolve_model_predicate(
|
||||
subgraph: &str,
|
||||
data_connectors: &HashMap<Qualified<DataConnectorName>, DataConnectorContext>,
|
||||
fields: &IndexMap<FieldName, FieldDefinition>,
|
||||
types: &HashMap<Qualified<CustomTypeName>, TypeRepresentation>,
|
||||
models: &IndexMap<Qualified<ModelName>, Model>,
|
||||
// type_representation: &TypeRepresentation,
|
||||
) -> Result<ModelPredicate, Error> {
|
||||
match model_predicate {
|
||||
@ -491,29 +489,120 @@ fn resolve_model_predicate(
|
||||
})
|
||||
}
|
||||
}
|
||||
permissions::ModelPredicate::Relationship(_) => {
|
||||
Err(Error::UnsupportedFeature {
|
||||
message: "'relationship' model predicate is not supported yet.".into(),
|
||||
})
|
||||
/* TODO: uncomment when we support this
|
||||
permissions::ModelPredicate::Relationship(RelationshipPredicate { name, predicate }) => {
|
||||
if let Some(nested_predicate) = predicate {
|
||||
let resolved_predicate =
|
||||
resolve_model_predicate(nested_predicate, model_name, type_representation)?;
|
||||
Ok(ModelPredicate::Relationship {
|
||||
name: name.clone(),
|
||||
predicate: Some(Box::new(resolved_predicate)),
|
||||
})
|
||||
let object_type_representation =
|
||||
get_model_object_type_representation(types, &model.data_type, &model.name)?;
|
||||
let relationship_field_name = mk_name(&name.0)?;
|
||||
let relationship = &object_type_representation
|
||||
.relationships
|
||||
.get(&relationship_field_name)
|
||||
.ok_or_else(|| Error::UnknownRelationshipInSelectPermissionsPredicate {
|
||||
relationship_name: name.clone(),
|
||||
model_name: model.name.clone(),
|
||||
type_name: model.data_type.clone(),
|
||||
})?;
|
||||
|
||||
match &relationship.target {
|
||||
RelationshipTarget::Command { .. } => Err(Error::UnsupportedFeature {
|
||||
message: "Predicate cannot be built using command relationships"
|
||||
.to_string(),
|
||||
}),
|
||||
RelationshipTarget::Model {
|
||||
model_name,
|
||||
relationship_type,
|
||||
target_typename,
|
||||
mappings,
|
||||
} => {
|
||||
let target_model = models.get(model_name).ok_or_else(|| {
|
||||
Error::UnknownModelUsedInRelationshipSelectPermissionsPredicate {
|
||||
model_name: model.name.clone(),
|
||||
target_model_name: model_name.clone(),
|
||||
relationship_name: name.clone(),
|
||||
}
|
||||
})?;
|
||||
|
||||
// predicates with relationships is currently only supported for local relationships
|
||||
if let (Some(target_model_source), Some(model_source)) =
|
||||
(&target_model.source, &model.source)
|
||||
{
|
||||
if target_model_source.data_connector.name
|
||||
== model_source.data_connector.name
|
||||
{
|
||||
let target_source = ModelTargetSource::from_model_source(
|
||||
target_model_source,
|
||||
relationship,
|
||||
)
|
||||
.map_err(|_| Error::NoRelationshipCapabilitiesDefined {
|
||||
relationship_name: relationship.name.clone(),
|
||||
type_name: model.data_type.clone(),
|
||||
data_connector_name: target_model_source
|
||||
.data_connector
|
||||
.name
|
||||
.clone(),
|
||||
})?;
|
||||
|
||||
let annotation = PredicateRelationshipAnnotation {
|
||||
source_type: relationship.source.clone(),
|
||||
relationship_name: relationship.name.clone(),
|
||||
target_model_name: model_name.clone(),
|
||||
target_source: target_source.clone(),
|
||||
target_type: target_typename.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(),
|
||||
};
|
||||
|
||||
let target_model_predicate = resolve_model_predicate(
|
||||
nested_predicate,
|
||||
target_model,
|
||||
// local relationships exists in the same subgraph as the source model
|
||||
subgraph,
|
||||
data_connectors,
|
||||
&target_model.type_fields,
|
||||
types,
|
||||
models,
|
||||
)?;
|
||||
|
||||
Ok(ModelPredicate::Relationship {
|
||||
relationship_info: annotation,
|
||||
predicate: Box::new(target_model_predicate),
|
||||
})
|
||||
} else {
|
||||
Err(Error::UnsupportedFeature {
|
||||
message: "Predicate cannot be built using remote relationships"
|
||||
.to_string(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(
|
||||
Error::ModelAndTargetSourceRequiredForRelationshipPredicate {
|
||||
source_model_name: model.name.clone(),
|
||||
target_model_name: target_model.name.clone(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(ModelPredicate::Relationship {
|
||||
name: name.clone(),
|
||||
predicate: None,
|
||||
Err(Error::NoPredicateDefinedForRelationshipPredicate {
|
||||
model_name: model.name.clone(),
|
||||
relationship_name: name.clone(),
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
||||
permissions::ModelPredicate::Not(predicate) => {
|
||||
let resolved_predicate =
|
||||
resolve_model_predicate(predicate, model, subgraph, data_connectors, fields)?;
|
||||
let resolved_predicate = resolve_model_predicate(
|
||||
predicate,
|
||||
model,
|
||||
subgraph,
|
||||
data_connectors,
|
||||
fields,
|
||||
types,
|
||||
models,
|
||||
)?;
|
||||
Ok(ModelPredicate::Not(Box::new(resolved_predicate)))
|
||||
}
|
||||
permissions::ModelPredicate::And(predicates) => {
|
||||
@ -525,6 +614,8 @@ fn resolve_model_predicate(
|
||||
subgraph,
|
||||
data_connectors,
|
||||
fields,
|
||||
types,
|
||||
models,
|
||||
)?);
|
||||
}
|
||||
Ok(ModelPredicate::And(resolved_predicates))
|
||||
@ -538,6 +629,8 @@ fn resolve_model_predicate(
|
||||
subgraph,
|
||||
data_connectors,
|
||||
fields,
|
||||
types,
|
||||
models,
|
||||
)?);
|
||||
}
|
||||
Ok(ModelPredicate::Or(resolved_predicates))
|
||||
@ -550,6 +643,8 @@ pub fn resolve_model_select_permissions(
|
||||
subgraph: &str,
|
||||
model_permissions: &ModelPermissionsV1,
|
||||
data_connectors: &HashMap<Qualified<DataConnectorName>, DataConnectorContext>,
|
||||
types: &HashMap<Qualified<CustomTypeName>, TypeRepresentation>,
|
||||
models: &IndexMap<Qualified<ModelName>, Model>,
|
||||
) -> Result<HashMap<Role, SelectPermission>, Error> {
|
||||
let mut validated_permissions = HashMap::new();
|
||||
for model_permission in &model_permissions.permissions {
|
||||
@ -561,6 +656,8 @@ pub fn resolve_model_select_permissions(
|
||||
subgraph,
|
||||
data_connectors,
|
||||
&model.type_fields,
|
||||
types,
|
||||
models,
|
||||
)
|
||||
.map(FilterPermission::Filter)?,
|
||||
NullableModelPredicate::Null(()) => FilterPermission::AllowAll,
|
||||
@ -929,3 +1026,27 @@ pub fn resolve_model_source(
|
||||
ndc_validation::validate_ndc(&model.name, model, data_connector_context.schema)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the `ObjectTypeRepresentation` of the type identified with the
|
||||
/// `data_type`, it will throw an error if the type is not found to be an object
|
||||
/// or if the model has an unknown data type.
|
||||
pub(crate) fn get_model_object_type_representation<'s>(
|
||||
types: &'s HashMap<Qualified<CustomTypeName>, TypeRepresentation>,
|
||||
data_type: &Qualified<CustomTypeName>,
|
||||
model_name: &Qualified<ModelName>,
|
||||
) -> Result<&'s ObjectTypeRepresentation, crate::metadata::resolved::error::Error> {
|
||||
let object_type_representation = match types.get(data_type) {
|
||||
Some(TypeRepresentation::Object(object_type_representation)) => {
|
||||
Ok(object_type_representation)
|
||||
}
|
||||
Some(type_rep) => Err(Error::InvalidTypeRepresentation {
|
||||
model_name: model_name.clone(),
|
||||
type_representation: type_rep.clone(),
|
||||
}),
|
||||
None => Err(Error::UnknownModelDataType {
|
||||
model_name: model_name.clone(),
|
||||
data_type: data_type.clone(),
|
||||
}),
|
||||
}?;
|
||||
Ok(object_type_representation)
|
||||
}
|
||||
|
@ -58,6 +58,9 @@ pub struct RelationshipCommandMapping {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Relationship {
|
||||
pub name: RelationshipName,
|
||||
// `ast::Name` representation of `RelationshipName`. This is used to avoid
|
||||
// the recurring conversion between `RelationshipName` to `ast::Name` during
|
||||
// relationship IR generation
|
||||
pub field_name: ast::Name,
|
||||
pub source: Qualified<CustomTypeName>,
|
||||
pub target: RelationshipTarget,
|
||||
@ -72,6 +75,34 @@ pub struct RelationshipCapabilities {
|
||||
pub relationships: bool,
|
||||
}
|
||||
|
||||
pub enum RelationshipExecutionCategory {
|
||||
// Push down relationship definition to the data connector
|
||||
Local,
|
||||
// Use foreach in the data connector to fetch related rows for multiple objects in a single request
|
||||
RemoteForEach,
|
||||
}
|
||||
|
||||
#[allow(clippy::match_single_binding)]
|
||||
pub fn relationship_execution_category(
|
||||
source_connector: &DataConnector,
|
||||
target_connector: &DataConnector,
|
||||
target_source_relationship_capabilities: &RelationshipCapabilities,
|
||||
) -> RelationshipExecutionCategory {
|
||||
// It's a local relationship if the source and target connectors are the same and
|
||||
// the connector supports relationships.
|
||||
if target_connector.name == source_connector.name
|
||||
&& target_source_relationship_capabilities.relationships
|
||||
{
|
||||
RelationshipExecutionCategory::Local
|
||||
} else {
|
||||
match target_source_relationship_capabilities.foreach {
|
||||
// TODO: When we support naive relationships for connectors not implementing foreach,
|
||||
// add another match arm / return enum variant
|
||||
() => RelationshipExecutionCategory::RemoteForEach,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_relationship_source_mapping<'a>(
|
||||
relationship_name: &'a RelationshipName,
|
||||
source_type_name: &'a Qualified<CustomTypeName>,
|
||||
|
@ -37,6 +37,21 @@ pub enum QualifiedTypeName {
|
||||
Custom(Qualified<CustomTypeName>),
|
||||
}
|
||||
|
||||
pub fn serialize_optional_qualified_btreemap<T, V, S>(
|
||||
optional_map: &Option<BTreeMap<Qualified<T>, V>>,
|
||||
s: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
V: Serialize,
|
||||
T: Display + Serialize,
|
||||
{
|
||||
match optional_map {
|
||||
Some(map) => serialize_qualified_btreemap(map, s),
|
||||
None => s.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_qualified_btreemap<T, V, S>(
|
||||
map: &BTreeMap<Qualified<T>, V>,
|
||||
s: S,
|
||||
|
@ -4,9 +4,14 @@ use lang_graphql::schema as gql_schema;
|
||||
use open_dds::models::ModelName;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::types::output_type::get_object_type_representation;
|
||||
use super::types::output_type::relationship::{FilterRelationshipAnnotation, ModelTargetSource};
|
||||
use super::types::{input_type, output_type, InputAnnotation, ModelInputAnnotation, TypeId};
|
||||
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::types::mk_name;
|
||||
use crate::schema::permissions;
|
||||
@ -149,6 +154,97 @@ pub fn build_model_filter_expression_input_schema(
|
||||
);
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(gql_schema::TypeInfo::InputObject(
|
||||
|
@ -2,10 +2,15 @@ use hasura_authn_core::Role;
|
||||
use lang_graphql::ast::common as ast;
|
||||
use lang_graphql::schema as gql_schema;
|
||||
use open_dds::models::ModelName;
|
||||
use open_dds::relationships::RelationshipType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::types::output_type::relationship::{ModelTargetSource, OrderByRelationshipAnnotation};
|
||||
use super::types::{output_type::get_object_type_representation, Annotation, 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;
|
||||
@ -128,6 +133,94 @@ pub fn build_model_order_by_input_schema(
|
||||
);
|
||||
fields.insert(graphql_field_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)?;
|
||||
// order_by 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,
|
||||
) {
|
||||
// TODO(naveen): Support Array relationships in order_by when the support for aggregates is implemented
|
||||
if let RelationshipType::Object = relationship_type {
|
||||
// If the relationship target model does not have orderByExpressionType do not include
|
||||
// it in the source model order_by input type.
|
||||
if let Some(target_model_order_by_expression) =
|
||||
target_model.graphql_api.order_by_expression.as_ref()
|
||||
{
|
||||
let target_model_order_by_expression_type_name =
|
||||
&target_model_order_by_expression.order_by_type_name;
|
||||
|
||||
let annotation = OrderByRelationshipAnnotation {
|
||||
source_type: relationship.source.clone(),
|
||||
relationship_name: relationship.name.clone(),
|
||||
target_model_name: model_name.clone(),
|
||||
target_source: target_model_source.clone(),
|
||||
target_type: target_typename.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(),
|
||||
};
|
||||
|
||||
fields.insert(
|
||||
rel_name.clone(),
|
||||
builder.conditional_namespaced(
|
||||
gql_schema::InputField::new(
|
||||
rel_name.clone(),
|
||||
None,
|
||||
types::Annotation::Input(types::InputAnnotation::Model(
|
||||
types::ModelInputAnnotation::ModelOrderByRelationshipArgument(annotation),
|
||||
)),
|
||||
ast::TypeContainer::named_null(
|
||||
gql_schema::RegisteredTypeName::new(
|
||||
target_model_order_by_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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(gql_schema::TypeInfo::InputObject(
|
||||
gql_schema::InputObject::new(type_name.clone(), None, fields),
|
||||
))
|
||||
|
@ -54,13 +54,12 @@ pub(crate) fn get_select_one_namespace_annotations(
|
||||
/// We need to check the permissions of the source and target fields
|
||||
/// in the relationship mappings.
|
||||
pub(crate) fn get_model_relationship_namespace_annotations(
|
||||
model: &resolved::model::Model,
|
||||
target_model: &resolved::model::Model,
|
||||
source_object_type_representation: &ObjectTypeRepresentation,
|
||||
target_object_type_representation: &ObjectTypeRepresentation,
|
||||
mappings: &[resolved::relationship::RelationshipModelMapping],
|
||||
) -> HashMap<Role, Option<types::NamespaceAnnotation>> {
|
||||
let select_permissions = get_select_permissions_namespace_annotations(model);
|
||||
|
||||
let select_permissions = get_select_permissions_namespace_annotations(target_model);
|
||||
select_permissions
|
||||
.into_iter()
|
||||
.filter(|(role, _)| {
|
||||
|
@ -25,6 +25,10 @@ use crate::{
|
||||
};
|
||||
use strum_macros::Display;
|
||||
|
||||
use self::output_type::relationship::{
|
||||
FilterRelationshipAnnotation, OrderByRelationshipAnnotation,
|
||||
};
|
||||
|
||||
pub mod inbuilt_type;
|
||||
pub mod input_type;
|
||||
pub mod output_type;
|
||||
@ -63,6 +67,7 @@ pub enum ModelFilterArgument {
|
||||
OrOp,
|
||||
NotOp,
|
||||
Field { ndc_column: String },
|
||||
RelationshipField(FilterRelationshipAnnotation),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
@ -162,6 +167,8 @@ pub enum ModelInputAnnotation {
|
||||
ModelOrderByArgument {
|
||||
ndc_column: String,
|
||||
},
|
||||
ModelOrderByRelationshipArgument(OrderByRelationshipAnnotation),
|
||||
|
||||
ModelOrderByDirection {
|
||||
direction: ModelOrderByDirection,
|
||||
},
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use open_dds::{
|
||||
commands::{CommandName, FunctionName},
|
||||
models::ModelName,
|
||||
@ -10,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
metadata::resolved::{
|
||||
self,
|
||||
subgraph::{Qualified, QualifiedTypeReference},
|
||||
subgraph::{serialize_qualified_btreemap, Qualified, QualifiedTypeReference},
|
||||
},
|
||||
schema::{self, types::CommandSourceDetail},
|
||||
};
|
||||
@ -26,6 +28,48 @@ pub struct ModelRelationshipAnnotation {
|
||||
pub mappings: Vec<resolved::relationship::RelationshipModelMapping>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FilterRelationshipAnnotation {
|
||||
pub relationship_name: RelationshipName,
|
||||
pub relationship_type: RelationshipType,
|
||||
pub source_type: Qualified<CustomTypeName>,
|
||||
pub source_data_connector: resolved::data_connector::DataConnector,
|
||||
#[serde(serialize_with = "serialize_qualified_btreemap")]
|
||||
pub source_type_mappings: BTreeMap<Qualified<CustomTypeName>, resolved::types::TypeMapping>,
|
||||
pub target_source: ModelTargetSource,
|
||||
pub target_type: Qualified<CustomTypeName>,
|
||||
pub target_model_name: Qualified<ModelName>,
|
||||
pub mappings: Vec<resolved::relationship::RelationshipModelMapping>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OrderByRelationshipAnnotation {
|
||||
pub relationship_name: RelationshipName,
|
||||
pub relationship_type: RelationshipType,
|
||||
pub source_type: Qualified<CustomTypeName>,
|
||||
pub source_data_connector: resolved::data_connector::DataConnector,
|
||||
#[serde(serialize_with = "serialize_qualified_btreemap")]
|
||||
pub source_type_mappings: BTreeMap<Qualified<CustomTypeName>, resolved::types::TypeMapping>,
|
||||
pub target_source: ModelTargetSource,
|
||||
pub target_type: Qualified<CustomTypeName>,
|
||||
pub target_model_name: Qualified<ModelName>,
|
||||
pub mappings: Vec<resolved::relationship::RelationshipModelMapping>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PredicateRelationshipAnnotation {
|
||||
pub relationship_name: RelationshipName,
|
||||
pub relationship_type: RelationshipType,
|
||||
pub source_type: Qualified<CustomTypeName>,
|
||||
pub source_data_connector: resolved::data_connector::DataConnector,
|
||||
#[serde(serialize_with = "serialize_qualified_btreemap")]
|
||||
pub source_type_mappings: BTreeMap<Qualified<CustomTypeName>, resolved::types::TypeMapping>,
|
||||
pub target_source: ModelTargetSource,
|
||||
pub target_type: Qualified<CustomTypeName>,
|
||||
pub target_model_name: Qualified<ModelName>,
|
||||
pub mappings: Vec<resolved::relationship::RelationshipModelMapping>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ModelTargetSource {
|
||||
pub(crate) model: resolved::model::ModelSource,
|
||||
@ -40,21 +84,26 @@ impl ModelTargetSource {
|
||||
model
|
||||
.source
|
||||
.as_ref()
|
||||
.map(|model_source| {
|
||||
Ok(Self {
|
||||
model: model_source.clone(),
|
||||
capabilities: relationship
|
||||
.target_capabilities
|
||||
.as_ref()
|
||||
.ok_or_else(|| schema::Error::InternalMissingRelationshipCapabilities {
|
||||
type_name: relationship.source.clone(),
|
||||
relationship: relationship.name.clone(),
|
||||
})?
|
||||
.clone(),
|
||||
})
|
||||
})
|
||||
.map(|model_source| Self::from_model_source(model_source, relationship))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn from_model_source(
|
||||
model_source: &resolved::model::ModelSource,
|
||||
relationship: &resolved::relationship::Relationship,
|
||||
) -> Result<Self, schema::Error> {
|
||||
Ok(Self {
|
||||
model: model_source.clone(),
|
||||
capabilities: relationship
|
||||
.target_capabilities
|
||||
.as_ref()
|
||||
.ok_or_else(|| schema::Error::InternalMissingRelationshipCapabilities {
|
||||
type_name: relationship.source.clone(),
|
||||
relationship: relationship.name.clone(),
|
||||
})?
|
||||
.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -464,6 +464,14 @@
|
||||
"type": "named",
|
||||
"name": "Int"
|
||||
}
|
||||
},
|
||||
"GenreId": {
|
||||
"description": "The track's genre ID",
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "Int"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -495,6 +503,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Genre": {
|
||||
"description": "A Genre",
|
||||
"fields": {
|
||||
"GenreId": {
|
||||
"description": "The genre's primary key",
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "Int"
|
||||
}
|
||||
},
|
||||
"Name": {
|
||||
"description": "The genre's name",
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "String"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"collections": [
|
||||
@ -651,6 +680,19 @@
|
||||
"deletable": false,
|
||||
"uniqueness_constraints": {},
|
||||
"foreign_keys": {}
|
||||
},
|
||||
{
|
||||
"name": "Genre",
|
||||
"arguments": {},
|
||||
"type": "Genre",
|
||||
"uniqueness_constraints": {
|
||||
"PK_Genre": {
|
||||
"unique_columns": [
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"foreign_keys": {}
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
@ -756,4 +798,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,619 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"name": "Album",
|
||||
"fields": [
|
||||
{
|
||||
"name": "AlbumId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Title",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "ArtistId",
|
||||
"type": "Int"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Album"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Track",
|
||||
"fields": [
|
||||
{
|
||||
"name": "TrackId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "AlbumId",
|
||||
"type": "Int"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Track"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"kind": "DataConnectorScalarRepresentation",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorScalarType": "String",
|
||||
"representation": "String",
|
||||
"graphql": {
|
||||
"comparisonExpressionTypeName": "String_Comparison_Exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "CustomString",
|
||||
"graphql": {
|
||||
"typeName": "CustomString"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Albums",
|
||||
"objectType": "Album",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Album",
|
||||
"typeMapping": {
|
||||
"Album": {
|
||||
"fieldMapping": {
|
||||
"AlbumId": {
|
||||
"column": "AlbumId"
|
||||
},
|
||||
"Title": {
|
||||
"column": "Title"
|
||||
},
|
||||
"ArtistId": {
|
||||
"column": "ArtistId"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "AlbumByID",
|
||||
"uniqueIdentifier": [
|
||||
"AlbumId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Album"
|
||||
},
|
||||
"filterExpressionType": "Album_Where_Exp",
|
||||
"orderByExpressionType": "Album_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Title",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Title",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Tracks",
|
||||
"objectType": "Track",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Track",
|
||||
"typeMapping": {
|
||||
"Track": {
|
||||
"fieldMapping": {
|
||||
"TrackId": {
|
||||
"column": "TrackId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
},
|
||||
"AlbumId": {
|
||||
"column": "AlbumId"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "TrackByID",
|
||||
"uniqueIdentifier": [
|
||||
"TrackId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Track"
|
||||
},
|
||||
"filterExpressionType": "Track_Where_Exp",
|
||||
"orderByExpressionType": "Track_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "TrackId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "TrackId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Album",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"AlbumId",
|
||||
"Title",
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"AlbumId",
|
||||
"Title",
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Track",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"TrackId",
|
||||
"Name",
|
||||
"AlbumId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"TrackId",
|
||||
"Name",
|
||||
"AlbumId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Albums",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Tracks",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Album",
|
||||
"name": "Tracks",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Tracks",
|
||||
"relationshipType": "Array"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "Album",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Albums",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "TrackAlbums",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Albums",
|
||||
"relationshipType": "Array"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Artist",
|
||||
"fields": [
|
||||
{
|
||||
"name": "ArtistId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Artist"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Artists",
|
||||
"objectType": "Artist",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Artist",
|
||||
"typeMapping": {
|
||||
"Artist": {
|
||||
"fieldMapping": {
|
||||
"ArtistId": {
|
||||
"column": "ArtistId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "ArtistByID",
|
||||
"uniqueIdentifier": [
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Artist"
|
||||
},
|
||||
"filterExpressionType": "Artist_Where_Exp",
|
||||
"orderByExpressionType": "Artist_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Artist",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"ArtistId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"ArtistId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Album",
|
||||
"name": "Artist",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Artists",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "ArtistId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "ArtistId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"kind": "DataConnectorScalarRepresentation",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorScalarType": "Int",
|
||||
"representation": "Int",
|
||||
"graphql": {
|
||||
"comparisonExpressionTypeName": "Int_comparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,306 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackOrderByWithFilter": [
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 2,
|
||||
"Artist": {
|
||||
"ArtistId": 2
|
||||
},
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 4,
|
||||
"Artist": {
|
||||
"ArtistId": 1
|
||||
},
|
||||
"Title": "Let There Be Rock"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackOrderByWithFilter": [
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 2,
|
||||
"Artist": {
|
||||
"ArtistId": 2
|
||||
},
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,24 @@
|
||||
query MyQuery {
|
||||
Track(order_by: {Album: {Artist: {ArtistId: Asc}, AlbumId: Asc}}, limit: 15) {
|
||||
Album {
|
||||
AlbumId
|
||||
Artist {
|
||||
ArtistId
|
||||
}
|
||||
Title
|
||||
}
|
||||
}
|
||||
TrackOrderByWithFilter: Track(
|
||||
order_by: {Album: {Artist: {ArtistId: Desc}, AlbumId: Asc}}
|
||||
where: {AlbumId: {_eq: 2}}
|
||||
limit: 15
|
||||
) {
|
||||
Album {
|
||||
AlbumId
|
||||
Artist {
|
||||
ArtistId
|
||||
}
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "2"
|
||||
}
|
||||
]
|
@ -0,0 +1,126 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"Album": {
|
||||
"ArtistId": 275,
|
||||
"Title": "Koyaanisqatsi (Soundtrack from the Motion Picture)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"ArtistId": 274,
|
||||
"Title": "Mozart: Chamber Music"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"ArtistId": 273,
|
||||
"Title": "Monteverdi: L'Orfeo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackOrderByWithFilter": [
|
||||
{
|
||||
"TrackId": 5,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 2,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"Album": {
|
||||
"ArtistId": 275,
|
||||
"Title": "Koyaanisqatsi (Soundtrack from the Motion Picture)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"ArtistId": 274,
|
||||
"Title": "Mozart: Chamber Music"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"ArtistId": 273,
|
||||
"Title": "Monteverdi: L'Orfeo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackOrderByWithFilter": [
|
||||
{
|
||||
"TrackId": 5,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 2,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,20 @@
|
||||
query MyQuery {
|
||||
Track(order_by: {Album: {ArtistId: Desc}}, limit: 3) {
|
||||
Album {
|
||||
ArtistId
|
||||
Title
|
||||
}
|
||||
}
|
||||
TrackOrderByWithFilter: Track(
|
||||
order_by: {Album: {ArtistId: Asc}, TrackId: Desc}
|
||||
where: {Album: {Artist: {ArtistId: {_eq: 2}}}}
|
||||
) {
|
||||
TrackId
|
||||
Album {
|
||||
Artist {
|
||||
ArtistId
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "2"
|
||||
}
|
||||
]
|
@ -0,0 +1,157 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumWithFilterAndPredicate": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumWithFilterAndPredicate": []
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,91 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Albums",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": {
|
||||
"relationship": {
|
||||
"name": "Tracks",
|
||||
"predicate": {
|
||||
"relationship": {
|
||||
"name": "TrackAlbums",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "AlbumId",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-user-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Tracks",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
query MyQuery {
|
||||
Album(limit: 2) {
|
||||
AlbumId
|
||||
Tracks {
|
||||
TrackAlbums {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
||||
AlbumWithFilterAndPredicate: Album(limit: 2, where: {Tracks: {TrackId: {_eq: 3}}}) {
|
||||
AlbumId
|
||||
Tracks {
|
||||
TrackAlbums {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "2"
|
||||
}
|
||||
]
|
@ -0,0 +1,195 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You",
|
||||
"ArtistId": 1,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "For Those About To Rock (We Salute You)",
|
||||
"TrackId": 1
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Put The Finger On You",
|
||||
"TrackId": 6
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Let's Get It Up",
|
||||
"TrackId": 7
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Inject The Venom",
|
||||
"TrackId": 8
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Snowballed",
|
||||
"TrackId": 9
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Evil Walks",
|
||||
"TrackId": 10
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "C.O.D.",
|
||||
"TrackId": 11
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Breaking The Rules",
|
||||
"TrackId": 12
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Night Of The Long Knives",
|
||||
"TrackId": 13
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Spellbound",
|
||||
"TrackId": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"TrackId": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumPredicateOrderBy": [
|
||||
{
|
||||
"AlbumId": 347,
|
||||
"Title": "Koyaanisqatsi (Soundtrack from the Motion Picture)",
|
||||
"ArtistId": 275,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 347,
|
||||
"Name": "Koyaanisqatsi",
|
||||
"TrackId": 3503
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 346,
|
||||
"Title": "Mozart: Chamber Music",
|
||||
"ArtistId": 274,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 346,
|
||||
"Name": "Quintet for Horn, Violin, 2 Violas, and Cello in E Flat Major, K. 407/386c: III. Allegro",
|
||||
"TrackId": 3502
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumWithFilterAndPredicate": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Title": "For Those About To Rock We Salute You",
|
||||
"ArtistId": 1,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "For Those About To Rock (We Salute You)",
|
||||
"TrackId": 1
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Put The Finger On You",
|
||||
"TrackId": 6
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Let's Get It Up",
|
||||
"TrackId": 7
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Inject The Venom",
|
||||
"TrackId": 8
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Snowballed",
|
||||
"TrackId": 9
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Evil Walks",
|
||||
"TrackId": 10
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "C.O.D.",
|
||||
"TrackId": 11
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Breaking The Rules",
|
||||
"TrackId": 12
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Night Of The Long Knives",
|
||||
"TrackId": 13
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Spellbound",
|
||||
"TrackId": 14
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"TrackId": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumPredicateOrderBy": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"TrackId": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumWithFilterAndPredicate": []
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,86 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Albums",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": {
|
||||
"relationship": {
|
||||
"name": "Tracks",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "TrackId",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-user-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Tracks",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
query MyQuery {
|
||||
Album(limit: 2) {
|
||||
AlbumId
|
||||
Title
|
||||
ArtistId
|
||||
Tracks {
|
||||
AlbumId
|
||||
Name
|
||||
TrackId
|
||||
}
|
||||
}
|
||||
AlbumPredicateOrderBy: Album(limit: 2, order_by: {AlbumId: Desc}) {
|
||||
AlbumId
|
||||
Title
|
||||
ArtistId
|
||||
Tracks {
|
||||
AlbumId
|
||||
Name
|
||||
TrackId
|
||||
}
|
||||
}
|
||||
AlbumWithFilterAndPredicate: Album(limit: 2, where: {Tracks: {TrackId: {_eq: 1}}}) {
|
||||
AlbumId
|
||||
Title
|
||||
ArtistId
|
||||
Tracks {
|
||||
AlbumId
|
||||
Name
|
||||
TrackId
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "2"
|
||||
}
|
||||
]
|
@ -0,0 +1,723 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"name": "Album",
|
||||
"fields": [
|
||||
{
|
||||
"name": "AlbumId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Title",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "ArtistId",
|
||||
"type": "Int"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Album"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Track",
|
||||
"fields": [
|
||||
{
|
||||
"name": "TrackId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "AlbumId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "GenreId",
|
||||
"type": "Int"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Track"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Genre",
|
||||
"fields": [
|
||||
{
|
||||
"name": "GenreId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Genre"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"kind": "DataConnectorScalarRepresentation",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorScalarType": "String",
|
||||
"representation": "String",
|
||||
"graphql": {
|
||||
"comparisonExpressionTypeName": "String_Comparison_Exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "CustomString",
|
||||
"graphql": {
|
||||
"typeName": "CustomString"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Albums",
|
||||
"objectType": "Album",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Album",
|
||||
"typeMapping": {
|
||||
"Album": {
|
||||
"fieldMapping": {
|
||||
"AlbumId": {
|
||||
"column": "AlbumId"
|
||||
},
|
||||
"Title": {
|
||||
"column": "Title"
|
||||
},
|
||||
"ArtistId": {
|
||||
"column": "ArtistId"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "AlbumByID",
|
||||
"uniqueIdentifier": [
|
||||
"AlbumId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Album"
|
||||
},
|
||||
"filterExpressionType": "Album_Where_Exp",
|
||||
"orderByExpressionType": "Album_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Title",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Title",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Tracks",
|
||||
"objectType": "Track",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Track",
|
||||
"typeMapping": {
|
||||
"Track": {
|
||||
"fieldMapping": {
|
||||
"TrackId": {
|
||||
"column": "TrackId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
},
|
||||
"AlbumId": {
|
||||
"column": "AlbumId"
|
||||
},
|
||||
"GenreId": {
|
||||
"column": "GenreId"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "TrackByID",
|
||||
"uniqueIdentifier": [
|
||||
"TrackId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Track"
|
||||
},
|
||||
"filterExpressionType": "Track_Where_Exp",
|
||||
"orderByExpressionType": "Track_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "TrackId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "TrackId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Genres",
|
||||
"objectType": "Genre",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Genre",
|
||||
"typeMapping": {
|
||||
"Genre": {
|
||||
"fieldMapping": {
|
||||
"GenreId": {
|
||||
"column": "GenreId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "GenreByID",
|
||||
"uniqueIdentifier": [
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Genre"
|
||||
},
|
||||
"filterExpressionType": "Genre_Where_Exp",
|
||||
"orderByExpressionType": "Genre_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Album",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"AlbumId",
|
||||
"Title",
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"AlbumId",
|
||||
"Title",
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Track",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"TrackId",
|
||||
"Name",
|
||||
"AlbumId",
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"TrackId",
|
||||
"Name",
|
||||
"AlbumId",
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Genre",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"GenreId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"GenreId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Album",
|
||||
"name": "Tracks",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Tracks",
|
||||
"relationshipType": "Array"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "Album",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Albums",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "Genre",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Genres",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "GenreId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "GenreId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "TrackAlbums",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Albums",
|
||||
"relationshipType": "Array"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Artist",
|
||||
"fields": [
|
||||
{
|
||||
"name": "ArtistId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Artist"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Artists",
|
||||
"objectType": "Artist",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Artist",
|
||||
"typeMapping": {
|
||||
"Artist": {
|
||||
"fieldMapping": {
|
||||
"ArtistId": {
|
||||
"column": "ArtistId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "ArtistByID",
|
||||
"uniqueIdentifier": [
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Artist"
|
||||
},
|
||||
"filterExpressionType": "Artist_Where_Exp",
|
||||
"orderByExpressionType": "Artist_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Artist",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"ArtistId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"ArtistId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Album",
|
||||
"name": "Artist",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Artists",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "ArtistId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "ArtistId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"kind": "DataConnectorScalarRepresentation",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorScalarType": "Int",
|
||||
"representation": "Int",
|
||||
"graphql": {
|
||||
"comparisonExpressionTypeName": "Int_comparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"TrackId": 1,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 1,
|
||||
"Name": "AC/DC"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 2,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackWithFilterAndPredicate": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"TrackId": 1,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 1,
|
||||
"Name": "AC/DC"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 6,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 1,
|
||||
"Name": "AC/DC"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackWithFilterAndPredicate": []
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,91 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Albums",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Tracks",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": {
|
||||
"relationship": {
|
||||
"name": "Album",
|
||||
"predicate": {
|
||||
"relationship": {
|
||||
"name": "Artist",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "ArtistId",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-user-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
query MyQuery {
|
||||
Track(limit: 2) {
|
||||
TrackId
|
||||
Album {
|
||||
Artist {
|
||||
ArtistId
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
TrackWithFilterAndPredicate: Track(
|
||||
where: {_and: [{Album: {Artist: {ArtistId: {_eq: 2}}}}, {TrackId: {_eq: 3}}]}
|
||||
) {
|
||||
TrackId
|
||||
Album {
|
||||
Artist {
|
||||
ArtistId
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "1"
|
||||
}
|
||||
]
|
@ -0,0 +1,108 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "For Those About To Rock (We Salute You)",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"Album": {
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackWithFilterAndPredicate": [
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "For Those About To Rock (We Salute You)",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Put The Finger On You",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Let's Get It Up",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Inject The Venom",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Snowballed",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Evil Walks",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "C.O.D.",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Breaking The Rules",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Night Of The Long Knives",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 1,
|
||||
"Name": "Spellbound",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"Album": {
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackWithFilterAndPredicate": []
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,86 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Albums",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Tracks",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": {
|
||||
"relationship": {
|
||||
"name": "Album",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "Title",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-album-title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
query MyQuery {
|
||||
Track(limit: 2) {
|
||||
AlbumId
|
||||
Name
|
||||
Album {
|
||||
Title
|
||||
}
|
||||
}
|
||||
TrackWithFilterAndPredicate: Track(
|
||||
where: {_and: [{Album: {Title: {_eq: "For Those About To Rock We Salute You"}}}, {AlbumId: {_eq: 1}}]}
|
||||
) {
|
||||
AlbumId
|
||||
Name
|
||||
Album {
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-album-title": "Balls to the Wall"
|
||||
}
|
||||
]
|
@ -0,0 +1,58 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"Tracks": [
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1
|
||||
},
|
||||
"Genre": {
|
||||
"GenreId": 1
|
||||
},
|
||||
"Name": "For Those About To Rock (We Salute You)"
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1
|
||||
},
|
||||
"Genre": {
|
||||
"GenreId": 1
|
||||
},
|
||||
"Name": "Put The Finger On You"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"Tracks": [
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1
|
||||
},
|
||||
"Genre": {
|
||||
"GenreId": 1
|
||||
},
|
||||
"Name": "For Those About To Rock (We Salute You)"
|
||||
},
|
||||
{
|
||||
"Album": {
|
||||
"AlbumId": 1
|
||||
},
|
||||
"Genre": {
|
||||
"GenreId": 1
|
||||
},
|
||||
"Name": "Put The Finger On You"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,130 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Albums",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": {
|
||||
"relationship": {
|
||||
"name": "Tracks",
|
||||
"predicate": {
|
||||
"and": [
|
||||
{
|
||||
"relationship": {
|
||||
"name": "Album",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "AlbumId",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-user-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"relationship": {
|
||||
"name": "Genre",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "GenreId",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-user-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Tracks",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Genres",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
query MyQuery {
|
||||
Album(limit: 1) {
|
||||
Tracks(limit: 2) {
|
||||
Album {
|
||||
AlbumId
|
||||
}
|
||||
Genre {
|
||||
GenreId
|
||||
}
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "1"
|
||||
}
|
||||
]
|
@ -0,0 +1,124 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 1,
|
||||
"Name": "For Those About To Rock (We Salute You)",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 6,
|
||||
"Name": "Put The Finger On You",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 7,
|
||||
"Name": "Let's Get It Up",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 8,
|
||||
"Name": "Inject The Venom",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 9,
|
||||
"Name": "Snowballed",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 10,
|
||||
"Name": "Evil Walks",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 11,
|
||||
"Name": "C.O.D.",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 12,
|
||||
"Name": "Breaking The Rules",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 13,
|
||||
"Name": "Night Of The Long Knives",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 14,
|
||||
"Name": "Spellbound",
|
||||
"Album": {
|
||||
"Title": "For Those About To Rock We Salute You"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"Album": {
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"Name": "Restless and Wild",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"Name": "Princess of the Dawn",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,99 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Albums",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": {
|
||||
"relationship": {
|
||||
"name": "Tracks",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "TrackId",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-user-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Tracks",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": {
|
||||
"relationship": {
|
||||
"name": "Album",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "Title",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-album-title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
query MyQuery {
|
||||
Album(limit: 2) {
|
||||
Tracks {
|
||||
TrackId
|
||||
Name
|
||||
Album {
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "3",
|
||||
"x-hasura-album-title": "Restless and Wild"
|
||||
}
|
||||
]
|
@ -0,0 +1,608 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumAnd": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumOr": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 2,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumAnd": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumOr": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 2,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Tracks": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"TrackAlbums": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,31 @@
|
||||
query MyQuery {
|
||||
Album(where: {Tracks: {TrackAlbums: {AlbumId: {_eq: 3}}}}) {
|
||||
AlbumId
|
||||
Tracks {
|
||||
TrackAlbums {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
||||
AlbumAnd: Album(where: {_and: [{Tracks: {TrackAlbums: {AlbumId: {_eq: 3}}}}, {AlbumId: {_eq: 3}}]}) {
|
||||
AlbumId
|
||||
Tracks {
|
||||
TrackId
|
||||
TrackAlbums {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
||||
AlbumOr: Album(where: {_or: [{Tracks: {TrackAlbums: {AlbumId: {_eq: 3}}}}, {AlbumId: {_eq: 2}}]}) {
|
||||
AlbumId
|
||||
Tracks {
|
||||
TrackId
|
||||
TrackAlbums {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "2"
|
||||
}
|
||||
]
|
@ -0,0 +1,314 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumAnd": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumOr": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"TrackId": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumTracksTwoRelationships": [
|
||||
{
|
||||
"Tracks": [
|
||||
{
|
||||
"Genre": {
|
||||
"GenreId": 1
|
||||
},
|
||||
"Album": {
|
||||
"AlbumId": 1
|
||||
},
|
||||
"Name": "For Those About To Rock (We Salute You)"
|
||||
},
|
||||
{
|
||||
"Genre": {
|
||||
"GenreId": 1
|
||||
},
|
||||
"Album": {
|
||||
"AlbumId": 1
|
||||
},
|
||||
"Name": "Put The Finger On You"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Album": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumAnd": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumOr": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Title": "Balls to the Wall",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"TrackId": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Title": "Restless and Wild",
|
||||
"ArtistId": 2,
|
||||
"Tracks": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"TrackId": 3
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"TrackId": 4
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"TrackId": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AlbumTracksTwoRelationships": [
|
||||
{
|
||||
"Tracks": [
|
||||
{
|
||||
"Genre": {
|
||||
"GenreId": 1
|
||||
},
|
||||
"Album": {
|
||||
"AlbumId": 1
|
||||
},
|
||||
"Name": "For Those About To Rock (We Salute You)"
|
||||
},
|
||||
{
|
||||
"Genre": {
|
||||
"GenreId": 1
|
||||
},
|
||||
"Album": {
|
||||
"AlbumId": 1
|
||||
},
|
||||
"Name": "Put The Finger On You"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,50 @@
|
||||
query MyQuery {
|
||||
Album(where: {Tracks: {TrackId: {_eq: 3}}}) {
|
||||
AlbumId
|
||||
Title
|
||||
ArtistId
|
||||
Tracks {
|
||||
AlbumId
|
||||
Name
|
||||
TrackId
|
||||
}
|
||||
}
|
||||
AlbumAnd: Album(
|
||||
where: {_and: [{Tracks: {TrackId: {_eq: 3}}}, {ArtistId: {_eq: 2}}]}
|
||||
) {
|
||||
AlbumId
|
||||
Title
|
||||
ArtistId
|
||||
Tracks {
|
||||
AlbumId
|
||||
Name
|
||||
TrackId
|
||||
}
|
||||
}
|
||||
AlbumOr: Album(
|
||||
where: {_or: [{Tracks: {TrackId: {_eq: 3}}}, {ArtistId: {_eq: 2}}]}
|
||||
) {
|
||||
AlbumId
|
||||
Title
|
||||
ArtistId
|
||||
Tracks {
|
||||
AlbumId
|
||||
Name
|
||||
TrackId
|
||||
}
|
||||
}
|
||||
AlbumTracksTwoRelationships: Album(
|
||||
where: {Tracks: {Album: {AlbumId: {_eq: 1}}, Genre: {GenreId: {_eq: 1}}}}
|
||||
limit: 1
|
||||
) {
|
||||
Tracks(limit: 2) {
|
||||
Genre {
|
||||
GenreId
|
||||
}
|
||||
Album {
|
||||
AlbumId
|
||||
}
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "2"
|
||||
}
|
||||
]
|
@ -0,0 +1,807 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"subgraphs": [
|
||||
{
|
||||
"name": "default",
|
||||
"objects": [
|
||||
{
|
||||
"definition": {
|
||||
"name": "Album",
|
||||
"fields": [
|
||||
{
|
||||
"name": "AlbumId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Title",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "ArtistId",
|
||||
"type": "Int"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Album"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Track",
|
||||
"fields": [
|
||||
{
|
||||
"name": "TrackId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "AlbumId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "GenreId",
|
||||
"type": "Int"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Track"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Genre",
|
||||
"fields": [
|
||||
{
|
||||
"name": "GenreId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Genre"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"kind": "DataConnectorScalarRepresentation",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorScalarType": "String",
|
||||
"representation": "String",
|
||||
"graphql": {
|
||||
"comparisonExpressionTypeName": "String_Comparison_Exp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ScalarType",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"name": "CustomString",
|
||||
"graphql": {
|
||||
"typeName": "CustomString"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Albums",
|
||||
"objectType": "Album",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Album",
|
||||
"typeMapping": {
|
||||
"Album": {
|
||||
"fieldMapping": {
|
||||
"AlbumId": {
|
||||
"column": "AlbumId"
|
||||
},
|
||||
"Title": {
|
||||
"column": "Title"
|
||||
},
|
||||
"ArtistId": {
|
||||
"column": "ArtistId"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "AlbumByID",
|
||||
"uniqueIdentifier": [
|
||||
"AlbumId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Album"
|
||||
},
|
||||
"filterExpressionType": "Album_Where_Exp",
|
||||
"orderByExpressionType": "Album_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Title",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Title",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Tracks",
|
||||
"objectType": "Track",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Track",
|
||||
"typeMapping": {
|
||||
"Track": {
|
||||
"fieldMapping": {
|
||||
"TrackId": {
|
||||
"column": "TrackId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
},
|
||||
"AlbumId": {
|
||||
"column": "AlbumId"
|
||||
},
|
||||
"GenreId": {
|
||||
"column": "GenreId"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "TrackByID",
|
||||
"uniqueIdentifier": [
|
||||
"TrackId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Track"
|
||||
},
|
||||
"filterExpressionType": "Track_Where_Exp",
|
||||
"orderByExpressionType": "Track_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "TrackId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "TrackId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "AlbumId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Genres",
|
||||
"objectType": "Genre",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Genre",
|
||||
"typeMapping": {
|
||||
"Genre": {
|
||||
"fieldMapping": {
|
||||
"GenreId": {
|
||||
"column": "GenreId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "GenreByID",
|
||||
"uniqueIdentifier": [
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Genre"
|
||||
},
|
||||
"filterExpressionType": "Genre_Where_Exp",
|
||||
"orderByExpressionType": "Genre_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Album",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"AlbumId",
|
||||
"Title",
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"AlbumId",
|
||||
"Title",
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Track",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"TrackId",
|
||||
"Name",
|
||||
"AlbumId",
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"TrackId",
|
||||
"Name",
|
||||
"AlbumId",
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Genre",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"GenreId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"GenreId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Albums",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Tracks",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Genres",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Album",
|
||||
"name": "Tracks",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Tracks",
|
||||
"relationshipType": "Array"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "Album",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Albums",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "Genre",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Genres",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "GenreId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "GenreId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "TrackAlbums",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Albums",
|
||||
"relationshipType": "Array"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "AlbumId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Artist",
|
||||
"fields": [
|
||||
{
|
||||
"name": "ArtistId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Artist"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Artists",
|
||||
"objectType": "Artist",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Artist",
|
||||
"typeMapping": {
|
||||
"Artist": {
|
||||
"fieldMapping": {
|
||||
"ArtistId": {
|
||||
"column": "ArtistId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "ArtistByID",
|
||||
"uniqueIdentifier": [
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Artist"
|
||||
},
|
||||
"filterExpressionType": "Artist_Where_Exp",
|
||||
"orderByExpressionType": "Artist_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "ArtistId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Artist",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"ArtistId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"ArtistId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Album",
|
||||
"name": "Artist",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Artists",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "ArtistId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "ArtistId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"kind": "DataConnectorScalarRepresentation",
|
||||
"version": "v1",
|
||||
"definition": {
|
||||
"dataConnectorName": "db",
|
||||
"dataConnectorScalarType": "Int",
|
||||
"representation": "Int",
|
||||
"graphql": {
|
||||
"comparisonExpressionTypeName": "Int_comparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"TrackId": 2,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackAnd": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackOr": [
|
||||
{
|
||||
"TrackId": 2,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"TrackId": 2,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackAnd": [
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackOr": [
|
||||
{
|
||||
"TrackId": 2,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 3,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 4,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TrackId": 5,
|
||||
"Album": {
|
||||
"Artist": {
|
||||
"ArtistId": 2,
|
||||
"Name": "Accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,29 @@
|
||||
query MyQuery {
|
||||
Track(where: {Album: {Artist: {ArtistId: {_eq: 2}}}}) {
|
||||
TrackId
|
||||
Album {
|
||||
Artist {
|
||||
ArtistId
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
TrackAnd:Track(where: {_and: [{Album: {Artist: {ArtistId: {_eq: 2}}}}, {TrackId: {_eq: 3}}]} ) {
|
||||
TrackId
|
||||
Album {
|
||||
Artist {
|
||||
ArtistId
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
TrackOr:Track(where: {_or: [{Album: {Artist: {ArtistId: {_eq: 2}}}}, {TrackId: {_eq: 3}}]} ) {
|
||||
TrackId
|
||||
Album {
|
||||
Artist {
|
||||
ArtistId
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "2"
|
||||
}
|
||||
]
|
@ -0,0 +1,162 @@
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackAnd": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackOr": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"Album": {
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"Track": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackAnd": [
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TrackOr": [
|
||||
{
|
||||
"AlbumId": 2,
|
||||
"Name": "Balls to the Wall",
|
||||
"Album": {
|
||||
"Title": "Balls to the Wall"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Fast As a Shark",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Restless and Wild",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"AlbumId": 3,
|
||||
"Name": "Princess of the Dawn",
|
||||
"Album": {
|
||||
"Title": "Restless and Wild"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,23 @@
|
||||
query MyQuery {
|
||||
Track(where: {Album: {Title: {_eq: "Restless and Wild"}}}) {
|
||||
AlbumId
|
||||
Name
|
||||
Album {
|
||||
Title
|
||||
}
|
||||
}
|
||||
TrackAnd: Track(where: {_and: [{Album: {Title: {_eq: "Restless and Wild"}}}, {AlbumId: {_eq: 3}} ]} ) {
|
||||
AlbumId
|
||||
Name
|
||||
Album {
|
||||
Title
|
||||
}
|
||||
}
|
||||
TrackOr: Track(where: {_or: [{Album: {Title: {_eq: "Restless and Wild"}}}, {AlbumId: {_eq: 2}} ]} ) {
|
||||
AlbumId
|
||||
Name
|
||||
Album {
|
||||
Title
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"x-hasura-role": "admin"
|
||||
},
|
||||
{
|
||||
"x-hasura-role": "user",
|
||||
"x-hasura-user-id": "2"
|
||||
}
|
||||
]
|
@ -102,6 +102,40 @@ fn test_model_select_many_type_permission_order_by() {
|
||||
common::test_execution_expectation(test_path_string, &[common_metadata_path_string]);
|
||||
}
|
||||
|
||||
// Relationships in order_by expressions
|
||||
// What is being tested:
|
||||
// 1. Object relationships in order_by expressions (Simple, Nested Object relationships). We also test multi column boolean expressions
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_order_by_object_relationship_simple() {
|
||||
let test_path_string = "execute/models/select_many/order_by/relationships/object/simple";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/order_by/relationships/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_order_by_object_relationship_nested() {
|
||||
let test_path_string = "execute/models/select_many/order_by/relationships/object/nested";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/order_by/relationships/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_type_permission_where() {
|
||||
let test_path_string = "execute/models/select_many/type_permission/where";
|
||||
@ -152,6 +186,71 @@ fn test_model_select_many_where_ndc_operators() {
|
||||
common::test_execution_expectation(test_path_string, &[common_metadata_path_string]);
|
||||
}
|
||||
|
||||
// Relationships in boolean expressions
|
||||
// What is being tested:
|
||||
// 1. Array relationships in boolean expressions (Simple, Nested array relationships). We also test multi column boolean expressions
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_where_array_relationship_simple() {
|
||||
let test_path_string = "execute/models/select_many/where/relationships/array/simple";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/where/relationships/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_where_array_relationship_nested() {
|
||||
let test_path_string = "execute/models/select_many/where/relationships/array/nested";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/where/relationships/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Object relationships in boolean expressions (Simple, Nested object relationships). We also test multi column boolean expressions
|
||||
#[test]
|
||||
fn test_model_select_many_where_object_relationship_simple() {
|
||||
let test_path_string = "execute/models/select_many/where/relationships/object/simple";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/where/relationships/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_where_object_relationship_nested() {
|
||||
let test_path_string = "execute/models/select_many/where/relationships/object/nested";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/where/relationships/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_select_many_object_type_input_arguments() {
|
||||
let test_path_string = "execute/models/select_many/object_type_input_arguments";
|
||||
@ -556,3 +655,139 @@ fn test_command_procedures_multiple_arguments() {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Tests using relationships in predicates
|
||||
// Array relationship
|
||||
#[test]
|
||||
fn test_model_select_many_relationship_predicate_array_simple() {
|
||||
let test_path_string = "execute/models/select_many/relationship_predicates/array/simple";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/relationship_predicates/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Tests using relationships in predicates
|
||||
|
||||
// Nested Array relationship
|
||||
#[test]
|
||||
fn test_model_select_many_relationship_predicate_array_nested() {
|
||||
let test_path_string = "execute/models/select_many/relationship_predicates/array/nested";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/relationship_predicates/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Tests using relationships in predicates
|
||||
// Object relationship
|
||||
#[test]
|
||||
fn test_model_select_many_relationship_predicate_object_simple() {
|
||||
let test_path_string = "execute/models/select_many/relationship_predicates/object/simple";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/relationship_predicates/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Tests using relationships in predicates
|
||||
// Nested bject relationship
|
||||
#[test]
|
||||
fn test_model_select_many_relationship_predicate_object_nested() {
|
||||
let test_path_string = "execute/models/select_many/relationship_predicates/object/nested";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/relationship_predicates/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Tests using relationships in predicates
|
||||
// We have the following relationships:
|
||||
// 1. 'Tracks' array relationship to 'Album' model
|
||||
// 2. 'Album' object relationship to 'Track' model
|
||||
//
|
||||
// Predicates using the relationship are defined on both the models as follows:
|
||||
// 1. The select permission for 'user' role on 'Album' model is defined as:
|
||||
// Select only those Album whose `TrackId` from the relationship `Track` is equal to "x-hasura-user-id"
|
||||
// 2. The select permission for 'user' role on 'Track' model is defined as:
|
||||
// Select only those Track whose `Title` from the relationship `Album` is equal to "x-hasura-album-title"
|
||||
//
|
||||
// In this test, we test what happens when we query both the `Tracks` and `Album` relationship in the same query.
|
||||
// The query we make is:
|
||||
// query MyQuery {
|
||||
// Album(limit: 1) {
|
||||
// Tracks {
|
||||
// TrackId
|
||||
// Name
|
||||
// Album {
|
||||
// Title
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// We expect the following results:
|
||||
// Fetch all the tracks of the Albums whose `TrackId` is equal to "x-hasura-user-id" and then
|
||||
// filter those tracks based on the "x-hasura-album-title" value.
|
||||
#[test]
|
||||
fn test_model_select_many_relationship_predicate_on_two_fields() {
|
||||
let test_path_string = "execute/models/select_many/relationship_predicates/on_two_fields";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/relationship_predicates/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Tests using relationships in predicates
|
||||
// We have the following relationships:
|
||||
// 1. 'Tracks' object relationship to 'Album' model
|
||||
// 2. 'Album' object relationship to 'Track' model
|
||||
// 3. 'Genre' object relationship to 'Track' model
|
||||
//
|
||||
// We have the following select permission defined for "user" role
|
||||
// It filters only those Albums whose Tracks's Album's AlbumnId is equal to "x-hasura-user-id" and
|
||||
// whose Tracks's Genre's GenreId is equal to "x-hasura-genre-name"
|
||||
#[test]
|
||||
fn test_model_select_many_relationship_predicate_object_two_relationship_fields() {
|
||||
let test_path_string =
|
||||
"execute/models/select_many/relationship_predicates/object/two_relationship_fields";
|
||||
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
|
||||
let boolean_exp_rel_metadata_path_string =
|
||||
"execute/models/select_many/relationship_predicates/common_metadata.json";
|
||||
common::test_execution_expectation(
|
||||
test_path_string,
|
||||
&[
|
||||
common_metadata_path_string,
|
||||
boolean_exp_rel_metadata_path_string,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -90,23 +90,26 @@
|
||||
},
|
||||
"collection": "articles",
|
||||
"arguments": {},
|
||||
"filter_clause": [
|
||||
{
|
||||
"type": "binary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "id",
|
||||
"path": []
|
||||
},
|
||||
"operator": {
|
||||
"type": "equal"
|
||||
},
|
||||
"value": {
|
||||
"type": "scalar",
|
||||
"value": 1
|
||||
"filter_clause": {
|
||||
"expressions": [
|
||||
{
|
||||
"type": "binary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "id",
|
||||
"path": []
|
||||
},
|
||||
"operator": {
|
||||
"type": "equal"
|
||||
},
|
||||
"value": {
|
||||
"type": "scalar",
|
||||
"value": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
],
|
||||
"relationships": {}
|
||||
},
|
||||
"limit": null,
|
||||
"offset": null,
|
||||
"order_by": null,
|
||||
|
@ -92,7 +92,10 @@
|
||||
},
|
||||
"collection": "articles",
|
||||
"arguments": {},
|
||||
"filter_clause": [],
|
||||
"filter_clause": {
|
||||
"expressions": [],
|
||||
"relationships": {}
|
||||
},
|
||||
"limit": 1,
|
||||
"offset": 1,
|
||||
"order_by": null,
|
||||
|
@ -449,7 +449,10 @@
|
||||
},
|
||||
"collection": "articles",
|
||||
"arguments": {},
|
||||
"filter_clause": [],
|
||||
"filter_clause": {
|
||||
"expressions": [],
|
||||
"relationships": {}
|
||||
},
|
||||
"limit": null,
|
||||
"offset": null,
|
||||
"order_by": null,
|
||||
@ -472,7 +475,10 @@
|
||||
},
|
||||
"collection": "authors",
|
||||
"arguments": {},
|
||||
"filter_clause": [],
|
||||
"filter_clause": {
|
||||
"expressions": [],
|
||||
"relationships": {}
|
||||
},
|
||||
"limit": null,
|
||||
"offset": null,
|
||||
"order_by": null,
|
||||
@ -495,7 +501,10 @@
|
||||
},
|
||||
"collection": "articles",
|
||||
"arguments": {},
|
||||
"filter_clause": [],
|
||||
"filter_clause": {
|
||||
"expressions": [],
|
||||
"relationships": {}
|
||||
},
|
||||
"limit": null,
|
||||
"offset": null,
|
||||
"order_by": null,
|
||||
@ -516,82 +525,11 @@
|
||||
},
|
||||
"name": "[{\"subgraph\":\"default\",\"name\":\"author\"},\"Articles\"]",
|
||||
"relationship_info": {
|
||||
"annotation": {
|
||||
"source_type": {
|
||||
"subgraph": "default",
|
||||
"name": "author"
|
||||
},
|
||||
"relationship_name": "Articles",
|
||||
"model_name": {
|
||||
"subgraph": "default",
|
||||
"name": "Articles"
|
||||
},
|
||||
"target_source": {
|
||||
"model": {
|
||||
"data_connector": {
|
||||
"name": {
|
||||
"subgraph": "default",
|
||||
"name": "db"
|
||||
},
|
||||
"url": {
|
||||
"singleUrl": "http://postgres_connector:8100/"
|
||||
},
|
||||
"headers": {
|
||||
"hasura-m-auth-token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!#$&'()*+,/:;=?@[]\""
|
||||
}
|
||||
},
|
||||
"collection": "articles",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"article\"}": {
|
||||
"Object": {
|
||||
"field_mappings": {
|
||||
"article_id": {
|
||||
"column": "id",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "int4"
|
||||
}
|
||||
},
|
||||
"author_id": {
|
||||
"column": "author_id",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "int4"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"column": "title",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "varchar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"argument_mappings": {}
|
||||
},
|
||||
"capabilities": {
|
||||
"foreach": null,
|
||||
"relationships": true
|
||||
}
|
||||
},
|
||||
"target_type": {
|
||||
"subgraph": "default",
|
||||
"name": "article"
|
||||
},
|
||||
"relationship_type": "Array",
|
||||
"mappings": [
|
||||
{
|
||||
"source_field": {
|
||||
"fieldName": "author_id"
|
||||
},
|
||||
"target_field": {
|
||||
"fieldName": "author_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
"relationship_name": "Articles",
|
||||
"relationship_type": "Array",
|
||||
"source_type": {
|
||||
"subgraph": "default",
|
||||
"name": "author"
|
||||
},
|
||||
"source_data_connector": {
|
||||
"name": {
|
||||
@ -684,7 +622,21 @@
|
||||
"foreach": null,
|
||||
"relationships": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"target_type": {
|
||||
"subgraph": "default",
|
||||
"name": "article"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"source_field": {
|
||||
"fieldName": "author_id"
|
||||
},
|
||||
"target_field": {
|
||||
"fieldName": "author_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -693,82 +645,11 @@
|
||||
},
|
||||
"name": "[{\"subgraph\":\"default\",\"name\":\"article\"},\"Author\"]",
|
||||
"relationship_info": {
|
||||
"annotation": {
|
||||
"source_type": {
|
||||
"subgraph": "default",
|
||||
"name": "article"
|
||||
},
|
||||
"relationship_name": "Author",
|
||||
"model_name": {
|
||||
"subgraph": "default",
|
||||
"name": "Authors"
|
||||
},
|
||||
"target_source": {
|
||||
"model": {
|
||||
"data_connector": {
|
||||
"name": {
|
||||
"subgraph": "default",
|
||||
"name": "db"
|
||||
},
|
||||
"url": {
|
||||
"singleUrl": "http://postgres_connector:8100/"
|
||||
},
|
||||
"headers": {
|
||||
"hasura-m-auth-token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!#$&'()*+,/:;=?@[]\""
|
||||
}
|
||||
},
|
||||
"collection": "authors",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"author\"}": {
|
||||
"Object": {
|
||||
"field_mappings": {
|
||||
"author_id": {
|
||||
"column": "id",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "int8"
|
||||
}
|
||||
},
|
||||
"first_name": {
|
||||
"column": "first_name",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "varchar"
|
||||
}
|
||||
},
|
||||
"last_name": {
|
||||
"column": "last_name",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "varchar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"argument_mappings": {}
|
||||
},
|
||||
"capabilities": {
|
||||
"foreach": null,
|
||||
"relationships": true
|
||||
}
|
||||
},
|
||||
"target_type": {
|
||||
"subgraph": "default",
|
||||
"name": "author"
|
||||
},
|
||||
"relationship_type": "Object",
|
||||
"mappings": [
|
||||
{
|
||||
"source_field": {
|
||||
"fieldName": "author_id"
|
||||
},
|
||||
"target_field": {
|
||||
"fieldName": "author_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
"relationship_name": "Author",
|
||||
"relationship_type": "Object",
|
||||
"source_type": {
|
||||
"subgraph": "default",
|
||||
"name": "article"
|
||||
},
|
||||
"source_data_connector": {
|
||||
"name": {
|
||||
@ -861,7 +742,21 @@
|
||||
"foreach": null,
|
||||
"relationships": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"target_type": {
|
||||
"subgraph": "default",
|
||||
"name": "author"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"source_field": {
|
||||
"fieldName": "author_id"
|
||||
},
|
||||
"target_field": {
|
||||
"fieldName": "author_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -870,82 +765,11 @@
|
||||
},
|
||||
"name": "[{\"subgraph\":\"default\",\"name\":\"commandArticle\"},\"article\"]",
|
||||
"relationship_info": {
|
||||
"annotation": {
|
||||
"source_type": {
|
||||
"subgraph": "default",
|
||||
"name": "commandArticle"
|
||||
},
|
||||
"relationship_name": "article",
|
||||
"model_name": {
|
||||
"subgraph": "default",
|
||||
"name": "Articles"
|
||||
},
|
||||
"target_source": {
|
||||
"model": {
|
||||
"data_connector": {
|
||||
"name": {
|
||||
"subgraph": "default",
|
||||
"name": "db"
|
||||
},
|
||||
"url": {
|
||||
"singleUrl": "http://postgres_connector:8100/"
|
||||
},
|
||||
"headers": {
|
||||
"hasura-m-auth-token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!#$&'()*+,/:;=?@[]\""
|
||||
}
|
||||
},
|
||||
"collection": "articles",
|
||||
"type_mappings": {
|
||||
"{\"subgraph\":\"default\",\"name\":\"article\"}": {
|
||||
"Object": {
|
||||
"field_mappings": {
|
||||
"article_id": {
|
||||
"column": "id",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "int4"
|
||||
}
|
||||
},
|
||||
"author_id": {
|
||||
"column": "author_id",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "int4"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"column": "title",
|
||||
"column_type": {
|
||||
"type": "named",
|
||||
"name": "varchar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"argument_mappings": {}
|
||||
},
|
||||
"capabilities": {
|
||||
"foreach": null,
|
||||
"relationships": true
|
||||
}
|
||||
},
|
||||
"target_type": {
|
||||
"subgraph": "default",
|
||||
"name": "article"
|
||||
},
|
||||
"relationship_type": "Object",
|
||||
"mappings": [
|
||||
{
|
||||
"source_field": {
|
||||
"fieldName": "article_id"
|
||||
},
|
||||
"target_field": {
|
||||
"fieldName": "article_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
"relationship_name": "article",
|
||||
"relationship_type": "Object",
|
||||
"source_type": {
|
||||
"subgraph": "default",
|
||||
"name": "commandArticle"
|
||||
},
|
||||
"source_data_connector": {
|
||||
"name": {
|
||||
@ -1038,7 +862,21 @@
|
||||
"foreach": null,
|
||||
"relationships": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"target_type": {
|
||||
"subgraph": "default",
|
||||
"name": "article"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"source_field": {
|
||||
"fieldName": "article_id"
|
||||
},
|
||||
"target_field": {
|
||||
"fieldName": "article_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,41 +90,44 @@
|
||||
},
|
||||
"collection": "articles",
|
||||
"arguments": {},
|
||||
"filter_clause": [
|
||||
{
|
||||
"type": "and",
|
||||
"expressions": [
|
||||
{
|
||||
"type": "binary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "title",
|
||||
"path": []
|
||||
},
|
||||
"operator": {
|
||||
"type": "other",
|
||||
"name": "_like"
|
||||
},
|
||||
"value": {
|
||||
"type": "scalar",
|
||||
"value": "%Functional%"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "not",
|
||||
"expression": {
|
||||
"type": "unary_comparison_operator",
|
||||
"filter_clause": {
|
||||
"expressions": [
|
||||
{
|
||||
"type": "and",
|
||||
"expressions": [
|
||||
{
|
||||
"type": "binary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "author_id",
|
||||
"name": "title",
|
||||
"path": []
|
||||
},
|
||||
"operator": "is_null"
|
||||
"operator": {
|
||||
"type": "other",
|
||||
"name": "_like"
|
||||
},
|
||||
"value": {
|
||||
"type": "scalar",
|
||||
"value": "%Functional%"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "not",
|
||||
"expression": {
|
||||
"type": "unary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "author_id",
|
||||
"path": []
|
||||
},
|
||||
"operator": "is_null"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
]
|
||||
}
|
||||
],
|
||||
"relationships": {}
|
||||
},
|
||||
"limit": null,
|
||||
"offset": null,
|
||||
"order_by": null,
|
||||
|
@ -92,24 +92,27 @@
|
||||
},
|
||||
"collection": "articles",
|
||||
"arguments": {},
|
||||
"filter_clause": [
|
||||
{
|
||||
"type": "binary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "title",
|
||||
"path": []
|
||||
},
|
||||
"operator": {
|
||||
"type": "other",
|
||||
"name": "_like"
|
||||
},
|
||||
"value": {
|
||||
"type": "scalar",
|
||||
"value": "random"
|
||||
"filter_clause": {
|
||||
"expressions": [
|
||||
{
|
||||
"type": "binary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "title",
|
||||
"path": []
|
||||
},
|
||||
"operator": {
|
||||
"type": "other",
|
||||
"name": "_like"
|
||||
},
|
||||
"value": {
|
||||
"type": "scalar",
|
||||
"value": "random"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
],
|
||||
"relationships": {}
|
||||
},
|
||||
"limit": 1,
|
||||
"offset": null,
|
||||
"order_by": null,
|
||||
@ -246,20 +249,23 @@
|
||||
},
|
||||
"collection": "authors",
|
||||
"arguments": {},
|
||||
"filter_clause": [
|
||||
{
|
||||
"type": "not",
|
||||
"expression": {
|
||||
"type": "unary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "first_name",
|
||||
"path": []
|
||||
},
|
||||
"operator": "is_null"
|
||||
"filter_clause": {
|
||||
"expressions": [
|
||||
{
|
||||
"type": "not",
|
||||
"expression": {
|
||||
"type": "unary_comparison_operator",
|
||||
"column": {
|
||||
"type": "column",
|
||||
"name": "first_name",
|
||||
"path": []
|
||||
},
|
||||
"operator": "is_null"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
],
|
||||
"relationships": {}
|
||||
},
|
||||
"limit": null,
|
||||
"offset": null,
|
||||
"order_by": null,
|
||||
|
@ -13,9 +13,9 @@
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
"hasura-m-auth-token": {
|
||||
"value": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!#$&'()*+,/:;=?@[]\""
|
||||
}
|
||||
"hasura-m-auth-token": {
|
||||
"value": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!#$&'()*+,/:;=?@[]\""
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"scalar_types": {
|
||||
@ -414,6 +414,14 @@
|
||||
"type": "named",
|
||||
"name": "int4"
|
||||
}
|
||||
},
|
||||
"GenreId": {
|
||||
"description": "The track's genre ID",
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "int4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -445,6 +453,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Genre": {
|
||||
"description": "A Genre",
|
||||
"fields": {
|
||||
"GenreId": {
|
||||
"description": "The genre's primary key",
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "int4"
|
||||
}
|
||||
},
|
||||
"Name": {
|
||||
"description": "The genre's name",
|
||||
"arguments": {},
|
||||
"type": {
|
||||
"type": "named",
|
||||
"name": "varchar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"collections": [
|
||||
@ -601,6 +630,19 @@
|
||||
"deletable": false,
|
||||
"uniqueness_constraints": {},
|
||||
"foreign_keys": {}
|
||||
},
|
||||
{
|
||||
"name": "Genre",
|
||||
"arguments": {},
|
||||
"type": "Genre",
|
||||
"uniqueness_constraints": {
|
||||
"PK_Genre": {
|
||||
"unique_columns": [
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"foreign_keys": {}
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
@ -785,6 +827,10 @@
|
||||
{
|
||||
"name": "AlbumId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "GenreId",
|
||||
"type": "Int"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
@ -814,6 +860,26 @@
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Genre",
|
||||
"fields": [
|
||||
{
|
||||
"name": "GenreId",
|
||||
"type": "Int"
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
"graphql": {
|
||||
"typeName": "Genre"
|
||||
}
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ObjectType"
|
||||
},
|
||||
{
|
||||
"kind": "ObjectType",
|
||||
"version": "v1",
|
||||
@ -1018,7 +1084,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"kind": "CommandPermissions",
|
||||
"version": "v1",
|
||||
@ -2231,6 +2296,9 @@
|
||||
},
|
||||
"AlbumId": {
|
||||
"column": "AlbumId"
|
||||
},
|
||||
"GenreId": {
|
||||
"column": "GenreId"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2269,6 +2337,12 @@
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
@ -2289,6 +2363,12 @@
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2347,6 +2427,73 @@
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"name": "Genres",
|
||||
"objectType": "Genre",
|
||||
"source": {
|
||||
"dataConnectorName": "db",
|
||||
"collection": "Genre",
|
||||
"typeMapping": {
|
||||
"Genre": {
|
||||
"fieldMapping": {
|
||||
"GenreId": {
|
||||
"column": "GenreId"
|
||||
},
|
||||
"Name": {
|
||||
"column": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
"selectUniques": [
|
||||
{
|
||||
"queryRootField": "GenreByID",
|
||||
"uniqueIdentifier": [
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectMany": {
|
||||
"queryRootField": "Genre"
|
||||
},
|
||||
"filterExpressionType": "Genre_Where_Exp",
|
||||
"orderByExpressionType": "Genre_Order_By"
|
||||
},
|
||||
"filterableFields": [
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"operators": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"orderableFields": [
|
||||
{
|
||||
"fieldName": "GenreId",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldName": "Name",
|
||||
"orderByDirections": {
|
||||
"enableAll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Model"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Artist",
|
||||
@ -2378,6 +2525,16 @@
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user_1",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"AlbumId",
|
||||
"Title",
|
||||
"ArtistId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2394,7 +2551,19 @@
|
||||
"allowedFields": [
|
||||
"TrackId",
|
||||
"Name",
|
||||
"AlbumId"
|
||||
"AlbumId",
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user_1",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"TrackId",
|
||||
"Name",
|
||||
"AlbumId",
|
||||
"GenreId"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -2421,6 +2590,33 @@
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"typeName": "Genre",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"GenreId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user_1",
|
||||
"output": {
|
||||
"allowedFields": [
|
||||
"GenreId",
|
||||
"Name"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "TypePermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Artists",
|
||||
@ -2445,6 +2641,48 @@
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user_1",
|
||||
"select": {
|
||||
"filter": {
|
||||
"relationship": {
|
||||
"name": "Tracks",
|
||||
"predicate": {
|
||||
"and": [
|
||||
{
|
||||
"relationship": {
|
||||
"name": "Album",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "AlbumId",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-user-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"relationship": {
|
||||
"name": "Genre",
|
||||
"predicate": {
|
||||
"fieldComparison": {
|
||||
"field": "GenreId",
|
||||
"operator": "_eq",
|
||||
"value": {
|
||||
"sessionVariable": "x-hasura-user-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2460,6 +2698,12 @@
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user_1",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2481,6 +2725,27 @@
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"modelName": "Genres",
|
||||
"permissions": [
|
||||
{
|
||||
"role": "admin",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user_1",
|
||||
"select": {
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "ModelPermissions"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Artist",
|
||||
@ -2638,6 +2903,38 @@
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"source": "Track",
|
||||
"name": "Genre",
|
||||
"target": {
|
||||
"model": {
|
||||
"name": "Genres",
|
||||
"relationshipType": "Object"
|
||||
}
|
||||
},
|
||||
"mapping": [
|
||||
{
|
||||
"source": {
|
||||
"fieldPath": [
|
||||
{
|
||||
"fieldName": "GenreId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"modelField": [
|
||||
{
|
||||
"fieldName": "GenreId"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "v1",
|
||||
"kind": "Relationship"
|
||||
},
|
||||
{
|
||||
"kind": "DataConnectorScalarRepresentation",
|
||||
"version": "v1",
|
||||
@ -2662,4 +2959,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user