Remove ndc_models types from arguments and order by IR (#798)

Note: This PR is stacked on #797 and should be merged after it.

This PR removes the use of ndc_models types from the arguments and order
by IR. It then shifts the logic that maps the IR to the ndc_models into
the plan part of the code where IR -> ndc_model mapping is performed.

This will help isolate the IR from ndc_models and move us towards being
able to have multiple IR -> ndc_models mapping codes, one per supported
ndc version.

This is a functional no-op PR.

V3_GIT_ORIGIN_REV_ID: 66be868ff4c8185c6190537d570d88813cb7f410
This commit is contained in:
Daniel Chambers 2024-07-05 20:43:55 +10:00 committed by hasura-bot
parent e44589931c
commit 90dbbccce5
4 changed files with 125 additions and 72 deletions

View File

@ -8,7 +8,6 @@ use lang_graphql::ast::common::Name;
use lang_graphql::normalized_ast::{InputField, Value};
use metadata_resolve::TypeMapping;
use metadata_resolve::{Qualified, QualifiedBaseType, QualifiedTypeName, QualifiedTypeReference};
use ndc_models;
use nonempty::NonEmpty;
use open_dds::{
data_connector::DataConnectorColumnName,
@ -18,9 +17,16 @@ use schema::GDS;
use schema::{
Annotation, ArgumentNameAndPath, ArgumentPresets, InputAnnotation, ModelInputAnnotation,
};
use serde::Serialize;
use super::permissions;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum Argument {
/// The argument is provided as a literal value
Literal { value: serde_json::Value },
}
/// Takes a field path and a serde_json object, and insert a serde_json value
/// into that object, following the field path.
///
@ -72,7 +78,7 @@ pub(crate) fn follow_field_path_and_insert_value(
pub(crate) fn process_model_arguments_presets(
argument_presets: &ArgumentPresets,
session_variables: &SessionVariables,
model_arguments: &mut BTreeMap<DataConnectorArgumentName, ndc_models::Argument>,
model_arguments: &mut BTreeMap<DataConnectorArgumentName, arguments::Argument>,
usage_counts: &mut UsagesCounts,
) -> Result<(), error::Error> {
let ArgumentPresets { argument_presets } = argument_presets;
@ -102,7 +108,7 @@ pub(crate) fn process_model_arguments_presets(
None => {
model_arguments.insert(
argument_name.clone(),
ndc_models::Argument::Literal {
arguments::Argument::Literal {
value: actual_value,
},
);
@ -111,14 +117,8 @@ pub(crate) fn process_model_arguments_presets(
Some(field_path) => {
if let Some(current_arg) = model_arguments.get_mut(&argument_name.clone()) {
let current_arg = match current_arg {
ndc_models::Argument::Variable { name: _ } => {
Err(error::InternalEngineError::ArgumentPresetExecution {
description: "unexpected; ndc argument can't be a variable"
.to_string(),
})
}
ndc_models::Argument::Literal { value } => Ok(value),
}?;
arguments::Argument::Literal { value } => value,
};
if let Some(current_arg_object) = current_arg.as_object_mut() {
arguments::follow_field_path_and_insert_value(
&field_path,
@ -166,7 +166,7 @@ pub fn build_ndc_model_arguments<'a, TInputFieldIter: Iterator<Item = &'a InputF
model_operation_field: &Name,
arguments: TInputFieldIter,
model_type_mappings: &BTreeMap<Qualified<CustomTypeName>, TypeMapping>,
) -> Result<BTreeMap<DataConnectorArgumentName, ndc_models::Argument>, error::Error> {
) -> Result<BTreeMap<DataConnectorArgumentName, arguments::Argument>, error::Error> {
let mut ndc_arguments = BTreeMap::new();
for argument in arguments {
match argument.info.generic {
@ -187,7 +187,7 @@ pub fn build_ndc_model_arguments<'a, TInputFieldIter: Iterator<Item = &'a InputF
)?;
ndc_arguments.insert(
ndc_table_argument.clone(),
ndc_models::Argument::Literal {
arguments::Argument::Literal {
value: mapped_argument_value,
},
);

View File

@ -36,7 +36,7 @@ pub struct ModelSelection<'s> {
pub(crate) collection: &'s CollectionName,
// Arguments for the NDC collection
pub(crate) arguments: BTreeMap<DataConnectorArgumentName, ndc_models::Argument>,
pub(crate) arguments: BTreeMap<DataConnectorArgumentName, arguments::Argument>,
// The boolean expression that would fetch a single row from this model
pub(crate) filter_clause: ResolvedFilterExpression<'s>,
@ -58,7 +58,7 @@ pub struct ModelSelection<'s> {
}
struct ModelSelectAggregateArguments<'s> {
model_arguments: BTreeMap<DataConnectorArgumentName, ndc_models::Argument>,
model_arguments: BTreeMap<DataConnectorArgumentName, arguments::Argument>,
filter_input_arguments: FilterInputArguments<'s>,
}
@ -75,7 +75,7 @@ pub(crate) fn model_selection_ir<'s>(
selection_set: &normalized_ast::SelectionSet<'s, GDS>,
data_type: &Qualified<CustomTypeName>,
model_source: &'s metadata_resolve::ModelSource,
arguments: BTreeMap<DataConnectorArgumentName, ndc_models::Argument>,
arguments: BTreeMap<DataConnectorArgumentName, arguments::Argument>,
filter_clauses: ResolvedFilterExpression<'s>,
permissions_predicate: &'s metadata_resolve::FilterPermission,
limit: Option<u32>,
@ -364,7 +364,7 @@ fn model_aggregate_selection_ir<'s>(
aggregate_selection_set: &normalized_ast::SelectionSet<'s, GDS>,
data_type: &Qualified<CustomTypeName>,
model_source: &'s metadata_resolve::ModelSource,
arguments: BTreeMap<DataConnectorArgumentName, ndc_models::Argument>,
arguments: BTreeMap<DataConnectorArgumentName, arguments::Argument>,
filter_clauses: ResolvedFilterExpression<'s>,
permissions_predicate: &'s metadata_resolve::FilterPermission,
limit: Option<u32>,

View File

@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use crate::model_tracking::{count_model, UsagesCounts};
use lang_graphql::normalized_ast::{self as normalized_ast, InputField};
use ndc_models;
use open_dds::data_connector::DataConnectorColumnName;
use schema::OrderByRelationshipAnnotation;
use schema::{Annotation, InputAnnotation, ModelInputAnnotation};
use serde::Serialize;
@ -16,19 +16,33 @@ use schema::GDS;
#[derive(Debug, Serialize)]
pub(crate) struct ResolvedOrderBy<'s> {
pub(crate) order_by: ndc_models::OrderBy,
pub(crate) order_by_elements: Vec<OrderByElement>,
// 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>>,
}
#[derive(Debug, Serialize)]
pub struct OrderByElement {
pub order_direction: schema::ModelOrderByDirection,
pub target: OrderByTarget,
}
#[derive(Debug, Serialize)]
pub enum OrderByTarget {
Column {
name: DataConnectorColumnName,
relationship_path: Vec<NDCRelationshipName>,
},
}
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::List(arguments) => {
let mut ndc_order_elements = Vec::new();
let mut order_by_elements = Vec::new();
let mut relationships = BTreeMap::new();
for v in arguments {
@ -53,7 +67,7 @@ pub(crate) fn build_ndc_order_by<'s>(
&mut relationships,
usage_counts,
)?;
ndc_order_elements.extend(order_by_element);
order_by_elements.extend(order_by_element);
} else {
Err(error::Error::OrderByObjectShouldExactlyHaveOneKeyValuePair)?;
}
@ -64,9 +78,7 @@ pub(crate) fn build_ndc_order_by<'s>(
}
}
Ok(ResolvedOrderBy {
order_by: ndc_models::OrderBy {
elements: ndc_order_elements,
},
order_by_elements,
relationships,
})
}
@ -107,7 +119,7 @@ pub(crate) fn build_ndc_order_by_element<'s>(
mut relationship_paths: Vec<NDCRelationshipName>,
relationships: &mut BTreeMap<NDCRelationshipName, LocalModelRelationshipInfo<'s>>,
usage_counts: &mut UsagesCounts,
) -> Result<Vec<ndc_models::OrderByElement>, error::Error> {
) -> Result<Vec<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,
@ -119,10 +131,7 @@ pub(crate) fn build_ndc_order_by_element<'s>(
let order_direction = match &order_by_value.info.generic {
Annotation::Input(InputAnnotation::Model(
ModelInputAnnotation::ModelOrderByDirection { direction },
)) => match &direction {
schema::ModelOrderByDirection::Asc => ndc_models::OrderDirection::Asc,
schema::ModelOrderByDirection::Desc => ndc_models::OrderDirection::Desc,
},
)) => direction,
&annotation => {
return Err(error::InternalEngineError::UnexpectedAnnotation {
annotation: annotation.clone(),
@ -130,48 +139,12 @@ pub(crate) fn build_ndc_order_by_element<'s>(
}
};
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 {
order_by_element_path.push(ndc_models::PathElement {
relationship: ndc_models::RelationshipName::from(path.0.as_str()),
arguments: BTreeMap::new(),
// 'AND' predicate indicates that the column can be accessed
// by joining all the relationships paths provided
predicate: Some(Box::new(ndc_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 = ndc_models::OrderByElement {
order_direction,
let order_element = OrderByElement {
order_direction: order_direction.clone(),
// TODO(naveen): When aggregates are supported, extend this to support other ndc_models::OrderByTarget
target: ndc_models::OrderByTarget::Column {
name: ndc_models::FieldName::from(ndc_column.as_str()),
path: order_by_element_path,
field_path: None,
target: OrderByTarget::Column {
name: ndc_column.clone(),
relationship_path: relationship_paths,
},
};

View File

@ -7,7 +7,9 @@ use indexmap::IndexMap;
use super::relationships;
use super::selection_set;
use crate::ir::aggregates::{AggregateFieldSelection, AggregateSelectionSet};
use crate::ir::arguments;
use crate::ir::model_selection::ModelSelection;
use crate::ir::order_by;
use crate::plan::error;
use crate::remote_joins::types::{JoinLocations, MonotonicCounter, RemoteJoin};
@ -34,7 +36,10 @@ pub(crate) fn ndc_query<'s, 'ir>(
fields: ndc_fields,
limit: ir.limit,
offset: ir.offset,
order_by: ir.order_by.as_ref().map(|x| x.order_by.clone()),
order_by: ir
.order_by
.as_ref()
.map(|x| ndc_order_by(&x.order_by_elements)),
predicate: ir.filter_clause.expression.clone(),
};
@ -123,10 +128,85 @@ pub(crate) fn ndc_ir<'s, 'ir>(
arguments: ir
.arguments
.iter()
.map(|(k, v)| (ndc_models::ArgumentName::from(k.as_str()), v.clone()))
.map(|(k, v)| {
let literal_value = match v {
arguments::Argument::Literal { value } => value,
};
(
ndc_models::ArgumentName::from(k.as_str()),
ndc_models::Argument::Literal {
value: literal_value.clone(),
},
)
})
.collect(),
collection_relationships,
variables: None,
};
Ok((query_request, join_locations))
}
fn ndc_order_by(order_by_elements: &[order_by::OrderByElement]) -> ndc_models::OrderBy {
ndc_models::OrderBy {
elements: order_by_elements
.iter()
.map(|element| ndc_models::OrderByElement {
order_direction: match element.order_direction {
schema::ModelOrderByDirection::Asc => ndc_models::OrderDirection::Asc,
schema::ModelOrderByDirection::Desc => ndc_models::OrderDirection::Desc,
},
target: ndc_order_by_target(&element.target),
})
.collect(),
}
}
fn ndc_order_by_target(target: &order_by::OrderByTarget) -> ndc_models::OrderByTarget {
match target {
order_by::OrderByTarget::Column {
name,
relationship_path,
} => {
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_path {
order_by_element_path.push(ndc_models::PathElement {
relationship: ndc_models::RelationshipName::from(path.0.as_str()),
arguments: BTreeMap::new(),
// 'AND' predicate indicates that the column can be accessed
// by joining all the relationships paths provided
predicate: Some(Box::new(ndc_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(),
})),
});
}
ndc_models::OrderByTarget::Column {
name: ndc_models::FieldName::from(name.as_str()),
path: order_by_element_path,
field_path: None,
}
}
}
}